diff --git a/.changeset/bitter-carpets-obey.md b/.changeset/bitter-carpets-obey.md deleted file mode 100644 index 2310ccf1ee..0000000000 --- a/.changeset/bitter-carpets-obey.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@leafygreen-ui/guide-cue': minor ---- - -Export `BeaconAlign` enum diff --git a/.changeset/five-bags-carry.md b/.changeset/five-bags-carry.md new file mode 100644 index 0000000000..c70f01dc68 --- /dev/null +++ b/.changeset/five-bags-carry.md @@ -0,0 +1,47 @@ +--- +'@leafygreen-ui/gallery-indicator': patch +'@leafygreen-ui/inline-definition': patch +'@leafygreen-ui/loading-indicator': patch +'@leafygreen-ui/vertical-stepper': patch +'@leafygreen-ui/radio-box-group': patch +'@lg-chat/fixed-chat-window': patch +'@leafygreen-ui/feature-walls': patch +'@leafygreen-ui/input-option': patch +'@leafygreen-ui/progress-bar': patch +'@leafygreen-ui/search-input': patch +'@leafygreen-ui/split-button': patch +'@lg-tools/storybook-addon': patch +'@leafygreen-ui/code-editor': patch +'@leafygreen-ui/descendants': patch +'@leafygreen-ui/form-footer': patch +'@leafygreen-ui/section-nav': patch +'@leafygreen-ui/date-utils': patch +'@leafygreen-ui/form-field': patch +'@lg-tools/eslint-plugin': patch +'@leafygreen-ui/text-area': patch +'@lg-charts/chart-card': patch +'@lg-chat/message-feed': patch +'@leafygreen-ui/side-nav': patch +'@lg-chat/chat-layout': patch +'@lg-chat/chat-window': patch +'@leafygreen-ui/callout': patch +'@leafygreen-ui/popover': patch +'@leafygreen-ui/toolbar': patch +'@leafygreen-ui/drawer': patch +'@leafygreen-ui/modal': patch +'@leafygreen-ui/table': patch +'@lg-tools/validate': patch +'@lg-charts/legend': patch +'@leafygreen-ui/a11y': patch +'@leafygreen-ui/code': patch +'@leafygreen-ui/icon': patch +'@leafygreen-ui/logo': patch +'@leafygreen-ui/menu': patch +'@leafygreen-ui/tabs': patch +'@lg-tools/build': patch +'@lg-tools/link': patch +'@lg-tools/lint': patch +'@lg-tools/test': patch +--- + +removed unnecessary and unused packages diff --git a/.changeset/full-ants-create.md b/.changeset/full-ants-create.md deleted file mode 100644 index 18077583e8..0000000000 --- a/.changeset/full-ants-create.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@leafygreen-ui/split-button': minor ---- - -Export `RenderMode` enum diff --git a/.changeset/loud-parts-flow.md b/.changeset/loud-parts-flow.md deleted file mode 100644 index 9718119247..0000000000 --- a/.changeset/loud-parts-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@leafygreen-ui/combobox': minor ---- - -Export `RenderMode` enum diff --git a/.changeset/public-readers-reply.md b/.changeset/public-readers-reply.md deleted file mode 100644 index 2cdc452914..0000000000 --- a/.changeset/public-readers-reply.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@leafygreen-ui/date-picker': minor ---- - -Export `Align` and `Justify` enums diff --git a/.changeset/tidy-dragons-wait.md b/.changeset/tidy-dragons-wait.md deleted file mode 100644 index a712f68028..0000000000 --- a/.changeset/tidy-dragons-wait.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@lg-tools/link': minor -'@lg-tools/cli': minor ---- - -developer experience slightly improves by adding more detailed logging to spawn processes in the link script. Previously, running link script would lead to a group of processes being spawned in parallel all piping their stdout and stderr to the console in verbose mode which made it difficult to distinguish what each line of output was from which process. The command and working directory are also now logged for each process along with their exit code. diff --git a/.changeset/yellow-carrots-say.md b/.changeset/yellow-carrots-say.md deleted file mode 100644 index 462da972dd..0000000000 --- a/.changeset/yellow-carrots-say.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@leafygreen-ui/menu': minor ---- - -Export `Align`, `Justify`, and `RenderMode` enums diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 5c19b0d234..58c490cf8e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -32,6 +32,7 @@ jobs: path: | charts/*/dist/* chat/*/dist/* + mcp-ui/*/dist/* packages/*/dist/* tools/*/dist/* key: ${{ runner.os }}-build-cache-${{ hashFiles('package.json', 'pnpm-lock.yaml', '**/src/') }} @@ -64,7 +65,7 @@ jobs: - name: Build if: ${{ steps.build-cache.outputs.cache-hit != 'true' }} - run: pnpm build + run: pnpm build:packages - uses: actions/cache/save@v4 name: Save build cache @@ -73,6 +74,7 @@ jobs: path: | charts/*/dist/* chat/*/dist/* + mcp-ui/*/dist/* packages/*/dist/* tools/*/dist/* key: ${{ steps.build-cache.outputs.cache-primary-key }} @@ -111,6 +113,7 @@ jobs: path: | charts/*/dist/* chat/*/dist/* + mcp-ui/*/dist/* packages/*/dist/* tools/*/dist/* key: ${{needs.build.outputs.cache-primary-key}} @@ -150,6 +153,7 @@ jobs: path: | charts/*/dist/* chat/*/dist/* + mcp-ui/*/dist/* packages/*/dist/* tools/*/dist/* key: ${{needs.build.outputs.cache-primary-key}} @@ -201,6 +205,7 @@ jobs: path: | charts/*/dist/* chat/*/dist/* + mcp-ui/*/dist/* packages/*/dist/* tools/*/dist/* key: ${{needs.build.outputs.cache-primary-key}} @@ -216,6 +221,7 @@ jobs: with: lcov-file: coverage/lcov.info delete-old-comments: true + filter-changed-files: true validate-builds: name: Validate builds & dependencies @@ -246,6 +252,7 @@ jobs: path: | charts/*/dist/* chat/*/dist/* + mcp-ui/*/dist/* packages/*/dist/* tools/*/dist/* key: ${{needs.build.outputs.cache-primary-key}} diff --git a/.github/workflows/react17.yml b/.github/workflows/react17.yml index 945f961179..2797b3e063 100644 --- a/.github/workflows/react17.yml +++ b/.github/workflows/react17.yml @@ -32,6 +32,7 @@ jobs: path: | charts/*/dist/* chat/*/dist/* + mcp-ui/*/dist/* packages/*/dist/* tools/*/dist/* key: ${{ runner.os }}-REACT17-build-cache-${{ hashFiles('package.json', 'pnpm-lock.yaml', '**/src/') }} @@ -51,7 +52,7 @@ jobs: run: pnpm install --prefer-offline # Intentionally not using --frozen-lockfile to allow for pnpm-lock.yaml updates - name: Build packages - run: pnpm build + run: pnpm build --filter=!'@lg-apps/*' - uses: actions/cache/save@v4 name: Save build cache @@ -60,6 +61,7 @@ jobs: path: | charts/*/dist/* chat/*/dist/* + mcp-ui/*/dist/* packages/*/dist/* tools/*/dist/* key: ${{ steps.build-cache.outputs.cache-primary-key }} @@ -85,6 +87,7 @@ jobs: path: | charts/*/dist/* chat/*/dist/* + mcp-ui/*/dist/* packages/*/dist/* tools/*/dist/* key: ${{needs.build.outputs.cache-primary-key}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2b3ec4ef95..24f83c3f10 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,7 @@ jobs: chat/*/dist/* chat/*/tsdoc.json chat/*/stories.js + mcp-ui/*/dist/* packages/*/dist/* packages/*/tsdoc.json packages/*/stories.js @@ -58,7 +59,7 @@ jobs: - name: Build if: ${{ steps.build-cache.outputs.cache-hit != 'true' }} - run: pnpm build + run: pnpm build:packages - name: Generate docs if: ${{ steps.build-cache.outputs.cache-hit != 'true' }} @@ -75,6 +76,7 @@ jobs: chat/*/dist/* chat/*/tsdoc.json chat/*/stories.js + mcp-ui/*/dist/* packages/*/dist/* packages/*/tsdoc.json packages/*/stories.js @@ -118,6 +120,7 @@ jobs: chat/*/dist/* chat/*/tsdoc.json chat/*/stories.js + mcp-ui/*/dist/* packages/*/dist/* packages/*/tsdoc.json packages/*/stories.js @@ -171,6 +174,7 @@ jobs: chat/*/dist/* chat/*/tsdoc.json chat/*/stories.js + mcp-ui/*/dist/* packages/*/dist/* packages/*/tsdoc.json packages/*/stories.js @@ -192,66 +196,30 @@ jobs: uses: changesets/action@v1 with: version: pnpm run version # Rebuild packages to ensure any uncommitted pre-build artifacts - publish: pnpm publish -r --no-git-checks # Use `pnpm publish` directly to resolve `workspace` dependencies + publish: pnpm run publish createGithubReleases: true env: GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' NPM_TOKEN: '${{ secrets.NPM_TOKEN }}' + - name: Debug changesets output + run: | + echo "Changesets output: ${{ steps.changesets.outputs }}" + echo "Changesets output type: ${{ toJSON(steps.changesets.outputs) }}" + echo "--------------------------------" + release-aws: name: Publish to AWS CodeArtifact runs-on: ubuntu-latest needs: [build, release] - if: ${{ needs.release.outputs.published == 'true' }} steps: - - uses: actions/checkout@v4 - - - name: pnpm - uses: pnpm/action-setup@v4 - with: - version: 10.18.3 - - - name: Use Node 18 - uses: actions/setup-node@v4 - with: - node-version: 18 - cache: 'pnpm' - cache-dependency-path: 'pnpm-lock.yaml' - - - name: Install Dependencies - run: pnpm install --frozen-lockfile --prefer-offline - - - uses: actions/cache/restore@v4 - name: Restore build cache - id: build-cache - with: - path: | - charts/*/dist/* - charts/*/tsdoc.json - charts/*/stories.js - chat/*/dist/* - chat/*/tsdoc.json - chat/*/stories.js - packages/*/dist/* - packages/*/tsdoc.json - packages/*/stories.js - tools/*/dist/* - tools/*/tsdoc.json - tools/*/stories.js - key: ${{needs.build.outputs.cache-primary-key}} - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ secrets.AWS_CODEARTIFACT_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_CODEARTIFACT_SECRET_ACCESS_KEY }} - aws-region: us-east-1 - - - name: Login to CodeArtifact - run: scripts/login-codeartifact.sh - - - name: Publish to CodeArtifact - run: pnpm publish -r --no-git-checks + - name: Debug published output + run: | + echo "Published: ${{ needs.release.outputs.published }}" + echo "Published type: ${{ toJSON(needs.release.outputs.published) }}" + echo "--------------------------------" + echo "Published Packages: ${{ needs.release.outputs.publishedPackages }}" + echo "--------------------------------" notify: name: Notify Slack & Website @@ -284,6 +252,7 @@ jobs: chat/*/dist/* chat/*/tsdoc.json chat/*/stories.js + mcp-ui/*/dist/* packages/*/dist/* packages/*/tsdoc.json packages/*/stories.js diff --git a/DEVELOPER.md b/DEVELOPER.md index 0a2fa17c4d..f2e02e80d4 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -68,22 +68,54 @@ When you run the scaffold script, a `README` file will appear, which is a templa ### Locally -We use @testing-library/react for writing tests locally. This library helps mock out user interactions with components. You can run all tests by running `yarn test` or turn on watch mode with `yarn test --watch`. +We use @testing-library/react for writing tests locally. This library helps mock out user interactions with components. You can run all tests by running `pnpm test` or turn on watch mode with `pnpm test --watch`. ### Linking -We also have a link script, such that you can test components that are in development in environments beyond Storybook. To do so, run `yarn run link -- [path-to-application]`. +We provide a `link` script to help you test in-development components within environments outside of Storybook such as in your application. To do this, run: -Note: There are some known issues using `yarn link` from yarn workspaces. Using Verdaccio, while more involved, is the more reliable and recommended approach for testing in an external project. +``` +pnpm run link --to=[path-to-application] +``` + +The script does the following in order: + +- It scans the destination application for any installed `leafygreen-ui` components in its `node_modules` folder. + **NOTE:** If the package is new and unpublished/not installed, you will need to create a directory for the new component within the destination application inside `node_modules` before running this command. +- If any `leafygreen-ui` components are found then: + - The script runs `pnpm link` in the corresponding leafygreen-ui package directory to publish a link to the package in the pnpm global registry. + - The script then runs `pnpm link ` in the destination application directory to install the package from the published link in the pnpm global registry. + +After the script completes, you can make changes directly to the component in your local `leafygreen-ui` repository. Once you do this, make sure to rebuild the component and the changes will be visible on your running application. + +If you encounter issues while linking, try the following any of the following flags: + +- When linking multiple packages with `--scope` or multiple `--packages` options, link processes run in parallel by default. If you experience failures, add the `--no-parallel` flag to run the linking tasks sequentially, which can help avoid race conditions. + +- If you are using a Node version manager such as `asdf` or `nvm`, add the `--launch-env="$(env)"` flag. This ensures the link script spawns commands using your current shell’s environment, preventing environment pollutions that may happen through the tooling of the version manager. + +- In your destination application project, make sure your module resolver picks up `'react'` from your own `node_modules` (not from LeafyGreen’s). If using webpack, you can enforce this by adding an alias in your webpack configuration: + + ```js + resolve: { + alias: { + react: path.dirname(require.resolve('react/package.json')) + }, + }, + ``` + +Note: There are some known issues in linking packages with `pnpm link`, if you encounter issues, try using a local registry instead. `Verdaccio` for example is a more reliable and recommended approach for testing in an external project. ### Using a local registry (Verdaccio) -Publishing test versions to a local registry can be helpful when you need to make changes and test in an external app (or other library). To do this, you can install and use [Verdaccio](https://verdaccio.org/) +Publishing test versions to a local registry can be helpful when you need to make changes and test +in an external app (or other library). To do this, you can install and +use [Verdaccio](https://verdaccio.org/) #### 1. Install `verdaccio` ```bash -yarn install --global verdaccio +pnpm install --global verdaccio ``` #### 2. Start `verdaccio`, and make note on the localhost port (should be `http://localhost:4873/` by default) @@ -147,12 +179,12 @@ With your local version published, open up some external app. If the app uses a Next, install the newly published version of your package in the external project. ```bash -yarn install @leafygreen-ui/ +pnpm install @leafygreen-ui/ ``` #### 6. Publishing additional versions -To publish additional versions, manually the version number in `packages//package.json`, and re-run step 4. Then, either manually update the external project's `package.json`, or re-run `yarn install @leafygreen-ui/`. +To publish additional versions, manually update the version number in `packages//package.json`, and re-run step 4. Then, either manually update the external project's `package.json`, or re-run `pnpm install @leafygreen-ui/`. #### 7. Publishing to NPM @@ -160,10 +192,10 @@ If you want to stop publishing to and/or reading from your local Verdaccio serve ## Creating a new component -- Run `yarn create-package ` to create a new component directory with default configurations +- Run `pnpm create-package ` to create a new component directory with default configurations - Add the new component to `build.tsconfig.json` - If you are using any `leafygreen-ui` dependencies in your new component, add the dependency to the component directory's `tsconfig.json`. -- Run `yarn run init` to link all packages before starting development +- Run `pnpm run init` to link all packages before starting development ## Marking a Storybook story to be imported in mongodb.design diff --git a/README.md b/README.md index 16b89cfcdb..23995b85f3 100644 --- a/README.md +++ b/README.md @@ -148,10 +148,8 @@ import Button from '@leafygreen-ui/button'; | [@lg-charts/drag-provider](./charts/drag-provider) | [![version](https://img.shields.io/npm/v/@lg-charts/drag-provider)](https://www.npmjs.com/package/@lg-charts/drag-provider) | ![downloads](https://img.shields.io/npm/dm/@lg-charts/drag-provider?color=white) | [Live Example](http://mongodb.design/component/drag-provider/live-example) | | [@lg-charts/legend](./charts/legend) | [![version](https://img.shields.io/npm/v/@lg-charts/legend)](https://www.npmjs.com/package/@lg-charts/legend) | ![downloads](https://img.shields.io/npm/dm/@lg-charts/legend?color=white) | [Live Example](http://mongodb.design/component/legend/live-example) | | [@lg-charts/series-provider](./charts/series-provider) | [![version](https://img.shields.io/npm/v/@lg-charts/series-provider)](https://www.npmjs.com/package/@lg-charts/series-provider) | ![downloads](https://img.shields.io/npm/dm/@lg-charts/series-provider?color=white) | [Live Example](http://mongodb.design/component/series-provider/live-example) | -| [@lg-chat/avatar](./chat/avatar) | [![version](https://img.shields.io/npm/v/@lg-chat/avatar)](https://www.npmjs.com/package/@lg-chat/avatar) | ![downloads](https://img.shields.io/npm/dm/@lg-chat/avatar?color=white) | [Live Example](http://mongodb.design/component/avatar/live-example) | | [@lg-chat/chat-layout](./chat/chat-layout) | [![version](https://img.shields.io/npm/v/@lg-chat/chat-layout)](https://www.npmjs.com/package/@lg-chat/chat-layout) | ![downloads](https://img.shields.io/npm/dm/@lg-chat/chat-layout?color=white) | [Live Example](http://mongodb.design/component/chat-layout/live-example) | | [@lg-chat/chat-window](./chat/chat-window) | [![version](https://img.shields.io/npm/v/@lg-chat/chat-window)](https://www.npmjs.com/package/@lg-chat/chat-window) | ![downloads](https://img.shields.io/npm/dm/@lg-chat/chat-window?color=white) | [Live Example](http://mongodb.design/component/chat-window/live-example) | -| [@lg-chat/fixed-chat-window](./chat/fixed-chat-window) | [![version](https://img.shields.io/npm/v/@lg-chat/fixed-chat-window)](https://www.npmjs.com/package/@lg-chat/fixed-chat-window) | ![downloads](https://img.shields.io/npm/dm/@lg-chat/fixed-chat-window?color=white) | [Live Example](http://mongodb.design/component/fixed-chat-window/live-example) | | [@lg-chat/input-bar](./chat/input-bar) | [![version](https://img.shields.io/npm/v/@lg-chat/input-bar)](https://www.npmjs.com/package/@lg-chat/input-bar) | ![downloads](https://img.shields.io/npm/dm/@lg-chat/input-bar?color=white) | [Live Example](http://mongodb.design/component/input-bar/live-example) | | [@lg-chat/leafygreen-chat-provider](./chat/leafygreen-chat-provider) | [![version](https://img.shields.io/npm/v/@lg-chat/leafygreen-chat-provider)](https://www.npmjs.com/package/@lg-chat/leafygreen-chat-provider) | ![downloads](https://img.shields.io/npm/dm/@lg-chat/leafygreen-chat-provider?color=white) | [Live Example](http://mongodb.design/component/leafygreen-chat-provider/live-example) | | [@lg-chat/lg-markdown](./chat/lg-markdown) | [![version](https://img.shields.io/npm/v/@lg-chat/lg-markdown)](https://www.npmjs.com/package/@lg-chat/lg-markdown) | ![downloads](https://img.shields.io/npm/dm/@lg-chat/lg-markdown?color=white) | [Live Example](http://mongodb.design/component/lg-markdown/live-example) | @@ -240,26 +238,7 @@ pnpm build --filter="[package]" ### Development within an Application -To actively develop `leafygreen-ui` components within an application, the following script will link all `leafygreen-ui` components within your application to the local `leafygreen-ui` repository. - -This will allow you to make changes to your local repository of `leafygreen-ui` and see those changes immediately reflected within your running application. This allows you to develop both in isolation (within `leafygreen-ui`) and in the context of your application. - -To do this, clone this repository and navigate to the root directory (where `package.json` is located), then run the following: - -``` -pnpm run link -- ${PATH_TO_APPLICATION} -``` - -The script does several things in order: - -1. This builds every `leafygreen-ui` component so they are ready to be linked - -2. It scans your application for any installed `leafygreen-ui` components in your `node_modules/@leafygreen-ui` folder. - **NOTE:** If the package is new and unpublished/not installed, you will need to create a directory for the new component within your application inside `node_modules/@leafygreen-ui` before running this command. - -3. If any `leafygreen-ui` components are found then the script uses `pnpm link` to link every `node_modules/@leafygreen-ui` module to your local `leafygreen-ui` repository. - -After the script completes, you can make changes directly to the component in your local `leafygreen-ui` repository. Once you do this, run `pnpm build` in the root of the `leafygreen-ui` repository and the changes will be visible on your running application. +To actively develop `leafygreen-ui` components within an application, we have a `link` script that will link all `leafygreen-ui` components within your application to the local `leafygreen-ui` repository. See the [DEVELOPER.md](./DEVELOPER.md) file for more information. ## Creating New Component diff --git a/apps/mcp-ui-app/.gitignore b/apps/mcp-ui-app/.gitignore new file mode 100644 index 0000000000..c1cf3d2709 --- /dev/null +++ b/apps/mcp-ui-app/.gitignore @@ -0,0 +1,37 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local +.env.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + diff --git a/apps/mcp-ui-app/README.md b/apps/mcp-ui-app/README.md new file mode 100644 index 0000000000..dc6a87250a --- /dev/null +++ b/apps/mcp-ui-app/README.md @@ -0,0 +1,73 @@ +# MCP UI App + +A Next.js application for the MCP UI, configured for iframe embedding with CORS and CSP headers. + +## Getting Started + +### 1. Environment Setup + +Create a `.env.local` file in the app root with the following variables: + +```bash +# Base URL for the application +NEXT_PUBLIC_BASE_URL=http://localhost:3000 + +# Allowed parent origins for iframe embedding (comma-separated) +ALLOWED_IFRAME_ORIGINS=http://localhost:3000,http://localhost:3001,https://your-app.com +``` + +Configuration options: + +- `NEXT_PUBLIC_BASE_URL`: The base URL of your application +- `ALLOWED_IFRAME_ORIGINS`: Comma-separated list of allowed parent origins for iframe embedding + +### 2. Install dependencies + +```bash +pnpm install +``` + +### 3. Run the development server + +```bash +pnpm dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +## Configuration + +### Base URL + +The base URL is configured via the `NEXT_PUBLIC_BASE_URL` environment variable and can be accessed in your code: + +```typescript +import config from '@/config'; + +console.log(config.baseUrl); // http://localhost:3000 +``` + +### CORS and Iframe Embedding + +The app is configured with: + +- **CORS headers**: Allow cross-origin requests from specified origins +- **CSP headers**: Content Security Policy appropriate for iframe embedding +- **Frame ancestors**: Controls which domains can embed this app in an iframe + +To allow your app to be embedded in an iframe from specific domains, add them to the `ALLOWED_IFRAME_ORIGINS` environment variable. + +## Build + +This app is excluded from the default `pnpm build` command in the workspace. To build this app specifically, run: + +```bash +cd apps/mcp-ui-app +pnpm build +``` + +Or from the workspace root: + +```bash +pnpm build:apps +``` diff --git a/apps/mcp-ui-app/next.config.js b/apps/mcp-ui-app/next.config.js new file mode 100644 index 0000000000..6c1d08e0c8 --- /dev/null +++ b/apps/mcp-ui-app/next.config.js @@ -0,0 +1,60 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + transpilePackages: [ + '@lg-mcp-ui/list-databases', + '@leafygreen-ui/card', + '@leafygreen-ui/typography', + '@leafygreen-ui/emotion', + '@leafygreen-ui/tokens', + '@leafygreen-ui/lib', + ], + + async headers() { + // Get allowed origins from environment variable + const allowedOrigins = process.env.ALLOWED_IFRAME_ORIGINS?.split(',') || [ + '*', + ]; + + return [ + { + // Apply to all routes + source: '/:path*', + headers: [ + // CORS headers for iframe embedding + { + key: 'Access-Control-Allow-Origin', + value: allowedOrigins[0] || '*', // Use first origin or wildcard + }, + { + key: 'Access-Control-Allow-Methods', + value: 'GET, POST, PUT, DELETE, OPTIONS', + }, + { + key: 'Access-Control-Allow-Headers', + value: 'Content-Type, Authorization', + }, + { + key: 'Access-Control-Allow-Credentials', + value: 'true', + }, + // Content Security Policy for iframe embedding + { + key: 'Content-Security-Policy', + value: [ + "default-src 'self'", + "script-src 'self' 'unsafe-eval' 'unsafe-inline'", + "style-src 'self' 'unsafe-inline'", + "img-src 'self' data: https:", + "font-src 'self' data:", + "connect-src 'self'", + `frame-ancestors ${allowedOrigins.join(' ')}`, // Allows embedding in iframes from these origins + ].join('; '), + }, + ], + }, + ]; + }, +}; + +module.exports = nextConfig; diff --git a/apps/mcp-ui-app/package.json b/apps/mcp-ui-app/package.json new file mode 100644 index 0000000000..7f52989096 --- /dev/null +++ b/apps/mcp-ui-app/package.json @@ -0,0 +1,24 @@ +{ + "name": "@lg-apps/mcp-ui-app", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@lg-mcp-ui/list-databases": "workspace:*", + "next": "^14.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/node": "^20.12.5", + "@types/react": "18.2.23", + "@types/react-dom": "18.2.8", + "typescript": "~5.8.0" + } +} + diff --git a/apps/mcp-ui-app/src/app/layout.tsx b/apps/mcp-ui-app/src/app/layout.tsx new file mode 100644 index 0000000000..9ae2c7da9c --- /dev/null +++ b/apps/mcp-ui-app/src/app/layout.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'MCP UI App', + description: 'MCP UI Application', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/apps/mcp-ui-app/src/app/list-databases/page.tsx b/apps/mcp-ui-app/src/app/list-databases/page.tsx new file mode 100644 index 0000000000..43e4e0dfb5 --- /dev/null +++ b/apps/mcp-ui-app/src/app/list-databases/page.tsx @@ -0,0 +1,8 @@ +'use client'; + +import React from 'react'; +import { ListDatabases } from '@lg-mcp-ui/list-databases'; + +export default function ListDatabasesPage() { + return ; +} diff --git a/apps/mcp-ui-app/src/app/page.tsx b/apps/mcp-ui-app/src/app/page.tsx new file mode 100644 index 0000000000..aca119d09b --- /dev/null +++ b/apps/mcp-ui-app/src/app/page.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import config from '@/config'; + +export default function Home() { + return ( +
+

MCP UI App

+

Welcome to the MCP UI Application!

+
+

+ Configuration +

+

+ Base URL: {config.baseUrl} +

+

+ Allowed Origins:{' '} + {config.allowedIframeOrigins.join(', ') || 'Not configured'} +

+
+
+ ); +} diff --git a/apps/mcp-ui-app/src/config/index.ts b/apps/mcp-ui-app/src/config/index.ts new file mode 100644 index 0000000000..85009db6c0 --- /dev/null +++ b/apps/mcp-ui-app/src/config/index.ts @@ -0,0 +1,18 @@ +/** + * Application configuration + */ + +export const config = { + /** + * Base URL of the application + * Defaults to localhost:3000 in development + */ + baseUrl: process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000', + + /** + * Allowed origins for iframe embedding + */ + allowedIframeOrigins: process.env.ALLOWED_IFRAME_ORIGINS?.split(',') || [], +} as const; + +export default config; diff --git a/apps/mcp-ui-app/tsconfig.json b/apps/mcp-ui-app/tsconfig.json new file mode 100644 index 0000000000..7f64e8882a --- /dev/null +++ b/apps/mcp-ui-app/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} + diff --git a/charts/chart-card/CHANGELOG.md b/charts/chart-card/CHANGELOG.md index 5022e21583..7de8bac4e4 100644 --- a/charts/chart-card/CHANGELOG.md +++ b/charts/chart-card/CHANGELOG.md @@ -1,5 +1,18 @@ # @lg-charts/chart-card +## 1.1.4 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/icon-button@17.1.4 + - @leafygreen-ui/typography@22.2.3 + ## 1.1.3 ### Patch Changes diff --git a/charts/chart-card/package.json b/charts/chart-card/package.json index 92f698f82e..4de275f7cc 100644 --- a/charts/chart-card/package.json +++ b/charts/chart-card/package.json @@ -1,6 +1,6 @@ { "name": "@lg-charts/chart-card", - "version": "1.1.3", + "version": "1.1.4", "description": "lg-charts ChartCard", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/charts/core/CHANGELOG.md b/charts/core/CHANGELOG.md index b2880e09c6..49efb0c0f8 100644 --- a/charts/core/CHANGELOG.md +++ b/charts/core/CHANGELOG.md @@ -1,5 +1,36 @@ # @lg-charts/core +## 2.4.2 + +### Patch Changes + +- 651c0bb: Ensure array fields like `labels` in `` are updated as expected in the chart options. + This update changes the recursive merge function in the EChart options utility to treat arrays as atoms: instead of merging their elements, it now replaces arrays entirely. Previously, arrays were being merged like objects, resulting in arrays becoming plain objects with numeric keys. With this fix, when merging, any existing array field (such as `labels` on ``) is now overwritten by the new array rather than partially merged. + +## 2.4.1 + +### Patch Changes + +- c5628dc: Fix the issue of chart tooltips not hiding when the user moves their mouse away from the chart, not through simple mouse movement (e.g., by scrolling away) + +## 2.4.0 + +### Minor Changes + +- 980a17f: - Adds `index` extra parameter to `headerFormatter` prop of `ChartTooltip` component, particularly useful to format header of category axes. + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/icon-button@17.1.4 + - @leafygreen-ui/typography@22.2.3 + - @lg-charts/chart-card@1.1.4 + ## 2.3.1 ### Patch Changes diff --git a/charts/core/package.json b/charts/core/package.json index 07e5f8350e..dc93b0e6f1 100644 --- a/charts/core/package.json +++ b/charts/core/package.json @@ -1,6 +1,6 @@ { "name": "@lg-charts/core", - "version": "2.3.1", + "version": "2.4.2", "description": "lg-charts Core Chart Components", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/charts/core/src/BarChart.stories.tsx b/charts/core/src/BarChart.stories.tsx new file mode 100644 index 0000000000..0d0afb6072 --- /dev/null +++ b/charts/core/src/BarChart.stories.tsx @@ -0,0 +1,210 @@ +import React, { useState } from 'react'; +import type { StoryObj } from '@storybook/react'; +import { expect, getByTestId, waitFor } from '@storybook/test'; +// because storybook ts files are excluded in the tsconfig to avoid redundant type-definition generation +// @ts-ignore +import { EChartsOption, getInstanceByDom, SeriesOption } from 'echarts'; + +import { ChartTooltipProps } from './ChartTooltip/ChartTooltip.types'; +import { BarHoverBehavior } from './Series/Bar'; +import { Bar } from './Series'; +import { makeSeriesData } from './testUtils'; +import { Chart, ChartTooltip, XAxis, YAxis } from '.'; + +const numOfLineColors = 15; +const seriesData = makeSeriesData(numOfLineColors); +const lowDensitySeriesData = seriesData + .filter((_, i) => i < 3) + .map(series => ({ + ...series, + data: series.data.filter((d, i) => i % 4 === 0), + })); + +export default { + title: 'Composition/Charts/Core/Bar', + component: Chart, +}; + +export const _Bar: StoryObj<{}> = { + render: () => { + return ( + + + {lowDensitySeriesData.map(({ name, data }) => ( + + ))} + + ); + }, +}; + +export const BarStacked: StoryObj<{}> = { + render: () => { + return ( + + + {lowDensitySeriesData.map(({ name, data }, index) => ( + + ))} + + ); + }, +}; + +export const BarWithOnHoverDimOthersBehavior: StoryObj<{ + hoverBehavior: BarHoverBehavior; +}> = { + args: { + hoverBehavior: BarHoverBehavior.DimOthers, + }, + argTypes: { + hoverBehavior: { + control: 'select', + options: [BarHoverBehavior.DimOthers, BarHoverBehavior.None], + defaultValue: BarHoverBehavior.DimOthers, + }, + }, + render: ({ hoverBehavior }) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [ready, setReady] = useState(false); + + return ( + setReady(true)}> + {ready &&
} + + {lowDensitySeriesData.map(({ name, data }) => ( + + ))} + + ); + }, + play: async ({ canvasElement, step }) => { + const chartContainerElement = getByTestId(canvasElement, 'chart-component'); + + const echartsInstance = await waitFor(function getEChartsInstance() { + expect(getByTestId(canvasElement, 'chart-is-ready')).toBeVisible(); + + const instance = getInstanceByDom(chartContainerElement); + if (!instance) throw new Error('ECharts instance not found'); + return instance; + }); + + await waitFor(function WaitForSeries() { + const option = echartsInstance.getOption() as EChartsOption; + const series = (option.series as Array) || []; + expect(series.length).toBe(lowDensitySeriesData.length); + }); + + await step('Trigger data point hover', () => { + echartsInstance.dispatchAction( + { + type: 'highlight', + seriesIndex: 1, + dataIndex: 0, + }, + { + flush: true, + }, + ); + }); + }, +}; + +export const BarWithCustomAxisPointer: StoryObj<{ + axisPointer: ChartTooltipProps['axisPointer']; +}> = { + args: { + axisPointer: 'shadow', + }, + argTypes: { + axisPointer: { + control: 'select', + options: ['line', 'shadow', 'none'], + defaultValue: 'shadow', + }, + }, + render: ({ axisPointer }) => { + return ( + + { + const date = new Date(value); + const minutes = String(date.getUTCMinutes()).padStart(2, '0'); + const seconds = String(date.getUTCSeconds()).padStart(2, '0'); + return `${minutes}:${seconds}`; + }} + /> + + + {lowDensitySeriesData.map(({ name, data }) => ( + + ))} + + ); + }, +}; + +export const BarWithCategoryAxisLabel: StoryObj<{ + axisPointer: ChartTooltipProps['axisPointer']; +}> = { + args: { + axisPointer: 'shadow', + }, + argTypes: { + axisPointer: { + control: 'select', + options: ['line', 'shadow', 'none'], + defaultValue: 'shadow', + }, + }, + render: ({ axisPointer }) => { + const xAxisData = lowDensitySeriesData[0].data.map(([x, _]) => + // Format as "mm:ss" instead of slicing the ISO string + (() => { + const date = new Date(x as number); + const minutes = String(date.getUTCMinutes()).padStart(2, '0'); + const seconds = String(date.getUTCSeconds()).padStart(2, '0'); + return `⏱️ ${minutes}:${seconds}`; + })(), + ); + + const yAxisData = [ + 'Low', + 'Medium-Low', + 'Medium', + 'Medium-High', + 'High', + 'Very High', + 'Extreme', + ]; + + return ( + + + + + {lowDensitySeriesData.map(({ name, data }) => ( + [ + i, + (value as number) % yAxisData.length, + ])} + /> + ))} + + ); + }, +}; diff --git a/charts/core/src/Chart.stories.tsx b/charts/core/src/Chart.stories.tsx index 79dad1f471..f5d3e62f3d 100644 --- a/charts/core/src/Chart.stories.tsx +++ b/charts/core/src/Chart.stories.tsx @@ -1,16 +1,22 @@ import React, { useState } from 'react'; import { storybookArgTypes } from '@lg-tools/storybook-utils'; import type { StoryObj } from '@storybook/react'; -import { getByTestId, waitFor } from '@storybook/test'; +import { + expect, + getByTestId, + getByText, + userEvent, + waitFor, +} from '@storybook/test'; +// because storybook ts files are excluded in the tsconfig to avoid redundant type-definition generation // @ts-ignore -import { getInstanceByDom } from 'echarts'; +import { EChartsOption, getInstanceByDom, SeriesOption } from 'echarts'; import { ChartProps } from './Chart/Chart.types'; import { ChartHeaderProps } from './ChartHeader/ChartHeader.types'; import { ChartTooltipProps } from './ChartTooltip/ChartTooltip.types'; -import { BarHoverBehavior } from './Series/Bar'; import { ContinuousAxisProps } from './Axis'; -import { Bar, LineProps } from './Series'; +import { LineProps } from './Series'; import { makeSeriesData } from './testUtils'; import { ThresholdLineProps } from './ThresholdLine'; import { @@ -32,12 +38,6 @@ import { const numOfLineColors = 15; const seriesData = makeSeriesData(numOfLineColors); -const lowDensitySeriesData = seriesData - .filter((_, i) => i < 3) - .map(series => ({ - ...series, - data: series.data.filter((d, i) => i % 4 === 0), - })); // Dynamically calculate x-axis min/max from the generated data to avoid timezone issues const allXValues = @@ -49,42 +49,7 @@ const xAxisMax = Math.max(...allXValues); const yAxisMin = 0; const yAxisMax = 2500; -export default { - title: 'Composition/Charts/Core', - component: Chart, - parameters: { - default: 'LiveExample', - chromatic: { - /** - * For some reason diffs keep getting flagged on non-changes to the canvas. - * The default threshold is .063, so bumping it up to 1 to test. We might - * consider lowering this in the future. - */ - diffThreshold: 1, - }, - }, - - argTypes: { - darkMode: storybookArgTypes.darkMode, - state: { - table: { - disable: true, - }, - }, - key: { - table: { - disable: true, - }, - }, - ref: { - table: { - disable: true, - }, - }, - }, -}; - -export const LiveExample: StoryObj<{ +interface LiveExampleProps { darkMode: boolean; data: Array; state: ChartProps['state']; @@ -130,7 +95,44 @@ export const LiveExample: StoryObj<{ thresholdLineLabel: ThresholdLineProps['label']; thresholdLineValue: ThresholdLineProps['value']; thresholdLinePosition: ThresholdLineProps['position']; -}> = { +} + +export default { + title: 'Composition/Charts/Core', + component: Chart, + parameters: { + default: 'LiveExample', + chromatic: { + /** + * For some reason diffs keep getting flagged on non-changes to the canvas. + * The default threshold is .063, so bumping it up to 1 to test. We might + * consider lowering this in the future. + */ + diffThreshold: 1, + }, + }, + + argTypes: { + darkMode: storybookArgTypes.darkMode, + state: { + table: { + disable: true, + }, + }, + key: { + table: { + disable: true, + }, + }, + ref: { + table: { + disable: true, + }, + }, + }, +} as StoryObj; + +export const LiveExample: StoryObj = { args: { data: seriesData, state: 'unset', @@ -898,163 +900,6 @@ export const _Line: StoryObj<{}> = { }, }; -export const _Bar: StoryObj<{}> = { - render: () => { - return ( - - - {lowDensitySeriesData.map(({ name, data }) => ( - - ))} - - ); - }, -}; - -export const BarStacked: StoryObj<{}> = { - render: () => { - return ( - - - {lowDensitySeriesData.map(({ name, data }, index) => ( - - ))} - - ); - }, -}; - -export const BarWithOnHoverDimOthersBehavior: StoryObj<{ - hoverBehavior: BarHoverBehavior; -}> = { - args: { - hoverBehavior: BarHoverBehavior.DimOthers, - }, - argTypes: { - hoverBehavior: { - control: 'select', - options: [BarHoverBehavior.DimOthers, BarHoverBehavior.None], - defaultValue: BarHoverBehavior.DimOthers, - }, - }, - render: ({ hoverBehavior }) => { - return ( - - - {lowDensitySeriesData.map(({ name, data }) => ( - - ))} - - ); - }, - play: async ({ canvasElement }) => { - const chartDom = getByTestId(canvasElement, 'chart-component'); - - const echartsInstance = await waitFor(function getEChartsInstance() { - const instance = getInstanceByDom(chartDom); - if (!instance) throw new Error('ECharts instance not found'); - return instance; - }); - - echartsInstance.dispatchAction({ - type: 'highlight', - seriesIndex: 1, - dataIndex: 0, - }); - }, -}; - -export const BarWithCustomAxisPointer: StoryObj<{ - axisPointer: ChartTooltipProps['axisPointer']; -}> = { - args: { - axisPointer: 'shadow', - }, - argTypes: { - axisPointer: { - control: 'select', - options: ['line', 'shadow', 'none'], - defaultValue: 'shadow', - }, - }, - render: ({ axisPointer }) => { - return ( - - - - - {lowDensitySeriesData.map(({ name, data }) => ( - - ))} - - ); - }, -}; - -export const BarWithCategoryAxisLabel: StoryObj<{ - axisPointer: ChartTooltipProps['axisPointer']; -}> = { - args: { - axisPointer: 'shadow', - }, - argTypes: { - axisPointer: { - control: 'select', - options: ['line', 'shadow', 'none'], - defaultValue: 'shadow', - }, - }, - render: ({ axisPointer }) => { - const xAxisData = lowDensitySeriesData[0].data.map(([x, _]) => - // Format as "mm:ss" instead of slicing the ISO string - (() => { - const date = new Date(x as number); - const minutes = String(date.getUTCMinutes()).padStart(2, '0'); - const seconds = String(date.getUTCSeconds()).padStart(2, '0'); - return `⏱️ ${minutes}:${seconds}`; - })(), - ); - - const yAxisData = [ - 'Low', - 'Medium-Low', - 'Medium', - 'Medium-High', - 'High', - 'Very High', - 'Extreme', - ]; - - return ( - - - - - {lowDensitySeriesData.map(({ name, data }) => ( - [ - i, - (value as number) % yAxisData.length, - ])} - /> - ))} - - ); - }, -}; - export const NullValues: StoryObj<{}> = { render: () => { return ( @@ -1077,8 +922,12 @@ export const NullValues: StoryObj<{}> = { export const WithTooltip: StoryObj<{}> = { render: () => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [ready, setReady] = useState(false); + return ( - + setReady(true)}> + {ready &&
} {seriesData.map(({ name, data }) => ( @@ -1086,6 +935,57 @@ export const WithTooltip: StoryObj<{}> = { ); }, + play: async ({ canvasElement, step }) => { + const chartContainerElement = getByTestId(canvasElement, 'chart-component'); + + const [echartsInstance, chartCanvasElement] = await waitFor( + function CaptureEChartsInstanceAndCanvas() { + expect(getByTestId(canvasElement, 'chart-is-ready')).toBeVisible(); + + const instance = getInstanceByDom(chartContainerElement); + if (!instance) throw new Error('ECharts instance not found'); + + const canvas = + chartContainerElement.querySelector('canvas'); + if (!canvas) throw new Error('Canvas element not found'); + + return [instance, canvas]; + }, + ); + + await waitFor(function WaitForSeries() { + const option = echartsInstance.getOption() as EChartsOption; + const series = (option.series as Array) || []; + expect(series.length).toBe(seriesData.length); + }); + + const tooltipElement = await waitFor( + function TriggerTooltipAndAssertHeader() { + echartsInstance.dispatchAction( + { + type: 'showTip', + seriesIndex: 1, + dataIndex: 0, + }, + { + flush: true, + }, + ); + const tooltip = canvasElement.querySelector('.lg-chart-tooltip'); + expect(tooltip).toBeVisible(); + getByText(tooltip as HTMLElement, 'Jan 1, 2024, 12:00:00 AM (UTC)'); + return tooltip; + }, + ); + + await step('Trigger tooltip hide', async () => { + userEvent.unhover(chartCanvasElement!); + }); + + await waitFor(function AssertTooltipIsHidden() { + expect(tooltipElement).not.toBeVisible(); + }); + }, }; export const WithXAxis: StoryObj<{}> = { diff --git a/charts/core/src/ChartTooltip/ChartTooltip.stories.tsx b/charts/core/src/ChartTooltip/ChartTooltip.stories.tsx index 05274dd6a7..1c2252ef90 100644 --- a/charts/core/src/ChartTooltip/ChartTooltip.stories.tsx +++ b/charts/core/src/ChartTooltip/ChartTooltip.stories.tsx @@ -76,7 +76,7 @@ export default { decorator: TooltipRoot, }, }, -}; +} as StoryObj; export const Default: StoryObj = { args: { diff --git a/charts/core/src/ChartTooltip/ChartTooltip.tsx b/charts/core/src/ChartTooltip/ChartTooltip.tsx index dc607cc000..c096e041b6 100644 --- a/charts/core/src/ChartTooltip/ChartTooltip.tsx +++ b/charts/core/src/ChartTooltip/ChartTooltip.tsx @@ -114,7 +114,7 @@ export function ChartTooltip({ renderMode: 'html', showContent: enableGroupTooltipSync || isChartHovered || tooltipPinned, trigger: 'axis', - triggerOn: 'none', + triggerOn: tooltipPinned ? 'none' : 'mousemove', /* STYLING PROPERTIES */ /** diff --git a/charts/core/src/ChartTooltip/ChartTooltip.types.ts b/charts/core/src/ChartTooltip/ChartTooltip.types.ts index 2f2f9c88d7..ebe2591a91 100644 --- a/charts/core/src/ChartTooltip/ChartTooltip.types.ts +++ b/charts/core/src/ChartTooltip/ChartTooltip.types.ts @@ -26,7 +26,7 @@ export interface ChartTooltipProps { sort?: (seriesA: SeriesInfo, seriesB: SeriesInfo) => number; seriesValueFormatter?: (value: OptionDataValue) => ReactNode; seriesNameFormatter?: (name: SeriesName) => ReactNode; - headerFormatter?: (value: number | string) => ReactNode; + headerFormatter?: (value: number | string, index: number) => ReactNode; /** * Specifies the visual indicator (axis pointer) used with the tooltip: * - 'line' (default): Shows a vertical dashed line on hover. diff --git a/charts/core/src/ChartTooltip/CustomTooltip/CustomTooltip.spec.tsx b/charts/core/src/ChartTooltip/CustomTooltip/CustomTooltip.spec.tsx index 961872c462..7e7e74fee8 100644 --- a/charts/core/src/ChartTooltip/CustomTooltip/CustomTooltip.spec.tsx +++ b/charts/core/src/ChartTooltip/CustomTooltip/CustomTooltip.spec.tsx @@ -129,12 +129,17 @@ describe('@lg-charts/core/ChartTooltip/CustomTooltip', () => { expect(screen.getByText('$300')).toBeInTheDocument(); }); - test('should render custom axis value with headerFormatter', () => { + test('should render custom axis header with headerFormatter', () => { renderCustomTooltip({ - headerFormatter: () => `Axis Value: test`, + headerFormatter: (value, dataIndex) => + `Axis Value: ${value} ${dataIndex}`, }); - expect(screen.getByText('Axis Value: test')).toBeInTheDocument(); + expect( + screen.getByText( + `Axis Value: ${baseSeriesData.axisValue} ${baseSeriesData.dataIndex}`, + ), + ).toBeInTheDocument(); }); test('should properly display zero values', () => { diff --git a/charts/core/src/ChartTooltip/CustomTooltip/CustomTooltip.tsx b/charts/core/src/ChartTooltip/CustomTooltip/CustomTooltip.tsx index 73383b48b5..204ade29c3 100644 --- a/charts/core/src/ChartTooltip/CustomTooltip/CustomTooltip.tsx +++ b/charts/core/src/ChartTooltip/CustomTooltip/CustomTooltip.tsx @@ -54,6 +54,7 @@ export function CustomTooltip({ seriesData[0].axisType === 'xAxis.time' ? (seriesData[0].axisValue as number) // Should be num since axisType is time : seriesData[0].axisValueLabel, + seriesData[0].dataIndex, ); } else { axisValueLabel = diff --git a/charts/core/src/Echart/utils/updateUtils.spec.ts b/charts/core/src/Echart/utils/updateUtils.spec.ts index 0b9b44609c..4ad4c08e44 100644 --- a/charts/core/src/Echart/utils/updateUtils.spec.ts +++ b/charts/core/src/Echart/utils/updateUtils.spec.ts @@ -53,7 +53,9 @@ describe('@lg-charts/core/Chart/hooks/updateUtils', () => { test('updateOptions should merge chart options non-destructively', () => { const currentOptions: Partial = { xAxis: { + type: 'category', show: true, + data: ['series1', 'series2', 'series3'], splitLine: { show: true, }, @@ -61,7 +63,9 @@ describe('@lg-charts/core/Chart/hooks/updateUtils', () => { }; const updatedOptions = updateOptions(currentOptions, { xAxis: { + type: 'category', show: false, // This should only update the show property and not other properties + data: ['series4', 'series5'], }, grid: { show: true, @@ -69,6 +73,8 @@ describe('@lg-charts/core/Chart/hooks/updateUtils', () => { }); // @ts-ignore: Property 'show' does not exist on type 'Arrayable'. expect(updatedOptions?.xAxis?.show).toBe(false); + // @ts-ignore: Property 'data' does not exist on type 'Arrayable'. + expect(updatedOptions?.xAxis?.data).toEqual(['series4', 'series5']); // @ts-ignore: Property 'show' does not exist on type 'Arrayable'. expect(updatedOptions?.xAxis?.splitLine?.show).toBe(true); // @ts-ignore: Property 'show' does not exist on type 'Arrayable'. diff --git a/charts/core/src/Echart/utils/updateUtils.ts b/charts/core/src/Echart/utils/updateUtils.ts index 0f0917ed0c..f0e3220932 100644 --- a/charts/core/src/Echart/utils/updateUtils.ts +++ b/charts/core/src/Echart/utils/updateUtils.ts @@ -1,3 +1,5 @@ +import { isPlainObject } from 'lodash'; + import { EChartOptions, EChartSeriesOption } from '../Echart.types'; export function addSeries( @@ -46,10 +48,7 @@ function recursiveMerge( const updatedObj = { ...target }; for (const key in source) { - if ( - typeof source[key] === 'object' && - typeof updatedObj[key] === 'object' - ) { + if (isPlainObject(source[key]) && isPlainObject(updatedObj[key])) { // Recursively update nested objects updatedObj[key] = recursiveMerge(updatedObj[key], source[key]); } else { diff --git a/charts/core/src/testUtils/makeSeriesData.testUtils.ts b/charts/core/src/testUtils/makeSeriesData.testUtils.ts index 57d96a464c..21f48aed4d 100644 --- a/charts/core/src/testUtils/makeSeriesData.testUtils.ts +++ b/charts/core/src/testUtils/makeSeriesData.testUtils.ts @@ -146,7 +146,7 @@ export function makeSeriesData(numOfSets: number): Array { const seriesParams = generateSeriesParams(numOfSets); // Create the start date - const startDate = new Date(2024, 0, 1); // January 1st, 2024 + const startDate = Date.parse('2024-01-01T00:00:00.000Z'); // January 1st, 2024 for (let i = 0; i < numOfSets; i++) { const series: SeriesProps = { diff --git a/charts/legend/CHANGELOG.md b/charts/legend/CHANGELOG.md index 96c9eb48ef..0ee45df75b 100644 --- a/charts/legend/CHANGELOG.md +++ b/charts/legend/CHANGELOG.md @@ -1,5 +1,15 @@ # @lg-charts/legend +## 1.0.10 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/checkbox@18.1.4 + ## 1.0.9 ### Patch Changes diff --git a/charts/legend/package.json b/charts/legend/package.json index c32d76b1d0..0ac0feff7b 100644 --- a/charts/legend/package.json +++ b/charts/legend/package.json @@ -1,6 +1,6 @@ { "name": "@lg-charts/legend", - "version": "1.0.9", + "version": "1.0.10", "description": "lg-charts Legend", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -26,9 +26,13 @@ "@leafygreen-ui/leafygreen-provider": "workspace:^5.0.0 || ^4.0.0 || ^3.2.0" }, "devDependencies": { + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@faker-js/faker": "8.0.2", "@leafygreen-ui/icon": "workspace:^", "@lg-charts/core": "workspace:^", - "@lg-tools/build": "workspace:^" + "@lg-tools/build": "workspace:^", + "echarts": "^5.5.0" }, "repository": { "type": "git", diff --git a/chat/chat-layout/CHANGELOG.md b/chat/chat-layout/CHANGELOG.md index 6d4e5c9f3e..fd1425e4b1 100644 --- a/chat/chat-layout/CHANGELOG.md +++ b/chat/chat-layout/CHANGELOG.md @@ -1,5 +1,25 @@ # @lg-chat/chat-layout +## 0.2.0 + +### Minor Changes + +- 4797af9: [LG-5712](https://jira.mongodb.org/browse/LG-5712): handle overflow in `ChatSideNav.SideNavItem` and add compact tooltip for truncated text + +### Patch Changes + +- 4797af9: Expand `ChatSideNav` when descendant element is focused +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [f7a63e2] +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/tooltip@14.3.0 + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/avatar@3.1.6 + - @leafygreen-ui/typography@22.2.3 + ## 0.1.1 ### Patch Changes diff --git a/chat/chat-layout/package.json b/chat/chat-layout/package.json index cdcbaf5dcc..5a2a950e8e 100644 --- a/chat/chat-layout/package.json +++ b/chat/chat-layout/package.json @@ -1,6 +1,6 @@ { "name": "@lg-chat/chat-layout", - "version": "0.1.1", + "version": "0.2.0", "description": "LeafyGreen UI Kit Chat Layout", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -31,24 +31,31 @@ "@leafygreen-ui/button": "workspace:^", "@leafygreen-ui/compound-component": "workspace:^", "@leafygreen-ui/emotion": "workspace:^", + "@leafygreen-ui/hooks": "workspace:^", "@leafygreen-ui/icon": "workspace:^", "@leafygreen-ui/lib": "workspace:^", "@leafygreen-ui/palette": "workspace:^", "@leafygreen-ui/polymorphic": "workspace:^", "@leafygreen-ui/tokens": "workspace:^", - "@leafygreen-ui/typography": "workspace:^", - "@lg-tools/test-harnesses": "workspace:^" + "@leafygreen-ui/typography": "workspace:^" }, "peerDependencies": { "@leafygreen-ui/leafygreen-provider": "workspace:^5.0.0 || ^4.0.0 || ^3.2.0", "@lg-chat/leafygreen-chat-provider": "workspace:^6.0.0" }, "devDependencies": { + "@floating-ui/react": "^0.26.20", "@lg-chat/chat-window": "workspace:^", "@lg-chat/input-bar": "workspace:^", "@lg-chat/message": "workspace:^", "@lg-chat/message-feed": "workspace:^", - "@lg-chat/title-bar": "workspace:^" + "@lg-chat/title-bar": "workspace:^", + "@lg-tools/build": "workspace:^", + "clipboard": "^2.0.11", + "facepaint": "^1.2.1", + "highlight.js": "^11.10.0", + "highlightjs-graphql": "^1.0.2", + "polished": "^4.2.2" }, "homepage": "https://github.com/mongodb/leafygreen-ui/tree/main/chat/chat-layout", "repository": { diff --git a/chat/chat-layout/src/ChatLayout.stories.tsx b/chat/chat-layout/src/ChatLayout.stories.tsx index 580bb04408..66ce6979c1 100644 --- a/chat/chat-layout/src/ChatLayout.stories.tsx +++ b/chat/chat-layout/src/ChatLayout.stories.tsx @@ -57,12 +57,34 @@ export default meta; const chatItems = [ { id: '1', name: 'MongoDB Atlas Setup', href: '/chat/1' }, - { id: '2', name: 'Writing a Database Query', href: '/chat/2' }, + { + id: '2', + name: 'Writing a very very very long Database Query', + href: '/chat/2', + }, { id: '3', name: 'Schema Design Discussion', href: '/chat/3' }, { id: '4', name: 'Performance Optimization', href: '/chat/4' }, { id: '5', name: 'Migration Planning', href: '/chat/5' }, ]; +const hoverSideNav = async (canvasElement: HTMLElement) => { + const canvas = within(canvasElement); + const sideNav = canvas.getByLabelText('Side navigation'); + await userEvent.hover(sideNav); +}; + +const hoverSideNavItem = async ({ + canvasElement, + itemText, +}: { + canvasElement: HTMLElement; + itemText: string; +}) => { + const canvas = within(canvasElement); + const item = canvas.getByText(itemText); + await userEvent.hover(item); +}; + const Template: StoryFn = props => { const [activeId, setActiveId] = useState('1'); @@ -172,13 +194,7 @@ export const UnpinnedAndHoveredLight: StoryObj = { initialIsPinned: false, }, play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - // Find the side nav - const sideNav = canvas.getByLabelText('Side navigation'); - - // Hover over the side nav - await userEvent.hover(sideNav); + await hoverSideNav(canvasElement); }, parameters: { chromatic: { @@ -194,13 +210,39 @@ export const UnpinnedAndHoveredDark: StoryObj = { initialIsPinned: false, }, play: async ({ canvasElement }) => { - const canvas = within(canvasElement); + await hoverSideNav(canvasElement); + }, + parameters: { + chromatic: { + delay: 350, + }, + }, +}; - // Find the side nav - const sideNav = canvas.getByLabelText('Side navigation'); +export const UnpinnedAndFocusedLight: StoryObj = { + render: Template, + args: { + darkMode: false, + initialIsPinned: false, + }, + play: async ({ canvasElement: _canvasElement }) => { + userEvent.tab(); + }, + parameters: { + chromatic: { + delay: 350, + }, + }, +}; - // Hover over the side nav - await userEvent.hover(sideNav); +export const UnpinnedAndFocusedDark: StoryObj = { + render: Template, + args: { + darkMode: true, + initialIsPinned: false, + }, + play: async ({ canvasElement: _canvasElement }) => { + userEvent.tab(); }, parameters: { chromatic: { @@ -208,3 +250,41 @@ export const UnpinnedAndHoveredDark: StoryObj = { }, }, }; + +export const SideNavItemHoveredLight: StoryObj = { + render: Template, + args: { + darkMode: false, + initialIsPinned: true, + }, + play: async ({ canvasElement }) => { + await hoverSideNavItem({ + canvasElement, + itemText: 'Writing a very very very long Database Query', + }); + }, + parameters: { + chromatic: { + delay: 550, + }, + }, +}; + +export const SideNavItemHoveredDark: StoryObj = { + render: Template, + args: { + darkMode: true, + initialIsPinned: true, + }, + play: async ({ canvasElement }) => { + await hoverSideNavItem({ + canvasElement, + itemText: 'Writing a very very very long Database Query', + }); + }, + parameters: { + chromatic: { + delay: 550, + }, + }, +}; diff --git a/chat/chat-layout/src/ChatSideNav/ChatSideNav.spec.tsx b/chat/chat-layout/src/ChatSideNav/ChatSideNav.spec.tsx index 90a0fac794..6364665ab2 100644 --- a/chat/chat-layout/src/ChatSideNav/ChatSideNav.spec.tsx +++ b/chat/chat-layout/src/ChatSideNav/ChatSideNav.spec.tsx @@ -10,6 +10,14 @@ const Providers = ({ children }: { children: React.ReactNode }) => ( ); describe('ChatSideNav', () => { + beforeAll(() => { + window.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + })); + }); + test('Header shows "New Chat" button when onClickNewChat provided', async () => { const onClickNewChat = jest.fn(); diff --git a/chat/chat-layout/src/ChatSideNav/ChatSideNav.tsx b/chat/chat-layout/src/ChatSideNav/ChatSideNav.tsx index 89469aa08b..0d6d07da0f 100644 --- a/chat/chat-layout/src/ChatSideNav/ChatSideNav.tsx +++ b/chat/chat-layout/src/ChatSideNav/ChatSideNav.tsx @@ -1,9 +1,10 @@ -import React, { forwardRef } from 'react'; +import React, { FocusEventHandler, forwardRef, useRef } from 'react'; import { CompoundComponent, findChild, } from '@leafygreen-ui/compound-component'; +import { useMergeRefs } from '@leafygreen-ui/hooks'; import LeafyGreenProvider, { useDarkMode, } from '@leafygreen-ui/leafygreen-provider'; @@ -23,10 +24,12 @@ import { ChatSideNavItem } from './ChatSideNavItem'; export const ChatSideNav = CompoundComponent( // eslint-disable-next-line react/display-name forwardRef( - ({ children, className, darkMode: darkModeProp, ...rest }, ref) => { + ({ children, className, darkMode: darkModeProp, ...rest }, fwdRef) => { const { darkMode, theme } = useDarkMode(darkModeProp); const { isPinned, setIsSideNavHovered, shouldRenderExpanded } = useChatLayoutContext(); + const navRef = useRef(null); + const ref = useMergeRefs([navRef, fwdRef]); const handleMouseEnter = () => { setIsSideNavHovered(true); @@ -36,7 +39,24 @@ export const ChatSideNav = CompoundComponent( setIsSideNavHovered(false); }; - // Find subcomponents + const handleFocus: FocusEventHandler = ({ target }) => { + const navElement = navRef.current; + + if (navElement?.contains(target as Node)) { + setIsSideNavHovered(true); + } + }; + + const handleBlur: FocusEventHandler = ({ + relatedTarget, + }) => { + const navElement = navRef.current; + + if (relatedTarget && !navElement?.contains(relatedTarget as Node)) { + setIsSideNavHovered(false); + } + }; + const header = findChild( children, ChatSideNavSubcomponentProperty.Header, @@ -59,6 +79,8 @@ export const ChatSideNav = CompoundComponent( aria-label="Side navigation" onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} + onFocus={handleFocus} + onBlur={handleBlur} {...rest} >
( - {children} -); - describe('ChatSideNavItem', () => { + beforeAll(() => { + window.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + })); + }); + test('renders with children', () => { render( - - - - Chat Name - - - , + + + Chat Name + + , ); expect(screen.getByText('Chat Name')).toBeInTheDocument(); @@ -28,15 +29,13 @@ describe('ChatSideNavItem', () => { test('renders as anchor when href is provided', () => { render( - - - - - Chat Name - - - - , + + + + Chat Name + + + , ); const link = screen.getByRole('link'); @@ -46,33 +45,27 @@ describe('ChatSideNavItem', () => { test('applies expected aria-current value when active', () => { render( - - - - - Active Chat - - - - , + + + Active Chat + + , ); - const item = screen.getByText('Active Chat'); + const item = screen.getByText('Active Chat').parentElement; expect(item).toHaveAttribute('aria-current', AriaCurrentValue.Page); }); test('applies expected aria-current value when inactive', () => { render( - - - - Inactive Chat - - - , + + + Inactive Chat + + , ); - const item = screen.getByText('Inactive Chat'); + const item = screen.getByText('Inactive Chat').parentElement; expect(item).toHaveAttribute('aria-current', AriaCurrentValue.Unset); }); @@ -80,37 +73,33 @@ describe('ChatSideNavItem', () => { const onClick = jest.fn(); render( - - - - - Clickable Chat - - - - , + + + + Clickable Chat + + + , ); - const item = screen.getByText('Clickable Chat'); - await userEvent.click(item); + const item = screen.getByText('Clickable Chat').parentElement; + await userEvent.click(item!); expect(onClick).toHaveBeenCalledTimes(1); }); test('applies className prop', () => { render( - - - - - Custom Chat - - - - , + + + + Custom Chat + + + , ); - const item = screen.getByText('Custom Chat'); + const item = screen.getByText('Custom Chat').parentElement; expect(item).toHaveClass('custom-class'); }); }); diff --git a/chat/chat-layout/src/ChatSideNav/ChatSideNavItem/ChatSideNavItem.styles.ts b/chat/chat-layout/src/ChatSideNav/ChatSideNavItem/ChatSideNavItem.styles.ts index eb6c66e69d..1710cce0f5 100644 --- a/chat/chat-layout/src/ChatSideNav/ChatSideNavItem/ChatSideNavItem.styles.ts +++ b/chat/chat-layout/src/ChatSideNav/ChatSideNavItem/ChatSideNavItem.styles.ts @@ -125,3 +125,11 @@ export const getItemStyles = ({ }, className, ); + +export const textOverflowStyles = css` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; + flex: 1; +`; diff --git a/chat/chat-layout/src/ChatSideNav/ChatSideNavItem/ChatSideNavItem.tsx b/chat/chat-layout/src/ChatSideNav/ChatSideNavItem/ChatSideNavItem.tsx index d3b6facde4..035658df4e 100644 --- a/chat/chat-layout/src/ChatSideNav/ChatSideNavItem/ChatSideNavItem.tsx +++ b/chat/chat-layout/src/ChatSideNav/ChatSideNavItem/ChatSideNavItem.tsx @@ -1,17 +1,24 @@ -import React, { ComponentType } from 'react'; +import React, { ComponentType, useEffect, useRef, useState } from 'react'; import { CompoundSubComponent } from '@leafygreen-ui/compound-component'; import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; -import { AriaCurrentValue } from '@leafygreen-ui/lib'; +import { AriaCurrentValue, getNodeTextContent } from '@leafygreen-ui/lib'; import { InferredPolymorphic, useInferredPolymorphic, } from '@leafygreen-ui/polymorphic'; +import { spacing as spacingToken } from '@leafygreen-ui/tokens'; +import { + Align, + Justify, + Tooltip, + TooltipVariant, +} from '@leafygreen-ui/tooltip'; import { useChatLayoutContext } from '../../ChatLayout'; import { ChatSideNavSubcomponentProperty } from '../ChatSideNav.types'; -import { getItemStyles } from './ChatSideNavItem.styles'; +import { getItemStyles, textOverflowStyles } from './ChatSideNavItem.styles'; import type { BaseChatSideNavItemProps, ChatSideNavItemProps, @@ -27,21 +34,70 @@ export const ChatSideNavItem = CompoundSubComponent( const { theme } = useDarkMode(); const { shouldRenderExpanded } = useChatLayoutContext(); + const textRef = useRef(null); + const [isTruncated, setIsTruncated] = useState(false); + + const tooltipTextContent = getNodeTextContent(children); + + useEffect(() => { + const checkTruncation = () => { + if (!textRef.current) { + return; + } + + const shouldTruncate = + textRef.current.scrollWidth > textRef.current.clientWidth; + + if (isTruncated === shouldTruncate) { + return; + } + + setIsTruncated(shouldTruncate); + }; + + checkTruncation(); + + const resizeObserver = new ResizeObserver(checkTruncation); + + if (textRef.current) { + resizeObserver.observe(textRef.current); + } + + return () => { + resizeObserver.disconnect(); + }; + }, [children, isTruncated]); + return ( - + + {children} + + + } > - {children} - + {tooltipTextContent} + ); }, ) as ComponentType, diff --git a/chat/chat-layout/tsconfig.json b/chat/chat-layout/tsconfig.json index 867dc2e293..317a825961 100644 --- a/chat/chat-layout/tsconfig.json +++ b/chat/chat-layout/tsconfig.json @@ -24,6 +24,9 @@ { "path": "../../packages/emotion" }, + { + "path": "../../packages/hooks" + }, { "path": "../../packages/icon" }, @@ -42,6 +45,9 @@ { "path": "../../packages/tokens" }, + { + "path": "../../packages/tooltip" + }, { "path": "../../packages/typography" }, diff --git a/chat/chat-window/package.json b/chat/chat-window/package.json index e8ef517827..729a98fbcb 100644 --- a/chat/chat-window/package.json +++ b/chat/chat-window/package.json @@ -16,19 +16,24 @@ "dependencies": { "@leafygreen-ui/emotion": "workspace:^", "@leafygreen-ui/lib": "workspace:^", - "@leafygreen-ui/palette": "workspace:^", "@leafygreen-ui/tokens": "workspace:^", "@lg-chat/title-bar": "workspace:^", "react-keyed-flatten-children": "^2.2.1" }, "devDependencies": { + "@floating-ui/react": "^0.26.20", "@leafygreen-ui/drawer": "workspace:^", + "@leafygreen-ui/palette": "workspace:^", "@lg-chat/input-bar": "workspace:^", "@lg-chat/message": "workspace:^", "@lg-chat/message-feed": "workspace:^", - "@lg-chat/message-feedback": "workspace:^", "@lg-chat/message-prompts": "workspace:^", - "@lg-tools/build": "workspace:^" + "@lg-tools/build": "workspace:^", + "clipboard": "^2.0.11", + "facepaint": "^1.2.1", + "highlight.js": "^11.10.0", + "highlightjs-graphql": "^1.0.2", + "polished": "^4.2.2" }, "peerDependencies": { "@leafygreen-ui/leafygreen-provider": "workspace:^5.0.0 || ^4.0.0 || ^3.2.0", diff --git a/chat/input-bar/CHANGELOG.md b/chat/input-bar/CHANGELOG.md index f386505cfb..270fd97547 100644 --- a/chat/input-bar/CHANGELOG.md +++ b/chat/input-bar/CHANGELOG.md @@ -1,5 +1,29 @@ # @lg-chat/input-bar +## 12.1.0 + +### Minor Changes + +- 384f19b: [LG-5736](https://jira.mongodb.org/browse/LG-5736): update disclaimer text + +## 12.0.1 + +### Patch Changes + +- 5845283: Update tsdocs for `errorMessage` and `state` props +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/avatar@3.1.6 + - @leafygreen-ui/banner@10.2.4 + - @leafygreen-ui/icon-button@17.1.4 + - @leafygreen-ui/search-input@6.1.2 + - @leafygreen-ui/typography@22.2.3 + - @leafygreen-ui/input-option@4.1.4 + ## 12.0.0 ### Major Changes diff --git a/chat/input-bar/package.json b/chat/input-bar/package.json index 25226480ab..47aaa77e5e 100644 --- a/chat/input-bar/package.json +++ b/chat/input-bar/package.json @@ -1,6 +1,6 @@ { "name": "@lg-chat/input-bar", - "version": "12.0.0", + "version": "12.1.0", "description": "lg-chat Input Bar", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -38,6 +38,7 @@ "@lg-chat/leafygreen-chat-provider": "workspace:^6.0.0" }, "devDependencies": { + "@leafygreen-ui/badge": "workspace:^", "@lg-tools/build": "workspace:^" }, "exports": { diff --git a/chat/input-bar/src/DisclaimerText/DisclaimerText.tsx b/chat/input-bar/src/DisclaimerText/DisclaimerText.tsx index acd84aead9..3ce0e39cf0 100644 --- a/chat/input-bar/src/DisclaimerText/DisclaimerText.tsx +++ b/chat/input-bar/src/DisclaimerText/DisclaimerText.tsx @@ -5,13 +5,13 @@ import { Disclaimer, Link } from '@leafygreen-ui/typography'; import { linkStyles } from './DisclaimerText.styles'; import { type DisclaimerTextProps } from './DisclaimerText.types'; -const DISCLAIMER_TEXT = 'AI can make mistakes, so review for accuracy.'; -const LEARN_MORE_TEXT = 'Learn more'; +const DISCLAIMER_TEXT = 'Review answers for accuracy. See our '; +const LEARN_MORE_TEXT = 'AI and data usage FAQ'; const LEARN_MORE_URL = 'https://www.mongodb.com/docs/generative-ai-faq/'; export const DisclaimerText = ({ className, ...rest }: DisclaimerTextProps) => ( - {DISCLAIMER_TEXT}{' '} + {DISCLAIMER_TEXT} {LEARN_MORE_TEXT} diff --git a/chat/input-bar/src/shared.types.ts b/chat/input-bar/src/shared.types.ts index 710a0f824f..e11b104a85 100644 --- a/chat/input-bar/src/shared.types.ts +++ b/chat/input-bar/src/shared.types.ts @@ -10,13 +10,11 @@ export type State = (typeof State)[keyof typeof State]; export interface SharedInputBarProps { /** * Custom error message to display when `state='error'` - * @remarks This prop is only considered when the parent `LeafyGreenChatProvider` has `variant="compact"`. */ errorMessage?: ReactNode; /** * The current state of the InputBar. This can be `'unset'`, `'error'`, or `'loading'` - * @remarks This prop is only considered when the parent `LeafyGreenChatProvider` has `variant="compact"`. */ state?: State; } diff --git a/chat/leafygreen-chat-provider/package.json b/chat/leafygreen-chat-provider/package.json index 56d3e4bd42..919f2ae694 100644 --- a/chat/leafygreen-chat-provider/package.json +++ b/chat/leafygreen-chat-provider/package.json @@ -14,7 +14,8 @@ "access": "public" }, "devDependencies": { - "@lg-tools/build": "workspace:^" + "@lg-tools/build": "workspace:^", + "use-resize-observer": "^9.1.0" }, "exports": { ".": { diff --git a/chat/lg-markdown/CHANGELOG.md b/chat/lg-markdown/CHANGELOG.md index 9be2e0b5f8..007549c4c0 100644 --- a/chat/lg-markdown/CHANGELOG.md +++ b/chat/lg-markdown/CHANGELOG.md @@ -1,5 +1,24 @@ # @lg-chat/lg-markdown +## 5.0.2 + +### Patch Changes + +- 19c0cbe: Use latest version of `@leafygreen-ui/code` for compact tooltip UI +- Updated dependencies [19c0cbe] + - @leafygreen-ui/code@20.2.5 + +## 5.0.1 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/code@20.2.4 + - @leafygreen-ui/typography@22.2.3 + ## 5.0.0 ### Major Changes diff --git a/chat/lg-markdown/package.json b/chat/lg-markdown/package.json index cae6a7faa2..4bd012b8f3 100644 --- a/chat/lg-markdown/package.json +++ b/chat/lg-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@lg-chat/lg-markdown", - "version": "5.0.0", + "version": "5.0.2", "description": "lg-chat LGMarkdown", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/chat/message-feed/CHANGELOG.md b/chat/message-feed/CHANGELOG.md index 8042b92288..911735f412 100644 --- a/chat/message-feed/CHANGELOG.md +++ b/chat/message-feed/CHANGELOG.md @@ -1,5 +1,19 @@ # @lg-chat/message-feed +## 9.0.1 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [d21ec41] +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @lg-chat/message-rating@7.1.0 + - @lg-chat/message@10.1.0 + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + ## 9.0.0 ### Major Changes diff --git a/chat/message-feed/package.json b/chat/message-feed/package.json index f847e4db31..2a1ab0d965 100644 --- a/chat/message-feed/package.json +++ b/chat/message-feed/package.json @@ -1,6 +1,6 @@ { "name": "@lg-chat/message-feed", - "version": "9.0.0", + "version": "9.0.1", "description": "lg-chat Message Feed", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -18,16 +18,22 @@ "@leafygreen-ui/emotion": "workspace:^", "@leafygreen-ui/icon": "workspace:^", "@leafygreen-ui/lib": "workspace:^", - "@leafygreen-ui/palette": "workspace:^", "@leafygreen-ui/tokens": "workspace:^", - "@lg-chat/message": "workspace:^", - "@lg-chat/message-rating": "workspace:^", "react-intersection-observer": "^8.25.1", "react-keyed-flatten-children": "^2.2.1" }, "devDependencies": { + "@floating-ui/react": "^0.26.20", + "@leafygreen-ui/palette": "workspace:^", + "@lg-chat/message": "workspace:^", "@lg-chat/message-prompts": "workspace:^", - "@lg-tools/build": "workspace:^" + "@lg-chat/message-rating": "workspace:^", + "@lg-tools/build": "workspace:^", + "clipboard": "^2.0.11", + "facepaint": "^1.2.1", + "highlight.js": "^11.10.0", + "highlightjs-graphql": "^1.0.2", + "polished": "^4.2.2" }, "peerDependencies": { "@leafygreen-ui/leafygreen-provider": "workspace:^5.0.0 || ^4.0.0 || ^3.2.0", diff --git a/chat/message-feedback/CHANGELOG.md b/chat/message-feedback/CHANGELOG.md index 9dcf8c2523..6a713579cb 100644 --- a/chat/message-feedback/CHANGELOG.md +++ b/chat/message-feedback/CHANGELOG.md @@ -1,5 +1,19 @@ # @lg-chat/message-feedback +## 9.0.1 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/icon-button@17.1.4 + - @leafygreen-ui/text-area@12.1.4 + - @leafygreen-ui/typography@22.2.3 + ## 9.0.0 ### Major Changes diff --git a/chat/message-feedback/package.json b/chat/message-feedback/package.json index 361f10efac..e90fe9f70e 100644 --- a/chat/message-feedback/package.json +++ b/chat/message-feedback/package.json @@ -1,6 +1,6 @@ { "name": "@lg-chat/message-feedback", - "version": "9.0.0", + "version": "9.0.1", "description": "LeafyGreen UI Kit Message Feedback", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -34,12 +34,13 @@ "@leafygreen-ui/icon": "workspace:^", "@leafygreen-ui/icon-button": "workspace:^", "@leafygreen-ui/lib": "workspace:^", - "@leafygreen-ui/palette": "workspace:^", "@leafygreen-ui/text-area": "workspace:^", "@leafygreen-ui/tokens": "workspace:^", "@leafygreen-ui/typography": "workspace:^" }, "devDependencies": { + "@leafygreen-ui/palette": "workspace:^", + "@leafygreen-ui/popover": "workspace:^", "@lg-chat/message-rating": "workspace:^", "@lg-tools/build": "workspace:^" }, diff --git a/chat/message-prompts/CHANGELOG.md b/chat/message-prompts/CHANGELOG.md index 8340785a85..60428fb4fb 100644 --- a/chat/message-prompts/CHANGELOG.md +++ b/chat/message-prompts/CHANGELOG.md @@ -1,5 +1,24 @@ # @lg-chat/message-prompts +## 4.2.0 + +### Minor Changes + +- d21ec41: Refactor `IconButton` instances to use compact tooltip UI + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [f7a63e2] +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/tooltip@14.3.0 + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/icon-button@17.1.4 + - @leafygreen-ui/typography@22.2.3 + ## 4.1.4 ### Patch Changes diff --git a/chat/message-prompts/package.json b/chat/message-prompts/package.json index 3114d48305..bba6cfaf36 100644 --- a/chat/message-prompts/package.json +++ b/chat/message-prompts/package.json @@ -1,6 +1,6 @@ { "name": "@lg-chat/message-prompts", - "version": "4.1.4", + "version": "4.2.0", "description": "LeafyGreen UI Kit Message Prompts", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -21,6 +21,7 @@ "@leafygreen-ui/lib": "workspace:^", "@leafygreen-ui/palette": "workspace:^", "@leafygreen-ui/tokens": "workspace:^", + "@leafygreen-ui/tooltip": "workspace:^", "@leafygreen-ui/typography": "workspace:^" }, "peerDependencies": { diff --git a/chat/message-prompts/src/MessagePrompts/MessagePrompts.tsx b/chat/message-prompts/src/MessagePrompts/MessagePrompts.tsx index 9eb5ac7e43..ac08ceae4e 100644 --- a/chat/message-prompts/src/MessagePrompts/MessagePrompts.tsx +++ b/chat/message-prompts/src/MessagePrompts/MessagePrompts.tsx @@ -4,6 +4,13 @@ import RefreshIcon from '@leafygreen-ui/icon/dist/Refresh'; import { IconButton } from '@leafygreen-ui/icon-button'; import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; import { isComponentType } from '@leafygreen-ui/lib'; +import { spacing as spacingToken } from '@leafygreen-ui/tokens'; +import { + Align, + Justify, + Tooltip, + TooltipVariant, +} from '@leafygreen-ui/tooltip'; import { Body } from '@leafygreen-ui/typography'; import { MessagePromptsProvider } from '../MessagePromptsContext'; @@ -54,15 +61,24 @@ export const MessagePrompts = forwardRef( {label} )} {onClickRefresh && ( - + + + } + variant={TooltipVariant.Compact} > - - + Refresh prompts + )}
)} diff --git a/chat/message-prompts/tsconfig.json b/chat/message-prompts/tsconfig.json index 25e588a1b5..daaecfd88d 100644 --- a/chat/message-prompts/tsconfig.json +++ b/chat/message-prompts/tsconfig.json @@ -35,6 +35,9 @@ { "path": "../../packages/tokens" }, + { + "path": "../../packages/tooltip" + }, { "path": "../../packages/typography" }, diff --git a/chat/message-rating/CHANGELOG.md b/chat/message-rating/CHANGELOG.md index 7ef0f02593..c74f027888 100644 --- a/chat/message-rating/CHANGELOG.md +++ b/chat/message-rating/CHANGELOG.md @@ -1,5 +1,23 @@ # @lg-chat/message-rating +## 7.1.0 + +### Minor Changes + +- d21ec41: Refactor `IconButton` instances to use compact tooltip UI + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [f7a63e2] +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/tooltip@14.3.0 + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/icon-button@17.1.4 + ## 7.0.0 ### Major Changes diff --git a/chat/message-rating/package.json b/chat/message-rating/package.json index 7f4a2ca29d..6436dd7050 100644 --- a/chat/message-rating/package.json +++ b/chat/message-rating/package.json @@ -1,6 +1,6 @@ { "name": "@lg-chat/message-rating", - "version": "7.0.0", + "version": "7.1.0", "description": "lg-chat Message Rating", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -19,7 +19,6 @@ "@leafygreen-ui/icon": "workspace:^", "@leafygreen-ui/icon-button": "workspace:^", "@leafygreen-ui/lib": "workspace:^", - "@leafygreen-ui/palette": "workspace:^", "@leafygreen-ui/tokens": "workspace:^" }, "peerDependencies": { @@ -27,6 +26,8 @@ "@lg-chat/leafygreen-chat-provider": "workspace:^6.0.0" }, "devDependencies": { + "@leafygreen-ui/palette": "workspace:^", + "@leafygreen-ui/typography": "workspace:^", "@lg-tools/build": "workspace:^" }, "exports": { diff --git a/chat/message-rating/src/MessageRating/MessageRating.tsx b/chat/message-rating/src/MessageRating/MessageRating.tsx index adde3e7581..7049fd4d01 100644 --- a/chat/message-rating/src/MessageRating/MessageRating.tsx +++ b/chat/message-rating/src/MessageRating/MessageRating.tsx @@ -7,6 +7,8 @@ import { IconButton } from '@leafygreen-ui/icon-button'; import LeafyGreenProvider, { useDarkMode, } from '@leafygreen-ui/leafygreen-provider'; +import { spacing as spacingToken } from '@leafygreen-ui/tokens'; +import { Justify, Tooltip, TooltipVariant } from '@leafygreen-ui/tooltip'; import { buttonContainerStyles, @@ -80,40 +82,56 @@ export const MessageRating = forwardRef( className={buttonContainerStyles} role="radiogroup" > - + + + } + variant={TooltipVariant.Compact} > - - - + + + + } + variant={TooltipVariant.Compact} > - - + Not helpful +
diff --git a/chat/message-rating/tsconfig.json b/chat/message-rating/tsconfig.json index 77a93ae932..f63a82be97 100644 --- a/chat/message-rating/tsconfig.json +++ b/chat/message-rating/tsconfig.json @@ -41,6 +41,9 @@ { "path": "../../packages/tokens" }, + { + "path": "../../packages/tooltip" + }, { "path": "../../packages/leafygreen-provider" } diff --git a/chat/message/CHANGELOG.md b/chat/message/CHANGELOG.md index 43931b96b2..9597d59c1c 100644 --- a/chat/message/CHANGELOG.md +++ b/chat/message/CHANGELOG.md @@ -1,5 +1,39 @@ # @lg-chat/message +## 10.1.1 + +### Patch Changes + +- 19c0cbe: Use latest version of `@leafygreen-ui/code` for compact tooltip UI +- Updated dependencies [19c0cbe] + - @lg-chat/lg-markdown@5.0.2 + +## 10.1.0 + +### Minor Changes + +- d21ec41: Refactor `IconButton` instances to use compact tooltip UI + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [f7a63e2] +- Updated dependencies [d21ec41] +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/tooltip@14.3.0 + - @lg-chat/message-rating@7.1.0 + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/avatar@3.1.6 + - @leafygreen-ui/banner@10.2.4 + - @leafygreen-ui/icon-button@17.1.4 + - @leafygreen-ui/typography@22.2.3 + - @lg-chat/message-feedback@9.0.1 + - @lg-chat/rich-links@4.0.6 + - @lg-chat/lg-markdown@5.0.1 + ## 10.0.0 ### Major Changes diff --git a/chat/message/package.json b/chat/message/package.json index 499e629f60..656e3db58d 100644 --- a/chat/message/package.json +++ b/chat/message/package.json @@ -1,6 +1,6 @@ { "name": "@lg-chat/message", - "version": "10.0.0", + "version": "10.1.1", "description": "lg-chat Message", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -23,8 +23,8 @@ "@leafygreen-ui/icon-button": "workspace:^", "@leafygreen-ui/lib": "workspace:^", "@leafygreen-ui/palette": "workspace:^", - "@leafygreen-ui/polymorphic": "workspace:^", "@leafygreen-ui/tokens": "workspace:^", + "@leafygreen-ui/tooltip": "workspace:^", "@leafygreen-ui/typography": "workspace:^", "@lg-chat/lg-markdown": "workspace:^", "@lg-chat/message-feedback": "workspace:^", @@ -33,6 +33,7 @@ }, "devDependencies": { "@emotion/styled": "^11.10.5", + "@leafygreen-ui/polymorphic": "workspace:^", "@lg-tools/build": "workspace:^" }, "peerDependencies": { diff --git a/chat/message/src/MessageActions/MessageActions.spec.tsx b/chat/message/src/MessageActions/MessageActions.spec.tsx index 6755c0a7ee..46dfeb6474 100644 --- a/chat/message/src/MessageActions/MessageActions.spec.tsx +++ b/chat/message/src/MessageActions/MessageActions.spec.tsx @@ -64,7 +64,7 @@ describe('packages/message-actions', () => { }); describe('copy button', () => { - test('renders copy button in compact mode by default', () => { + test('renders copy button', () => { renderMessageActions(); expect( diff --git a/chat/message/src/MessageActions/MessageActions.stories.tsx b/chat/message/src/MessageActions/MessageActions.stories.tsx index cedb5a0e83..7f799ab1c4 100644 --- a/chat/message/src/MessageActions/MessageActions.stories.tsx +++ b/chat/message/src/MessageActions/MessageActions.stories.tsx @@ -74,6 +74,88 @@ const Template: StoryFn = props => ( ); +/** Helper function to hover over a button and verify tooltip appears */ +const hoverAndVerifyTooltip = async ( + canvas: ReturnType, + options: { + buttonRole: 'button' | 'radio'; + buttonName: string; + tooltipName: string; + }, +) => { + const { buttonRole, buttonName, tooltipName } = options; + const button = canvas.getByRole(buttonRole, { name: buttonName }); + await userEvent.hover(button); + const tooltip = await canvas.findByRole('tooltip', { name: tooltipName }); + expect(tooltip).toBeInTheDocument(); +}; + +/** Helper function to click a button and unhover it */ +const clickAndUnhover = async ( + canvas: ReturnType, + options: { + buttonRole: 'button' | 'radio'; + buttonName: string; + }, +) => { + const { buttonRole, buttonName } = options; + const button = canvas.getByRole(buttonRole, { name: buttonName }); + await userEvent.click(button); + await userEvent.unhover(button); + return button; +}; + +/** Helper to get feedback form elements */ +const getFeedbackFormElements = (canvas: ReturnType) => { + const textarea = canvas.getByTestId(FEEDBACK_TEXTAREA_TEST_ID); + const submitButton = canvas.getByRole('button', { name: 'Submit' }); + return { textarea, submitButton }; +}; + +/** Helper to verify feedback form is visible */ +const verifyFeedbackFormVisible = (canvas: ReturnType) => { + const { textarea, submitButton } = getFeedbackFormElements(canvas); + expect(textarea).toBeInTheDocument(); + expect(submitButton).toBeInTheDocument(); +}; + +/** Helper to verify feedback form is hidden */ +const verifyFeedbackFormHidden = (canvas: ReturnType) => { + expect(canvas.queryByTestId(FEEDBACK_TEXTAREA_TEST_ID)).toBeNull(); + expect(canvas.queryByRole('button', { name: 'Submit' })).toBeNull(); +}; + +/** Helper to type feedback text */ +const typeFeedback = async ( + canvas: ReturnType, + text: string, +) => { + const { textarea } = getFeedbackFormElements(canvas); + await userEvent.type(textarea, text); + expect(textarea).toHaveValue(text); +}; + +/** Helper to submit feedback */ +const submitFeedback = async (canvas: ReturnType) => { + const { submitButton } = getFeedbackFormElements(canvas); + await userEvent.click(submitButton); +}; + +/** Helper to verify success message */ +const verifySuccessMessage = (canvas: ReturnType) => { + const successMessage = canvas.getByText('Thanks for your feedback!'); + expect(successMessage).toBeInTheDocument(); +}; + +/** Helper to verify error message */ +const verifyErrorMessage = ( + canvas: ReturnType, + errorMessage: string, +) => { + const error = canvas.getByText(errorMessage); + expect(error).toBeInTheDocument(); +}; + export const LiveExample: StoryObj = { render: Template, parameters: { @@ -83,6 +165,125 @@ export const LiveExample: StoryObj = { }, }; +export const LightModeWithCopyHover: StoryObj = { + render: Template, + args: { + onClickCopy: testOnClickCopy, + }, + play: async ({ canvasElement }) => { + await hoverAndVerifyTooltip(within(canvasElement), { + buttonRole: 'button', + buttonName: 'Copy message', + tooltipName: 'Copy', + }); + }, + parameters: { + chromatic: { + delay: 500, + }, + }, +}; + +export const DarkModeWithCopyHover: StoryObj = { + render: Template, + args: { + darkMode: true, + onClickCopy: testOnClickCopy, + }, + play: async ({ canvasElement }) => { + await hoverAndVerifyTooltip(within(canvasElement), { + buttonRole: 'button', + buttonName: 'Copy message', + tooltipName: 'Copy', + }); + }, + parameters: { + chromatic: { + delay: 500, + }, + }, +}; + +export const LightModeWithRetryHover: StoryObj = { + render: Template, + args: { + onClickRetry: testOnClickRetry, + }, + play: async ({ canvasElement }) => { + await hoverAndVerifyTooltip(within(canvasElement), { + buttonRole: 'button', + buttonName: 'Retry message', + tooltipName: 'Retry', + }); + }, + parameters: { + chromatic: { + delay: 500, + }, + }, +}; + +export const DarkModeWithRetryHover: StoryObj = { + render: Template, + args: { + darkMode: true, + onClickRetry: testOnClickRetry, + }, + play: async ({ canvasElement }) => { + await hoverAndVerifyTooltip(within(canvasElement), { + buttonRole: 'button', + buttonName: 'Retry message', + tooltipName: 'Retry', + }); + }, + parameters: { + chromatic: { + delay: 500, + }, + }, +}; + +export const LightModeWithRatingHover: StoryObj = { + render: Template, + args: { + onRatingChange: testOnRatingChange, + onSubmitFeedback: testOnSubmitFeedback, + }, + play: async ({ canvasElement }) => { + await hoverAndVerifyTooltip(within(canvasElement), { + buttonRole: 'radio', + buttonName: 'Like this message', + tooltipName: 'Helpful', + }); + }, + parameters: { + chromatic: { + delay: 500, + }, + }, +}; + +export const DarkModeWithRatingHover: StoryObj = { + render: Template, + args: { + darkMode: true, + onRatingChange: testOnRatingChange, + onSubmitFeedback: testOnSubmitFeedback, + }, + play: async ({ canvasElement }) => { + await hoverAndVerifyTooltip(within(canvasElement), { + buttonRole: 'radio', + buttonName: 'Like this message', + tooltipName: 'Helpful', + }); + }, + parameters: { + chromatic: { + delay: 500, + }, + }, +}; + export const LightModeWithRatingSelect: StoryObj = { render: Template, args: { @@ -92,18 +293,12 @@ export const LightModeWithRatingSelect: StoryObj = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - // Click thumbs up button - const thumbsUpButton = canvas.getByRole('radio', { - name: 'Like this message', + await clickAndUnhover(canvas, { + buttonRole: 'radio', + buttonName: 'Like this message', }); - await userEvent.click(thumbsUpButton); - - // Verify feedback form is visible - const textarea = canvas.getByTestId(FEEDBACK_TEXTAREA_TEST_ID); - expect(textarea).toBeInTheDocument(); - const submitButton = canvas.getByRole('button', { name: 'Submit' }); - expect(submitButton).toBeInTheDocument(); + verifyFeedbackFormVisible(canvas); }, parameters: { chromatic: { @@ -122,18 +317,12 @@ export const DarkModeWithRatingSelect: StoryObj = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - // Click thumbs up button - const thumbsUpButton = canvas.getByRole('radio', { - name: 'Like this message', + await clickAndUnhover(canvas, { + buttonRole: 'radio', + buttonName: 'Like this message', }); - await userEvent.click(thumbsUpButton); - // Verify feedback form is visible - const textarea = canvas.getByTestId(FEEDBACK_TEXTAREA_TEST_ID); - expect(textarea).toBeInTheDocument(); - - const submitButton = canvas.getByRole('button', { name: 'Submit' }); - expect(submitButton).toBeInTheDocument(); + verifyFeedbackFormVisible(canvas); }, parameters: { chromatic: { @@ -152,20 +341,14 @@ export const LightModeWithRatingSelectAndFeedback: StoryObj play: async ({ canvasElement }) => { const canvas = within(canvasElement); - // Click thumbs up button - const thumbsUpButton = canvas.getByRole('radio', { - name: 'Like this message', + await clickAndUnhover(canvas, { + buttonRole: 'radio', + buttonName: 'Like this message', }); - await userEvent.click(thumbsUpButton); - - // Type in feedback textarea - const textarea = canvas.getByTestId(FEEDBACK_TEXTAREA_TEST_ID); - await userEvent.type(textarea, 'Lorem ipsum'); - // Verify text was entered - expect(textarea).toHaveValue('Lorem ipsum'); + await typeFeedback(canvas, 'Lorem ipsum'); - const submitButton = canvas.getByRole('button', { name: 'Submit' }); + const { submitButton } = getFeedbackFormElements(canvas); expect(submitButton).toBeInTheDocument(); }, parameters: { @@ -186,20 +369,14 @@ export const DarkModeWithRatingSelectAndFeedback: StoryObj play: async ({ canvasElement }) => { const canvas = within(canvasElement); - // Click thumbs up button - const thumbsUpButton = canvas.getByRole('radio', { - name: 'Like this message', + await clickAndUnhover(canvas, { + buttonRole: 'radio', + buttonName: 'Like this message', }); - await userEvent.click(thumbsUpButton); - - // Type in feedback textarea - const textarea = canvas.getByTestId(FEEDBACK_TEXTAREA_TEST_ID); - await userEvent.type(textarea, 'Lorem ipsum'); - // Verify text was entered - expect(textarea).toHaveValue('Lorem ipsum'); + await typeFeedback(canvas, 'Lorem ipsum'); - const submitButton = canvas.getByRole('button', { name: 'Submit' }); + const { submitButton } = getFeedbackFormElements(canvas); expect(submitButton).toBeInTheDocument(); }, parameters: { @@ -219,27 +396,18 @@ export const LightModeWithRatingSelectAndFeedbackAndSubmitSuccess: StoryObj { const canvas = within(canvasElement); - // Click thumbs up button - const thumbsUpButton = canvas.getByRole('radio', { - name: 'Like this message', + await clickAndUnhover(canvas, { + buttonRole: 'radio', + buttonName: 'Like this message', }); - await userEvent.click(thumbsUpButton); - // Type in feedback textarea - const textarea = canvas.getByTestId(FEEDBACK_TEXTAREA_TEST_ID); - await userEvent.type(textarea, 'Lorem ipsum'); + await typeFeedback(canvas, 'Lorem ipsum'); - // Submit feedback - const submitButton = canvas.getByRole('button', { name: 'Submit' }); - await userEvent.click(submitButton); + await submitFeedback(canvas); - // Verify success message is shown - const successMessage = canvas.getByText('Thanks for your feedback!'); - expect(successMessage).toBeInTheDocument(); + verifySuccessMessage(canvas); - // Verify feedback form is no longer interactive - expect(canvas.queryByTestId(FEEDBACK_TEXTAREA_TEST_ID)).toBeNull(); - expect(canvas.queryByRole('button', { name: 'Submit' })).toBeNull(); + verifyFeedbackFormHidden(canvas); }, parameters: { chromatic: { @@ -259,27 +427,18 @@ export const DarkModeWithRatingSelectAndFeedbackAndSubmitSuccess: StoryObj { const canvas = within(canvasElement); - // Click thumbs up button - const thumbsUpButton = canvas.getByRole('radio', { - name: 'Like this message', + await clickAndUnhover(canvas, { + buttonRole: 'radio', + buttonName: 'Like this message', }); - await userEvent.click(thumbsUpButton); - // Type in feedback textarea - const textarea = canvas.getByTestId(FEEDBACK_TEXTAREA_TEST_ID); - await userEvent.type(textarea, 'Lorem ipsum'); + await typeFeedback(canvas, 'Lorem ipsum'); - // Submit feedback - const submitButton = canvas.getByRole('button', { name: 'Submit' }); - await userEvent.click(submitButton); + await submitFeedback(canvas); - // Verify success message is shown - const successMessage = canvas.getByText('Thanks for your feedback!'); - expect(successMessage).toBeInTheDocument(); + verifySuccessMessage(canvas); - // Verify feedback form is no longer interactive - expect(canvas.queryByTestId(FEEDBACK_TEXTAREA_TEST_ID)).toBeNull(); - expect(canvas.queryByRole('button', { name: 'Submit' })).toBeNull(); + verifyFeedbackFormHidden(canvas); }, parameters: { chromatic: { @@ -298,27 +457,18 @@ export const LightModeWithRatingSelectAndFeedbackAndSubmitSuccessAndFade: StoryO play: async ({ canvasElement }) => { const canvas = within(canvasElement); - // Click thumbs up button - const thumbsUpButton = canvas.getByRole('radio', { - name: 'Like this message', + await clickAndUnhover(canvas, { + buttonRole: 'radio', + buttonName: 'Like this message', }); - await userEvent.click(thumbsUpButton); - // Type in feedback textarea - const textarea = canvas.getByTestId(FEEDBACK_TEXTAREA_TEST_ID); - await userEvent.type(textarea, 'Lorem ipsum'); + await typeFeedback(canvas, 'Lorem ipsum'); - // Submit feedback - const submitButton = canvas.getByRole('button', { name: 'Submit' }); - await userEvent.click(submitButton); + await submitFeedback(canvas); - // Verify success message is shown - const successMessage = canvas.getByText('Thanks for your feedback!'); - expect(successMessage).toBeInTheDocument(); + verifySuccessMessage(canvas); - // Verify feedback form is no longer interactive - expect(canvas.queryByTestId(FEEDBACK_TEXTAREA_TEST_ID)).toBeNull(); - expect(canvas.queryByRole('button', { name: 'Submit' })).toBeNull(); + verifyFeedbackFormHidden(canvas); }, parameters: { chromatic: { @@ -338,27 +488,18 @@ export const DarkModeWithRatingSelectAndFeedbackAndSubmitSuccessAndFade: StoryOb play: async ({ canvasElement }) => { const canvas = within(canvasElement); - // Click thumbs up button - const thumbsUpButton = canvas.getByRole('radio', { - name: 'Like this message', + await clickAndUnhover(canvas, { + buttonRole: 'radio', + buttonName: 'Like this message', }); - await userEvent.click(thumbsUpButton); - // Type in feedback textarea - const textarea = canvas.getByTestId(FEEDBACK_TEXTAREA_TEST_ID); - await userEvent.type(textarea, 'Lorem ipsum'); + await typeFeedback(canvas, 'Lorem ipsum'); - // Submit feedback - const submitButton = canvas.getByRole('button', { name: 'Submit' }); - await userEvent.click(submitButton); + await submitFeedback(canvas); - // Verify success message is shown - const successMessage = canvas.getByText('Thanks for your feedback!'); - expect(successMessage).toBeInTheDocument(); + verifySuccessMessage(canvas); - // Verify feedback form is no longer interactive - expect(canvas.queryByTestId(FEEDBACK_TEXTAREA_TEST_ID)).toBeNull(); - expect(canvas.queryByRole('button', { name: 'Submit' })).toBeNull(); + verifyFeedbackFormHidden(canvas); }, parameters: { chromatic: { @@ -373,7 +514,6 @@ export const LightModeWithRatingSelectAndFeedbackSubmitError: StoryObj { - // Simulate a failed submission by throwing an error throw new Error('Network error'); }, errorMessage: 'Failed to submit feedback. Please try again.', @@ -381,31 +521,21 @@ export const LightModeWithRatingSelectAndFeedbackSubmitError: StoryObj { const canvas = within(canvasElement); - // Click thumbs up button - const thumbsUpButton = canvas.getByRole('radio', { - name: 'Like this message', + await clickAndUnhover(canvas, { + buttonRole: 'radio', + buttonName: 'Like this message', }); - await userEvent.click(thumbsUpButton); - // Type in feedback textarea - const textarea = canvas.getByTestId(FEEDBACK_TEXTAREA_TEST_ID); - await userEvent.type(textarea, 'Lorem ipsum'); + await typeFeedback(canvas, 'Lorem ipsum'); - // Submit feedback (this will fail) - const submitButton = canvas.getByRole('button', { name: 'Submit' }); - await userEvent.click(submitButton); + await submitFeedback(canvas); - // Verify error message is shown - const errorMessage = canvas.getByText( + verifyErrorMessage( + canvas, 'Failed to submit feedback. Please try again.', ); - expect(errorMessage).toBeInTheDocument(); - // Verify feedback form is still visible and interactive - expect(canvas.getByTestId(FEEDBACK_TEXTAREA_TEST_ID)).toBeInTheDocument(); - expect( - canvas.getByRole('button', { name: 'Submit' }), - ).toBeInTheDocument(); + verifyFeedbackFormVisible(canvas); }, parameters: { chromatic: { @@ -421,7 +551,6 @@ export const DarkModeWithRatingSelectAndFeedbackSubmitError: StoryObj { - // Simulate a failed submission by throwing an error throw new Error('Network error'); }, errorMessage: 'Failed to submit feedback. Please try again.', @@ -429,31 +558,21 @@ export const DarkModeWithRatingSelectAndFeedbackSubmitError: StoryObj { const canvas = within(canvasElement); - // Click thumbs up button - const thumbsUpButton = canvas.getByRole('radio', { - name: 'Like this message', + await clickAndUnhover(canvas, { + buttonRole: 'radio', + buttonName: 'Like this message', }); - await userEvent.click(thumbsUpButton); - // Type in feedback textarea - const textarea = canvas.getByTestId(FEEDBACK_TEXTAREA_TEST_ID); - await userEvent.type(textarea, 'Lorem ipsum'); + await typeFeedback(canvas, 'Lorem ipsum'); - // Submit feedback (this will fail) - const submitButton = canvas.getByRole('button', { name: 'Submit' }); - await userEvent.click(submitButton); + await submitFeedback(canvas); - // Verify error message is shown - const errorMessage = canvas.getByText( + verifyErrorMessage( + canvas, 'Failed to submit feedback. Please try again.', ); - expect(errorMessage).toBeInTheDocument(); - // Verify feedback form is still visible and interactive - expect(canvas.getByTestId(FEEDBACK_TEXTAREA_TEST_ID)).toBeInTheDocument(); - expect( - canvas.getByRole('button', { name: 'Submit' }), - ).toBeInTheDocument(); + verifyFeedbackFormVisible(canvas); }, parameters: { chromatic: { diff --git a/chat/message/src/MessageActions/MessageActions.tsx b/chat/message/src/MessageActions/MessageActions.tsx index 1f7554c9ee..f7329acb1d 100644 --- a/chat/message/src/MessageActions/MessageActions.tsx +++ b/chat/message/src/MessageActions/MessageActions.tsx @@ -16,6 +16,8 @@ import { IconButton } from '@leafygreen-ui/icon-button'; import LeafyGreenProvider, { useDarkMode, } from '@leafygreen-ui/leafygreen-provider'; +import { spacing as spacingToken } from '@leafygreen-ui/tokens'; +import { Justify, Tooltip, TooltipVariant } from '@leafygreen-ui/tooltip'; import { useMessageContext } from '../MessageContext'; @@ -182,21 +184,31 @@ export function MessageActions({ >
- + {copied ? : } + + } + variant={TooltipVariant.Compact} > - {copied ? : } - + Copy + {onClickRetry && ( - + + + } + variant={TooltipVariant.Compact} > - - + Retry + )}
{showMessageRating && ( diff --git a/chat/message/tsconfig.json b/chat/message/tsconfig.json index 14fde2476a..1b4c6bcb87 100644 --- a/chat/message/tsconfig.json +++ b/chat/message/tsconfig.json @@ -71,6 +71,9 @@ { "path": "../../packages/tokens" }, + { + "path": "../../packages/tooltip" + }, { "path": "../../packages/typography" } diff --git a/chat/rich-links/CHANGELOG.md b/chat/rich-links/CHANGELOG.md index 3bdb281c41..65d3c0410a 100644 --- a/chat/rich-links/CHANGELOG.md +++ b/chat/rich-links/CHANGELOG.md @@ -1,5 +1,17 @@ # @lg-chat/rich-links +## 4.0.6 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/typography@22.2.3 + ## 4.0.5 ### Patch Changes diff --git a/chat/rich-links/package.json b/chat/rich-links/package.json index 684aaf7b86..9aa9baa58d 100644 --- a/chat/rich-links/package.json +++ b/chat/rich-links/package.json @@ -1,6 +1,6 @@ { "name": "@lg-chat/rich-links", - "version": "4.0.5", + "version": "4.0.6", "description": "lg-chat Rich Links", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/chat/suggestions/CHANGELOG.md b/chat/suggestions/CHANGELOG.md index 11680d0bd0..e3a1ba5195 100644 --- a/chat/suggestions/CHANGELOG.md +++ b/chat/suggestions/CHANGELOG.md @@ -1,5 +1,18 @@ # @lg-chat/suggestions +## 0.2.8 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/banner@10.2.4 + - @leafygreen-ui/typography@22.2.3 + ## 0.2.7 ### Patch Changes diff --git a/chat/suggestions/package.json b/chat/suggestions/package.json index ed8404e01c..ed6659e57b 100644 --- a/chat/suggestions/package.json +++ b/chat/suggestions/package.json @@ -1,6 +1,6 @@ { "name": "@lg-chat/suggestions", - "version": "0.2.7", + "version": "0.2.8", "description": "LeafyGreen UI Kit Suggestion Card", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/chat/title-bar/CHANGELOG.md b/chat/title-bar/CHANGELOG.md index c7c53338b9..1eadbb23f7 100644 --- a/chat/title-bar/CHANGELOG.md +++ b/chat/title-bar/CHANGELOG.md @@ -1,5 +1,15 @@ # @lg-chat/title-bar +## 5.0.1 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/typography@22.2.3 + ## 5.0.0 ### Major Changes diff --git a/chat/title-bar/package.json b/chat/title-bar/package.json index c731531e11..285e12cca4 100644 --- a/chat/title-bar/package.json +++ b/chat/title-bar/package.json @@ -1,6 +1,6 @@ { "name": "@lg-chat/title-bar", - "version": "5.0.0", + "version": "5.0.1", "description": "lg-chat Title Bar", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -25,6 +25,8 @@ "@lg-chat/leafygreen-chat-provider": "workspace:^6.0.0" }, "devDependencies": { + "@leafygreen-ui/icon": "workspace:^", + "@leafygreen-ui/icon-button": "workspace:^", "@lg-tools/build": "workspace:^" }, "exports": { diff --git a/deprecated-packages/avatar/package.json b/deprecated-packages/avatar/package.json index 328b5fce79..cf7b1ddd81 100644 --- a/deprecated-packages/avatar/package.json +++ b/deprecated-packages/avatar/package.json @@ -1,6 +1,7 @@ { "name": "@lg-chat/avatar", "version": "8.0.1", + "private": true, "description": "lg-chat Avatar", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/deprecated-packages/fixed-chat-window/package.json b/deprecated-packages/fixed-chat-window/package.json index a7697ef80f..4d62b3ab7b 100644 --- a/deprecated-packages/fixed-chat-window/package.json +++ b/deprecated-packages/fixed-chat-window/package.json @@ -1,6 +1,7 @@ { "name": "@lg-chat/fixed-chat-window", "version": "5.0.1", + "private": true, "description": "LeafyGreen UI Kit Fixed Chat Window", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -32,7 +33,12 @@ "@lg-chat/message-feed": "workspace:^", "@lg-chat/message-feedback": "workspace:^", "@lg-chat/message-prompts": "workspace:^", - "@lg-tools/build": "workspace:^" + "@lg-tools/build": "workspace:^", + "clipboard": "^2.0.11", + "facepaint": "^1.2.1", + "highlight.js": "^11.10.0", + "highlightjs-graphql": "^1.0.2", + "polished": "^4.2.2" }, "peerDependencies": { "@leafygreen-ui/leafygreen-provider": "workspace:^5.0.0 || ^4.0.0 || ^3.2.0" diff --git a/mcp-ui/list-databases/package.json b/mcp-ui/list-databases/package.json new file mode 100644 index 0000000000..8140ece94c --- /dev/null +++ b/mcp-ui/list-databases/package.json @@ -0,0 +1,34 @@ +{ + "name": "@lg-mcp-ui/list-databases", + "version": "0.1.0", + "private": true, + "main": "./dist/umd/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", + "license": "Apache-2.0", + "scripts": { + "build": "lg-build bundle", + "tsc": "lg-build tsc", + "docs": "lg-build docs", + "dev": "lg-build bundle --watch" + }, + "peerDependencies": { + "react": "^18.2.0" + }, + "dependencies": { + "@leafygreen-ui/tokens": "workspace:^", + "@leafygreen-ui/lib": "workspace:^", + "@leafygreen-ui/palette": "workspace:^" + }, + "devDependencies": { + "@lg-tools/build": "workspace:^" + }, + "exports": { + ".": { + "development": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.js", + "require": "./dist/umd/index.js" + } + } +} diff --git a/mcp-ui/list-databases/src/ListDatabases.tsx b/mcp-ui/list-databases/src/ListDatabases.tsx new file mode 100644 index 0000000000..7161bb3e44 --- /dev/null +++ b/mcp-ui/list-databases/src/ListDatabases.tsx @@ -0,0 +1,88 @@ +// 'use client'; + +import React from 'react'; + +import { palette } from '@leafygreen-ui/palette'; +import { + borderRadius, + fontFamilies, + fontWeights, + spacing, + typeScales, +} from '@leafygreen-ui/tokens'; + +import { ListDatabasesProps } from './ListDatabases.types'; + +const H3 = ({ children }: { children: React.ReactNode }) => { + return ( +

+ {children} +

+ ); +}; + +const Body = ({ children }: { children: React.ReactNode }) => { + return ( +

+ {children} +

+ ); +}; + +const Card = ({ children }: { children: React.ReactNode }) => { + return ( +
+ {children} +
+ ); +}; + +export default function ListDatabases({ databases }: ListDatabasesProps) { + if (databases.length === 0) { + return ( + +

No databases found

+
+ ); + } + + return ( + +

Databases

+
    + {databases.map(database => ( +
  • + {database} +
  • + ))} +
+
+ ); +} diff --git a/mcp-ui/list-databases/src/ListDatabases.types.ts b/mcp-ui/list-databases/src/ListDatabases.types.ts new file mode 100644 index 0000000000..b492356476 --- /dev/null +++ b/mcp-ui/list-databases/src/ListDatabases.types.ts @@ -0,0 +1,3 @@ +export interface ListDatabasesProps { + databases: Array; +} diff --git a/mcp-ui/list-databases/src/index.ts b/mcp-ui/list-databases/src/index.ts new file mode 100644 index 0000000000..75629fbd30 --- /dev/null +++ b/mcp-ui/list-databases/src/index.ts @@ -0,0 +1,2 @@ +export { default as ListDatabases } from './ListDatabases'; +export type { ListDatabasesProps } from './ListDatabases.types'; diff --git a/mcp-ui/list-databases/tsconfig.json b/mcp-ui/list-databases/tsconfig.json new file mode 100644 index 0000000000..ba0251cda9 --- /dev/null +++ b/mcp-ui/list-databases/tsconfig.json @@ -0,0 +1,31 @@ +{ + "extends": "@lg-tools/build/config/package.tsconfig.json", + "compilerOptions": { + "paths": { + "@leafygreen-ui/*": [ + "../../packages/*/src" + ] + } + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "**/*.stories.*" + ], + "references": [ + { + "path": "../../packages/card" + }, + { + "path": "../../packages/typography" + }, + { + "path": "../../packages/emotion" + }, + { + "path": "../../packages/lib" + } + ] +} + diff --git a/package.json b/package.json index fbcba81d8d..b628bc1533 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,10 @@ "init:react17": "pnpm clean; npx node ./scripts/react17/init.mjs; pnpm run init", "create-package": "lg create", "build": "turbo run build tsc", + "build:apps": "turbo run build --filter='@lg-apps/*'", "build:cli": "turbo run build tsc --filter=@lg-tools/cli", + "build:packages": "turbo run build tsc --filter=!'@lg-apps/*'", + "dev:mcp-ui": "turbo run dev --filter=@lg-apps/mcp-ui-app", "build:docs": "turbo run docs", "build:tsc": "turbo run tsc", "build:ts-downlevel": "pnpm recursive exec lg-ts-downlevel", @@ -29,7 +32,7 @@ "link": "lg link", "lint": "lg lint", "prepublishOnly": "pnpm run build && pnpm build:ts-downlevel && pnpm build:docs", - "publish": "pnpm changeset publish --public", + "publish": "pnpm publish --recursive --no-git-checks", "reset:react17": "npx node ./scripts/react17/reset.mjs; pnpm run init", "slackbot": "lg slackbot", "start": "npx storybook dev -p 9001 --no-version-updates --no-open", @@ -95,7 +98,8 @@ "@leafygreen-ui": "packages", "@lg-charts": "charts", "@lg-chat": "chat", - "@lg-tools": "tools" + "@lg-tools": "tools", + "@lg-mcp-ui": "mcp-ui" } }, "keywords": [ diff --git a/packages/a11y/package.json b/packages/a11y/package.json index cabe71ae10..0242dd69d9 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -16,8 +16,7 @@ }, "dependencies": { "@leafygreen-ui/emotion": "workspace:^", - "@leafygreen-ui/hooks": "workspace:^", - "@leafygreen-ui/lib": "workspace:^" + "@leafygreen-ui/hooks": "workspace:^" }, "homepage": "https://github.com/mongodb/leafygreen-ui/tree/main/packages/a11y", "repository": { diff --git a/packages/avatar/CHANGELOG.md b/packages/avatar/CHANGELOG.md index 66723a4af4..bafaba05f8 100644 --- a/packages/avatar/CHANGELOG.md +++ b/packages/avatar/CHANGELOG.md @@ -1,5 +1,15 @@ # @leafygreen-ui/avatar +## 3.1.6 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + ## 3.1.5 ### Patch Changes diff --git a/packages/avatar/package.json b/packages/avatar/package.json index f7ea37ab9b..a266794ea6 100644 --- a/packages/avatar/package.json +++ b/packages/avatar/package.json @@ -1,6 +1,6 @@ { "name": "@leafygreen-ui/avatar", - "version": "3.1.5", + "version": "3.1.6", "description": "LeafyGreen UI Avatar", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/packages/banner/CHANGELOG.md b/packages/banner/CHANGELOG.md index 79c405b470..65000ffd96 100644 --- a/packages/banner/CHANGELOG.md +++ b/packages/banner/CHANGELOG.md @@ -1,5 +1,18 @@ # @leafygreen-ui/banner +## 10.2.4 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/icon-button@17.1.4 + - @leafygreen-ui/typography@22.2.3 + ## 10.2.3 ### Patch Changes diff --git a/packages/banner/package.json b/packages/banner/package.json index 8526af6aa4..d89dc9f488 100644 --- a/packages/banner/package.json +++ b/packages/banner/package.json @@ -1,6 +1,6 @@ { "name": "@leafygreen-ui/banner", - "version": "10.2.3", + "version": "10.2.4", "description": "leafyGreen UI Kit Banner", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/packages/callout/CHANGELOG.md b/packages/callout/CHANGELOG.md index bab054826c..268c505b88 100644 --- a/packages/callout/CHANGELOG.md +++ b/packages/callout/CHANGELOG.md @@ -1,5 +1,17 @@ # @leafygreen-ui/callout +## 12.1.4 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/typography@22.2.3 + ## 12.1.3 ### Patch Changes diff --git a/packages/callout/package.json b/packages/callout/package.json index b562861d22..cdc79abbc7 100644 --- a/packages/callout/package.json +++ b/packages/callout/package.json @@ -1,6 +1,6 @@ { "name": "@leafygreen-ui/callout", - "version": "12.1.3", + "version": "12.1.4", "description": "leafyGreen UI Kit Callout", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -16,15 +16,21 @@ }, "dependencies": { "@leafygreen-ui/emotion": "workspace:^", - "@leafygreen-ui/icon": "workspace:^", "@leafygreen-ui/lib": "workspace:^", "@leafygreen-ui/palette": "workspace:^", "@leafygreen-ui/tokens": "workspace:^", "@leafygreen-ui/typography": "workspace:^" }, "devDependencies": { + "@floating-ui/react": "^0.26.20", "@leafygreen-ui/code": "workspace:^", - "@lg-tools/build": "workspace:^" + "@leafygreen-ui/icon": "workspace:^", + "@lg-tools/build": "workspace:^", + "clipboard": "^2.0.11", + "facepaint": "^1.2.1", + "highlight.js": "^11.10.0", + "highlightjs-graphql": "^1.0.2", + "polished": "^4.2.2" }, "peerDependencies": { "@leafygreen-ui/leafygreen-provider": "workspace:^5.0.0 || ^4.0.0 || ^3.2.0" diff --git a/packages/canvas-header/CHANGELOG.md b/packages/canvas-header/CHANGELOG.md new file mode 100644 index 0000000000..ec3eadeed2 --- /dev/null +++ b/packages/canvas-header/CHANGELOG.md @@ -0,0 +1,78 @@ +# @leafygreen-ui/canvas-header + +## 3.0.2 + +### Patch Changes + +- 78f8481: First public release of CanvasHeader +- Updated dependencies [f7a63e2] +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/tooltip@14.3.0 + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/typography@22.2.3 + +Previously published as `@lg-private/canvas-header` + +## 3.0.1 + +### Patch Changes + +- 1916bd3: Removes background color from canvas header. + + Fixes "Copied" tooltip alignment. + +- f66e884: Updates `LiveExample` story so it displays correctly on .design + +## 3.0.0 + +### Major Changes + +- c9203f7: Removes `prop-types`. Updates LG core packages to latest + +## 2.1.0 + +### Minor Changes + +- daef9ca: Adds `resourceBadges` prop to render badges to the right of the resource name + +## 2.0.1 + +### Patch Changes + +- 8f076fb: Fixes naming of lg private packages from `@leafygreen-ui/*` to `@lg-private/*` + +## 2.0.0 + +### Major Changes + +- 0d12b4c: - Adds `badges` prop. This prop allows users to pass in [Badges](https://www.mongodb.design/component/badge/live-example). + + E.g. + + ```js + badges={Enabled} + ``` + + - Bump to @leafygreen-ui/leafygreen-provider@3.1.12 + +## 1.0.2 + +### Patch Changes + +- 33e6b57: No functional changes. Upgrades build to use Node 18 + +## 1.0.1 + +### Patch Changes + +- 476f90a: Updates `icon` & `tokens` packages to latest +- d5c17a8: Updates TS builds to use `typescript@4.9.5` + +## 1.0.0 + +### Major Changes + +- 8a2c4d1: First major release of `CanvasHeader`. [LG-3949](https://jira.mongodb.org/browse/LG-3949) diff --git a/packages/canvas-header/README.md b/packages/canvas-header/README.md new file mode 100644 index 0000000000..e32d8c8b3e --- /dev/null +++ b/packages/canvas-header/README.md @@ -0,0 +1,68 @@ +# Canvas Header + +![npm (scoped)](https://img.shields.io/npm/v/@leafygreen-ui/canvas-header.svg) + +#### [View on MongoDB.design](https://www.mongodb.design/component/canvas-header/example/) + +## Installation + +### Yarn + +```shell +yarn add @leafygreen-ui/canvas-header +``` + +### NPM + +```shell +npm install @leafygreen-ui/canvas-header +``` + +## Example + +```js +import { CanvasHeader } from `@leafygreen-ui/canvas-header`; +import Button from '@leafygreen-ui/button'; +import Icon from '@leafygreen-ui/icon'; +import { BackLink } from '@leafygreen-ui/typography'; + + + } + backLink={ + + Back to Cluster + + } + actions={ + + } + badges={ + <> + Enabled + In Dev Mode + + } + /> +``` + +## Properties + +| Prop | Type | Description | Default | +| -------------- | ----------------- | ----------------------------------------------------------------------------- | ------- | +| `darkMode` | `boolean` | Determines if the component renders in dark mode | `false` | +| `pageTitle` | `React.ReactNode` | Required page title | | +| `resourceName` | `string` | Optional resource name that will copy to the clipboard when clicked | | +| `resourceIcon` | `React.ReactNode` | Optional icon that will render to the left of the resource name | | +| `badges` | `React.ReactNode` | Optional badges that will render to the right of the resource name | | +| `actions` | `React.ReactNode` | Optional buttons that will render to the right of the badges or resource name | | +| `backLink` | `React.ReactNode` | Optional link that will render above the page title. | | diff --git a/packages/canvas-header/package.json b/packages/canvas-header/package.json new file mode 100644 index 0000000000..5b362e7285 --- /dev/null +++ b/packages/canvas-header/package.json @@ -0,0 +1,51 @@ +{ + "name": "@leafygreen-ui/canvas-header", + "version": "3.0.2", + "description": "LeafyGreen UI Kit Canvas Header", + "main": "./dist/umd/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", + "exports": { + ".": { + "require": "./dist/umd/index.js", + "import": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts" + } + }, + "license": "Apache-2.0", + "scripts": { + "build": "lg-build bundle", + "tsc": "lg-build tsc", + "docs": "lg-build docs" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@leafygreen-ui/emotion": "workspace:^", + "@leafygreen-ui/icon": "workspace:^", + "@leafygreen-ui/lib": "workspace:^", + "@leafygreen-ui/palette": "workspace:^", + "@leafygreen-ui/tokens": "workspace:^", + "@leafygreen-ui/tooltip": "workspace:^", + "@leafygreen-ui/typography": "workspace:^" + }, + "peerDependencies": { + "@leafygreen-ui/leafygreen-provider": "workspace:^5.0.0 || ^4.0.0 || ^3.2.0" + }, + "devDependencies": { + "@leafygreen-ui/badge": "workspace:^", + "@leafygreen-ui/button": "workspace:^", + "@lg-tools/build": "workspace:^", + "@lg-tools/storybook-utils": "workspace:^", + "@faker-js/faker": "^8.4.1" + }, + "homepage": "https://github.com/mongodb/leafygreen-ui/tree/main/packages/canvas-header", + "repository": { + "type": "git", + "url": "https://github.com/mongodb/leafygreen-ui" + }, + "bugs": { + "url": "https://jira.mongodb.org/projects/LG/summary" + } +} diff --git a/packages/canvas-header/src/CanvasHeader.stories.tsx b/packages/canvas-header/src/CanvasHeader.stories.tsx new file mode 100644 index 0000000000..adaa94987d --- /dev/null +++ b/packages/canvas-header/src/CanvasHeader.stories.tsx @@ -0,0 +1,277 @@ +import React from 'react'; +import { faker } from '@faker-js/faker'; +import { StoryMetaType, StoryType } from '@lg-tools/storybook-utils'; +import { StoryFn, StoryObj } from '@storybook/react'; + +import Badge from '@leafygreen-ui/badge'; +import Button from '@leafygreen-ui/button'; +import { css } from '@leafygreen-ui/emotion'; +import Icon from '@leafygreen-ui/icon'; +import { breakpoints } from '@leafygreen-ui/tokens'; +import { BackLink } from '@leafygreen-ui/typography'; + +import { CanvasHeader } from '.'; + +faker.seed(123); + +const testResourceNames = { + long: `${faker.database.collation()}-shard-${faker.string.uuid()}.${faker.database.mongodbObjectId()}${faker.database.mongodbObjectId()}${faker.database.mongodbObjectId()}`, + normal: `shard-${faker.database.mongodbObjectId()}`, + short: `myShard`, +}; + +const meta: StoryMetaType = { + title: 'Components/CanvasHeader', + component: CanvasHeader, + parameters: { + default: 'LiveExample', + controls: { + exclude: [ + 'resourceIcon', + 'actions', + 'backLink', + 'badges', + 'resourceBadges', + ], + }, + generate: { + storyNames: [ + 'LightMode', + 'DarkMode', + 'LightModeWithResource', + 'DarkModeWithResource', + 'Interactions', + ], + decorator: Instance => { + return ( +
+ +
+ ); + }, + combineArgs: { + backLink: [ + undefined, + + Back to Cluster + , + ], + badges: [ + undefined, + <> + Enabled + In Dev Mode + , + ], + actions: [ + undefined, + <> + + + , + ], + pageTitle: [ + 'Page Title', + 'Some very very long page title name that will trigger truncation eventually', + ], + }, + }, + }, + args: { + backLink: Back to Cluster, + pageTitle: 'Cluster Shards', + resourceName: testResourceNames.normal, + darkMode: false, + resourceIcon: , + actions: ( + + ), + badges: ( + <> + Enabled + In Dev Mode + + ), + resourceBadges: ( + <> + Ready + Queryable + + ), + }, + argTypes: { + darkMode: { + control: 'boolean', + }, + pageTitle: { + control: 'text', + }, + resourceName: { + control: 'text', + }, + }, +}; +export default meta; + +// Creating an explicit story so that it works on .design +export const LiveExample: StoryFn = props => ( +
+ Back to Cluster} + actions={ + + } + badges={ + <> + Enabled + In Dev Mode + + } + resourceIcon={} + resourceBadges={ + <> + Ready + Queryable + + } + /> +
+); +LiveExample.parameters = { + chromatic: { + disableSnapshot: true, + }, +}; + +export const LightMode: StoryObj = { + render: () => <>, + parameters: { + generate: { + args: { + darkMode: false, + resourceName: undefined, + }, + }, + }, +}; + +export const DarkMode: StoryObj = { + render: () => <>, + parameters: { + generate: { + args: { + darkMode: true, + resourceName: undefined, + }, + }, + }, +}; + +const staticArgsForResourceStories = { + pageTitle: 'Page title', + backLink: Back to Cluster, + actions: ( + + ), + badges: ( + <> + Enabled + In Dev Mode + + ), +}; + +const combinedArgsForResourceStories = { + resourceIcon: [undefined, ], + resourceBadges: [ + undefined, + <> + Ready + Queryable + , + ], + resourceName: [ + testResourceNames.short, + testResourceNames.normal, + testResourceNames.long, + ], +}; + +export const LightModeWithResource: StoryObj = { + render: () => <>, + parameters: { + generate: { + args: { + darkMode: false, + ...staticArgsForResourceStories, + }, + combineArgs: { + ...combinedArgsForResourceStories, + }, + }, + }, +}; + +export const DarkModeWithResource: StoryObj = { + render: () => <>, + parameters: { + generate: { + args: { + darkMode: true, + ...staticArgsForResourceStories, + }, + combineArgs: { + ...combinedArgsForResourceStories, + }, + }, + }, +}; + +export const Interactions: StoryType = () => <>; +Interactions.parameters = { + generate: { + args: { + pageTitle: 'Page title', + actions: undefined, + badges: undefined, + backLink: undefined, + }, + combineArgs: { + darkMode: [true, false], + resourceName: [testResourceNames.short, testResourceNames.long], + // @ts-ignore + 'data-hover': [false, true], + 'data-focus': [false, true], + }, + excludeCombinations: [ + { + // @ts-ignore + 'data-hover': false, + 'data-focus': false, + }, + ], + }, +}; diff --git a/packages/canvas-header/src/CanvasHeader/CanvasHeader.spec.tsx b/packages/canvas-header/src/CanvasHeader/CanvasHeader.spec.tsx new file mode 100644 index 0000000000..9290befd25 --- /dev/null +++ b/packages/canvas-header/src/CanvasHeader/CanvasHeader.spec.tsx @@ -0,0 +1,159 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; + +import Badge from '@leafygreen-ui/badge'; +import Button from '@leafygreen-ui/button'; +import Icon from '@leafygreen-ui/icon'; +import { Link } from '@leafygreen-ui/typography'; + +import { CanvasHeader, CanvasHeaderProps } from '.'; + +const resourceNameString = + 'ac_iqttxwn_shard-00-01.hvcuthh.mongodbnet:27017_324892384903284902384903284903284902384903284832908_long_name'; + +type PartialCanvasHeaderProps = Partial; + +const renderCanvasHeader = ({ ...props }: PartialCanvasHeaderProps) => { + const utils = render( + + Back to Cluster + + } + resourceName={resourceNameString} + resourceIcon={} + actions={ + + } + badges={ + + Enabled + + } + {...props} + />, + ); + + const pageTitle = utils.getByTestId('lg-canvas_header-page_title'); + const backLink = utils.getByTestId('lg-canvas_header-back_link'); + const resourceName = utils.getByTestId('lg-canvas_header-resource_name'); + const actionBtn = utils.getByTestId('lg-canvas_header-button'); + const badge = utils.getByTestId('lg-canvas_header-badge'); + const resourceIcon = utils.getByLabelText('Sharded Cluster Icon'); + + return { + ...utils, + pageTitle, + backLink, + resourceIcon, + resourceName, + actionBtn, + badge, + }; +}; + +describe('packages/canvas-header', () => { + describe('a11y', () => { + test('does not have basic accessibility issues', async () => { + const { container } = renderCanvasHeader({}); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + }); + + describe('render', () => { + test('page title', () => { + const { pageTitle } = renderCanvasHeader({}); + expect(pageTitle.textContent).toBe('page title'); + }); + + test('back link', () => { + const { backLink } = renderCanvasHeader({}); + expect(backLink.textContent).toBe('Back to Cluster'); + }); + + test('resource name', () => { + const { resourceName } = renderCanvasHeader({}); + expect(resourceName.textContent).toBe(resourceNameString); + }); + + test('resource icon', () => { + const { resourceIcon } = renderCanvasHeader({}); + expect(resourceIcon).toBeInTheDocument(); + }); + + test('action button', () => { + const { actionBtn } = renderCanvasHeader({}); + expect(actionBtn.textContent).toBe('Invite user'); + }); + + test('badge', () => { + const { badge } = renderCanvasHeader({}); + expect(badge.textContent).toBe('Enabled'); + }); + + test('does not render resourceIcon without a resourceName', () => { + const { queryByText } = render( + } + />, + ); + + const resourceName = queryByText(resourceNameString); + expect(resourceName).not.toBeInTheDocument(); + }); + }); + + describe('TypeScript types are correct', () => { + // eslint-disable-next-line jest/no-disabled-tests + test.skip('CanvasHeader component types', () => { + <> + {/* @ts-expect-error Property 'pageTitle' is missing */} + + + + Back to Cluster + + } + resourceName={resourceNameString} + resourceIcon={} + actions={ + + } + badges={ + + Enabled + + } + /> + {/* @ts-expect-error Property 'children' does not exist */} + hi + ; + }); + }); +}); diff --git a/packages/canvas-header/src/CanvasHeader/CanvasHeader.styles.ts b/packages/canvas-header/src/CanvasHeader/CanvasHeader.styles.ts new file mode 100644 index 0000000000..f3337e3ed9 --- /dev/null +++ b/packages/canvas-header/src/CanvasHeader/CanvasHeader.styles.ts @@ -0,0 +1,54 @@ +import { css } from '@leafygreen-ui/emotion'; +import { createUniqueClassName, Theme } from '@leafygreen-ui/lib'; +import { color, spacing } from '@leafygreen-ui/tokens'; + +export const canvasHeaderClassname = createUniqueClassName('canvas-header'); + +export const canvasHeaderBaseStyles = css` + display: flex; + flex-direction: column; + gap: ${spacing[100]}px; + margin-block: ${spacing[400]}px; +`; + +export const titleWrapperStyles = css` + display: flex; + align-items: center; + gap: ${spacing[300]}px; +`; + +export const getTitleStyles = (theme: Theme) => css` + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + color: ${color[theme].text.primary?.default}; +`; + +export const backLinkStyles = css` + display: flex; +`; + +export const actionsStyles = css` + display: flex; + flex-wrap: nowrap; + flex-shrink: 0; + gap: ${spacing[200]}px; + justify-content: end; + flex-grow: 1; + padding-inline-start: ${spacing[300]}px; + + button { + white-space: nowrap; + } +`; + +export const badgesStyles = css` + display: flex; + flex-wrap: nowrap; + gap: ${spacing[200]}px; + flex-shrink: 0; + + position: relative; + // Badges are not vertically aligned with the title + top: 3px; +`; diff --git a/packages/canvas-header/src/CanvasHeader/CanvasHeader.tsx b/packages/canvas-header/src/CanvasHeader/CanvasHeader.tsx new file mode 100644 index 0000000000..0444ee2603 --- /dev/null +++ b/packages/canvas-header/src/CanvasHeader/CanvasHeader.tsx @@ -0,0 +1,73 @@ +import React from 'react'; + +import { cx } from '@leafygreen-ui/emotion'; +import LeafyGreenProvider, { + useDarkMode, +} from '@leafygreen-ui/leafygreen-provider'; +import { H2 } from '@leafygreen-ui/typography'; + +import { LGIDS } from '../constants'; +import { Resource } from '../Resource'; + +import { + actionsStyles, + backLinkStyles, + badgesStyles, + canvasHeaderBaseStyles, + canvasHeaderClassname, + getTitleStyles, + titleWrapperStyles, +} from './CanvasHeader.styles'; +import { type CanvasHeaderProps } from './CanvasHeader.types'; + +export const CanvasHeader = React.forwardRef( + ( + { + darkMode: darkModeProp, + pageTitle, + resourceIcon, + resourceName, + resourceBadges, + actions, + backLink, + className, + badges, + ...rest + }: CanvasHeaderProps, + forwardRef, + ) => { + const { darkMode, theme } = useDarkMode(darkModeProp); + return ( + +
+ {!!backLink &&
{backLink}
} +
+

+ {pageTitle} +

+ {!!badges &&
{badges}
} + {!!actions &&
{actions}
} +
+ {!!resourceName && ( + + )} +
+
+ ); + }, +); + +CanvasHeader.displayName = 'CanvasHeader'; diff --git a/packages/canvas-header/src/CanvasHeader/CanvasHeader.types.ts b/packages/canvas-header/src/CanvasHeader/CanvasHeader.types.ts new file mode 100644 index 0000000000..c4238941a3 --- /dev/null +++ b/packages/canvas-header/src/CanvasHeader/CanvasHeader.types.ts @@ -0,0 +1,42 @@ +import { ReactNode } from 'react'; + +import { DarkModeProps, HTMLElementProps } from '@leafygreen-ui/lib'; + +export interface CanvasHeaderProps + extends DarkModeProps, + Omit, 'children'> { + /** + * Required page title + */ + pageTitle: string; + + /** + * Optional buttons that will render to the right of the badges or resource name + */ + actions?: ReactNode; + + /** + * Optional link that will render above the page title. + */ + backLink?: ReactNode; + + /** + * Optional badges that will render to the right of the page title + */ + badges?: ReactNode; + + /** + * Optional resource name that will copy to the clipboard when clicked + */ + resourceName?: string; + + /** + * Optional icon that will render to the left of the resource name + */ + resourceIcon?: ReactNode; + + /** + * Optional badges that will render to the right of the resource name + */ + resourceBadges?: ReactNode; +} diff --git a/packages/canvas-header/src/CanvasHeader/index.ts b/packages/canvas-header/src/CanvasHeader/index.ts new file mode 100644 index 0000000000..6139bb5248 --- /dev/null +++ b/packages/canvas-header/src/CanvasHeader/index.ts @@ -0,0 +1,2 @@ +export { CanvasHeader } from './CanvasHeader'; +export { type CanvasHeaderProps } from './CanvasHeader.types'; diff --git a/packages/canvas-header/src/Resource/Resource.spec.tsx b/packages/canvas-header/src/Resource/Resource.spec.tsx new file mode 100644 index 0000000000..fe1516f75b --- /dev/null +++ b/packages/canvas-header/src/Resource/Resource.spec.tsx @@ -0,0 +1,123 @@ +import React from 'react'; +import { act, render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { axe } from 'jest-axe'; + +import Icon from '@leafygreen-ui/icon'; + +import { ResourceProps } from './Resource.types'; +import { Resource } from '.'; + +const renderResource = (props: ResourceProps) => { + const utils = render(); + + const resourceName = utils.getByTestId('lg-canvas_header-resource_name'); + + return { resourceName, ...utils }; +}; + +describe('packages/canvas-header/resource', () => { + // https://stackoverflow.com/a/69574825/13156339 + Object.defineProperty(navigator, 'clipboard', { + value: { + // Provide mock implementation + writeText: jest.fn().mockReturnValueOnce(Promise.resolve()), + }, + }); + describe('a11y', () => { + test('does not have basic accessibility issues', async () => { + const { container, resourceName } = renderResource({ + resourceName: 'resource name', + }); + const results = await axe(container); + expect(results).toHaveNoViolations(); + + let newResults = null as any; + act(() => void userEvent.click(resourceName)); + await act(async () => { + newResults = await axe(container); + }); + expect(newResults).toHaveNoViolations(); + }); + }); + + describe('render', () => { + test('name and icon', () => { + const { container, getByLabelText } = renderResource({ + resourceName: 'resource name', + resourceIcon: , + }); + + const icon = getByLabelText('Sharded Cluster Icon'); + expect(icon).toBeInTheDocument(); + expect(container.textContent).toBe('resource name'); + }); + }); + + describe('copy', () => { + describe('resource name is copied to clipboard', () => { + test('onClick', () => { + const { resourceName } = renderResource({ + resourceName: 'onClick', + }); + + userEvent.click(resourceName); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith('onClick'); + }); + + test('on enter', () => { + const { resourceName } = renderResource({ + resourceName: 'on Enter', + }); + + resourceName.focus(); + expect(resourceName).toHaveFocus(); + userEvent.keyboard('[Enter]'); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith('on Enter'); + }); + + test('on space', () => { + const { resourceName } = renderResource({ + resourceName: 'on Space', + }); + + resourceName.focus(); + expect(resourceName).toHaveFocus(); + userEvent.keyboard('[Space]'); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith('on Space'); + }); + }); + describe('tooltip renders', () => { + test('onClick', async () => { + const { resourceName, getByText } = renderResource({ + resourceName: 'onClick', + }); + + userEvent.click(resourceName); + await waitFor(() => expect(getByText('Copied!')).toBeVisible()); + }); + + test('on enter', async () => { + const { resourceName, getByText } = renderResource({ + resourceName: 'on Enter', + }); + + resourceName.focus(); + expect(resourceName).toHaveFocus(); + userEvent.keyboard('[Enter]'); + await waitFor(() => expect(getByText('Copied!')).toBeVisible()); + }); + + test('on space', async () => { + const { resourceName, getByText } = renderResource({ + resourceName: 'on Space', + }); + + resourceName.focus(); + expect(resourceName).toHaveFocus(); + userEvent.keyboard('[Space]'); + await waitFor(() => expect(getByText('Copied!')).toBeVisible()); + }); + }); + }); +}); diff --git a/packages/canvas-header/src/Resource/Resource.styles.ts b/packages/canvas-header/src/Resource/Resource.styles.ts new file mode 100644 index 0000000000..771aa1958e --- /dev/null +++ b/packages/canvas-header/src/Resource/Resource.styles.ts @@ -0,0 +1,114 @@ +import { css } from '@leafygreen-ui/emotion'; +import { createUniqueClassName, Theme } from '@leafygreen-ui/lib'; +import { palette } from '@leafygreen-ui/palette'; +import { + color, + focusRing, + spacing, + transitionDuration, + typeScales, +} from '@leafygreen-ui/tokens'; + +import { canvasHeaderClassname } from '../CanvasHeader/CanvasHeader.styles'; + +export const resourceNameButtonClassName = createUniqueClassName( + 'canvas-header-resource', +); + +export const resourceBaseStyles = css` + line-break: anywhere; + position: relative; + display: inline; +`; + +export const resourceIconBaseStyles = css` + display: inline-block; + color: ${palette.gray.base}; + padding-right: ${spacing[200]}px; + + svg { + position: relative; + top: 2px; + } +`; + +export const getResourceNameButtonStyles = (theme: Theme) => css` + display: inline; + cursor: pointer; + border-radius: 6px; + color: ${color[theme].text.secondary?.default}; + + &:focus-visible, + .${canvasHeaderClassname}[data-focus="true"] & { + outline: 1px solid white; + box-shadow: ${focusRing[theme].default}; + } +`; + +export const getResourceNameStyles = (theme: Theme) => css` + display: inline; + color: inherit; + font-size: ${typeScales.body2.fontSize}px; + line-height: ${typeScales.body2.lineHeight}px; + transition: border-color ${transitionDuration.default}ms ease-in-out; + border-bottom: 2px solid; + border-color: transparent; + padding-bottom: 2px; + + .${resourceNameButtonClassName}:hover &, + .${canvasHeaderClassname}[data-hover="true"] & { + border-color: ${color[theme].border.secondary?.default}; + } +`; + +export const getResourceCopyIconWrapperStyles = (theme: Theme) => css` + position: relative; + right: 0; + margin-left: -24px; + opacity: 0; + transition: ${transitionDuration.default}ms ease-in-out; + transition-property: border-color, opacity; + background-color: ${color[theme].background.primary.default}; + padding-inline: ${spacing[100]}px; + border-radius: 0 ${spacing[150]}px ${spacing[150]}px 0; + + svg { + position: relative; + top: ${spacing[150] / 2}px; + } + + .${resourceNameButtonClassName}:hover &, + .${resourceNameButtonClassName}:focus-visible &, + .${canvasHeaderClassname}[data-hover="true"] &, + .${canvasHeaderClassname}[data-focus="true"] & { + opacity: 1; + } + + // Add a linear gradient over the resource name + &:before { + content: ''; + position: absolute; + display: inline-block; + left: -${spacing[300]}px; + top: 0; + width: ${spacing[300]}px; + height: 100%; + background: linear-gradient( + to right, + ${palette.transparent}, + ${color[theme].background.primary.default} + ); + } +`; + +export const resourceCopiedStyles = css` + opacity: 1; +`; + +export const resourceBadgeStyles = css` + position: relative; + display: inline-flex; + flex-shrink: 0; + gap: ${spacing[200]}px; + padding-left: ${spacing[200]}px; +`; diff --git a/packages/canvas-header/src/Resource/Resource.tsx b/packages/canvas-header/src/Resource/Resource.tsx new file mode 100644 index 0000000000..92df583407 --- /dev/null +++ b/packages/canvas-header/src/Resource/Resource.tsx @@ -0,0 +1,106 @@ +import React, { + KeyboardEventHandler, + MouseEventHandler, + useEffect, + useRef, + useState, +} from 'react'; + +import { cx } from '@leafygreen-ui/emotion'; +import Icon from '@leafygreen-ui/icon'; +import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; +import { keyMap } from '@leafygreen-ui/lib'; +import Tooltip, { Justify } from '@leafygreen-ui/tooltip'; + +import { LGIDS } from '../constants'; + +import { + getResourceCopyIconWrapperStyles, + getResourceNameButtonStyles, + getResourceNameStyles, + resourceBadgeStyles, + resourceBaseStyles, + resourceCopiedStyles, + resourceIconBaseStyles, + resourceNameButtonClassName, +} from './Resource.styles'; +import { ResourceProps } from './Resource.types'; + +/** + * Internal contents for the resource name + * @internal + */ +export const Resource = React.forwardRef( + ( + { resourceIcon, resourceName, resourceBadges }: ResourceProps, + forwardRef, + ) => { + const [copied, setCopied] = useState(false); + const { theme } = useDarkMode(); + const timerRef = useRef(null); + const copyIconRef = useRef(null); + + useEffect(() => { + // Clear the timeout when the component unmounts + return () => clearTimeout(timerRef.current as NodeJS.Timeout); + }, []); + + const copyTextToClipboard = () => { + navigator.clipboard.writeText(resourceName as string); + setCopied(true); + timerRef.current = setTimeout(() => { + setCopied(false); + }, 2000); + }; + + const handleClick: MouseEventHandler = () => { + copyTextToClipboard(); + }; + + const handleKeyDown: KeyboardEventHandler = e => { + if (e.key === keyMap.Enter || e.key === keyMap.Space) { + copyTextToClipboard(); + } + }; + + if (!resourceName) return null; + + return ( +
+ {!!resourceIcon && ( + {resourceIcon} + )} + + {resourceName} + + + + + Copied! + + + {resourceBadges && ( + {resourceBadges} + )} +
+ ); + }, +); + +Resource.displayName = 'Resource'; diff --git a/packages/canvas-header/src/Resource/Resource.types.ts b/packages/canvas-header/src/Resource/Resource.types.ts new file mode 100644 index 0000000000..ff4c774382 --- /dev/null +++ b/packages/canvas-header/src/Resource/Resource.types.ts @@ -0,0 +1,6 @@ +import { CanvasHeaderProps } from '../CanvasHeader'; + +export type ResourceProps = Pick< + CanvasHeaderProps, + 'resourceName' | 'resourceIcon' | 'resourceBadges' +>; diff --git a/packages/canvas-header/src/Resource/index.ts b/packages/canvas-header/src/Resource/index.ts new file mode 100644 index 0000000000..b732dfd137 --- /dev/null +++ b/packages/canvas-header/src/Resource/index.ts @@ -0,0 +1 @@ +export { Resource } from './Resource'; diff --git a/packages/canvas-header/src/constants.ts b/packages/canvas-header/src/constants.ts new file mode 100644 index 0000000000..725232dd57 --- /dev/null +++ b/packages/canvas-header/src/constants.ts @@ -0,0 +1,7 @@ +const LGID_ROOT = 'lg-canvas_header'; + +export const LGIDS = { + root: LGID_ROOT, + pageTitle: `${LGID_ROOT}-page_title`, + resourceName: `${LGID_ROOT}-resource_name`, +} as const; diff --git a/packages/canvas-header/src/index.ts b/packages/canvas-header/src/index.ts new file mode 100644 index 0000000000..5b0392928b --- /dev/null +++ b/packages/canvas-header/src/index.ts @@ -0,0 +1 @@ +export { CanvasHeader, type CanvasHeaderProps } from './CanvasHeader'; diff --git a/packages/canvas-header/tsconfig.json b/packages/canvas-header/tsconfig.json new file mode 100644 index 0000000000..58645103e5 --- /dev/null +++ b/packages/canvas-header/tsconfig.json @@ -0,0 +1,36 @@ +{ + "extends": "@lg-tools/build/config/package.tsconfig.json", + "compilerOptions": { + "paths": { + "@lg-private/*": ["../*/src"] + } + }, + "include": ["src/**/*"], + "exclude": ["**/*.spec.*", "**/*.story.*"], + "references": [ + { + "path": "../emotion" + }, + { + "path": "../icon" + }, + { + "path": "../leafygreen-provider" + }, + { + "path": "../lib" + }, + { + "path": "../palette" + }, + { + "path": "../tokens" + }, + { + "path": "../tooltip" + }, + { + "path": "../typography" + } + ] +} diff --git a/packages/checkbox/CHANGELOG.md b/packages/checkbox/CHANGELOG.md index f1f65ad05a..d9b24cd99b 100644 --- a/packages/checkbox/CHANGELOG.md +++ b/packages/checkbox/CHANGELOG.md @@ -1,5 +1,15 @@ # @leafygreen-ui/checkbox +## 18.1.4 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/typography@22.2.3 + ## 18.1.3 ### Patch Changes diff --git a/packages/checkbox/package.json b/packages/checkbox/package.json index 0751373715..51caf44f39 100644 --- a/packages/checkbox/package.json +++ b/packages/checkbox/package.json @@ -1,6 +1,6 @@ { "name": "@leafygreen-ui/checkbox", - "version": "18.1.3", + "version": "18.1.4", "description": "LeafyGreen UI Kit Checkbox", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/packages/chip/CHANGELOG.md b/packages/chip/CHANGELOG.md index e2278ec376..d25d22bed3 100644 --- a/packages/chip/CHANGELOG.md +++ b/packages/chip/CHANGELOG.md @@ -1,5 +1,17 @@ # @leafygreen-ui/chip +## 4.0.10 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/inline-definition@9.1.4 + ## 4.0.9 ### Patch Changes diff --git a/packages/chip/package.json b/packages/chip/package.json index 9288de54fb..d308d3a4e8 100644 --- a/packages/chip/package.json +++ b/packages/chip/package.json @@ -1,6 +1,6 @@ { "name": "@leafygreen-ui/chip", - "version": "4.0.9", + "version": "4.0.10", "description": "LeafyGreen UI Kit Chip", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/packages/code-editor/CHANGELOG.md b/packages/code-editor/CHANGELOG.md index c968239fb9..f38b91d6c9 100644 --- a/packages/code-editor/CHANGELOG.md +++ b/packages/code-editor/CHANGELOG.md @@ -1,5 +1,11 @@ # @leafygreen-ui/code-editor +## 1.0.3 + +### Patch Changes + +- 19c0cbe: Refactor `IconButton` instances to use compact tooltip UI + ## 1.0.2 ### Patch Changes diff --git a/packages/code-editor/package.json b/packages/code-editor/package.json index 5d1eb098d8..f9234ed608 100644 --- a/packages/code-editor/package.json +++ b/packages/code-editor/package.json @@ -1,6 +1,6 @@ { "name": "@leafygreen-ui/code-editor", - "version": "1.0.2", + "version": "1.0.3", "description": "LeafyGreen UI Kit Code Editor", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -63,17 +63,17 @@ "@leafygreen-ui/tooltip": "workspace:^", "@leafygreen-ui/typography": "workspace:^", "@lezer/highlight": "^1.2.1", - "@lg-tools/test-harnesses": "^0.3.4", "@replit/codemirror-lang-csharp": "^6.2.0", "@uiw/codemirror-extensions-hyper-link": "^4.23.12", "@wasm-fmt/clang-format": "^20.1.7", "@wasm-fmt/gofmt": "^0.4.9", "@wasm-fmt/ruff_fmt": "^0.10.0", - "codemirror": "^6.0.2", - "prettier": "2.8.8" + "codemirror": "^6.0.2" }, "devDependencies": { - "@lg-tools/build": "workspace:^" + "@lg-tools/build": "workspace:^", + "@lg-tools/test-harnesses": "^0.3.4", + "prettier": "2.8.8" }, "peerDependencies": { "@leafygreen-ui/leafygreen-provider": "workspace:^5.0.0 || ^4.0.0 || ^3.2.0" diff --git a/packages/code-editor/src/CodeEditorCopyButton/CodeEditorCopyButton.tsx b/packages/code-editor/src/CodeEditorCopyButton/CodeEditorCopyButton.tsx index f520f42e9e..e756a6a565 100644 --- a/packages/code-editor/src/CodeEditorCopyButton/CodeEditorCopyButton.tsx +++ b/packages/code-editor/src/CodeEditorCopyButton/CodeEditorCopyButton.tsx @@ -4,13 +4,14 @@ import { useBackdropClick } from '@leafygreen-ui/hooks'; import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; import { keyMap } from '@leafygreen-ui/lib'; import { palette } from '@leafygreen-ui/palette'; -import { color } from '@leafygreen-ui/tokens'; +import { color, spacing } from '@leafygreen-ui/tokens'; import { Align, hoverDelay, Justify, RenderMode, Tooltip, + TooltipVariant, } from '@leafygreen-ui/tooltip'; import { CopyButtonTrigger } from '../CodeEditorCopyButtonTrigger'; @@ -179,6 +180,7 @@ export function CodeEditorCopyButton({ renderMode={RenderMode.TopLayer} setOpen={setTooltipOpen} darkMode={theme === 'dark'} + spacing={spacing[100]} trigger={ } shouldClose={shouldClose} + variant={TooltipVariant.Compact} > {copied ? COPIED_TEXT : COPY_TEXT} diff --git a/packages/code-editor/src/Panel/Panel.tsx b/packages/code-editor/src/Panel/Panel.tsx index 1554fc87b4..645aed5a26 100644 --- a/packages/code-editor/src/Panel/Panel.tsx +++ b/packages/code-editor/src/Panel/Panel.tsx @@ -16,7 +16,8 @@ import { IconButton } from '@leafygreen-ui/icon-button'; import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; import { Menu, MenuItem, MenuVariant } from '@leafygreen-ui/menu'; import { Modal } from '@leafygreen-ui/modal'; -import { Tooltip } from '@leafygreen-ui/tooltip'; +import { spacing } from '@leafygreen-ui/tokens'; +import { Tooltip, TooltipVariant } from '@leafygreen-ui/tooltip'; import { useUpdatedBaseFontSize } from '@leafygreen-ui/typography'; import { useCodeEditorContext } from '../CodeEditor/CodeEditorContext'; @@ -154,6 +155,7 @@ export function Panel({ baseFontSize={baseFontSize} align="top" justify="middle" + spacing={spacing[100]} trigger={ } + variant={TooltipVariant.Compact} > Prettify code diff --git a/packages/code/CHANGELOG.md b/packages/code/CHANGELOG.md index 8b967c74a5..bf38db3201 100644 --- a/packages/code/CHANGELOG.md +++ b/packages/code/CHANGELOG.md @@ -1,5 +1,28 @@ # @leafygreen-ui/code +## 20.2.5 + +### Patch Changes + +- 19c0cbe: Refactor `IconButton` instances to use compact tooltip UI + +## 20.2.4 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [f7a63e2] +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/tooltip@14.3.0 + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/icon-button@17.1.4 + - @leafygreen-ui/select@17.0.2 + - @leafygreen-ui/skeleton-loader@3.0.10 + - @leafygreen-ui/typography@22.2.3 + ## 20.2.3 ### Patch Changes diff --git a/packages/code/package.json b/packages/code/package.json index 0aadc8804c..48e7fbc980 100644 --- a/packages/code/package.json +++ b/packages/code/package.json @@ -1,6 +1,6 @@ { "name": "@leafygreen-ui/code", - "version": "20.2.3", + "version": "20.2.5", "description": "leafyGreen UI Kit Code Blocks", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", @@ -29,9 +29,6 @@ "publishConfig": { "access": "public" }, - "devDependencies": { - "@lg-tools/build": "workspace:^" - }, "dependencies": { "@leafygreen-ui/a11y": "workspace:^", "@leafygreen-ui/button": "workspace:^", @@ -46,16 +43,19 @@ "@leafygreen-ui/tokens": "workspace:^", "@leafygreen-ui/tooltip": "workspace:^", "@leafygreen-ui/typography": "workspace:^", - "@lg-tools/test-harnesses": "workspace:^", "@types/facepaint": "^1.2.1", "@types/highlight.js": "^10.1.0", "clipboard": "^2.0.6", "facepaint": "^1.2.1", "highlight.js": "~11.5.0", "highlightjs-graphql": "^1.0.1", - "lodash": "^4.17.21", "polished": "^4.2.2" }, + "devDependencies": { + "@lg-tools/build": "workspace:^", + "@lg-tools/test-harnesses": "workspace:^", + "lodash": "^4.17.21" + }, "peerDependencies": { "@leafygreen-ui/leafygreen-provider": "workspace:^5.0.0 || ^4.0.0 || ^3.2.0" }, diff --git a/packages/code/src/CopyButton/CopyButton.tsx b/packages/code/src/CopyButton/CopyButton.tsx index 106ca45c8b..abda2eda63 100644 --- a/packages/code/src/CopyButton/CopyButton.tsx +++ b/packages/code/src/CopyButton/CopyButton.tsx @@ -12,12 +12,14 @@ import { usePopoverPortalContainer, } from '@leafygreen-ui/leafygreen-provider'; import { keyMap } from '@leafygreen-ui/lib'; +import { spacing } from '@leafygreen-ui/tokens'; import { Align, hoverDelay, Justify, RenderMode, Tooltip, + TooltipVariant, } from '@leafygreen-ui/tooltip'; import { useCodeContext } from '../CodeContext/CodeContext'; @@ -152,6 +154,8 @@ function CopyButton({ onCopy, contents, className, ...rest }: CopyProps) { open={tooltipOpen} renderMode={RenderMode.TopLayer} setOpen={setTooltipOpen} + shouldClose={shouldClose} + spacing={spacing[100]} trigger={ showPanel ? ( @@ -172,7 +176,7 @@ function CopyButton({ onCopy, contents, className, ...rest }: CopyProps) { ) } - shouldClose={shouldClose} + variant={TooltipVariant.Compact} > {copied ? COPIED_TEXT : COPY_TEXT} diff --git a/packages/combobox/CHANGELOG.md b/packages/combobox/CHANGELOG.md index bcad41b9fe..cfe146997e 100644 --- a/packages/combobox/CHANGELOG.md +++ b/packages/combobox/CHANGELOG.md @@ -1,5 +1,26 @@ # @leafygreen-ui/combobox +## 12.3.0 + +### Minor Changes + +- c7d6e62: Export `RenderMode` enum + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/chip@4.0.10 + - @leafygreen-ui/form-field@4.0.8 + - @leafygreen-ui/icon-button@17.1.4 + - @leafygreen-ui/typography@22.2.3 + - @leafygreen-ui/checkbox@18.1.4 + - @leafygreen-ui/input-option@4.1.4 + ## 12.2.1 ### Patch Changes diff --git a/packages/combobox/package.json b/packages/combobox/package.json index 3fac3565d9..eb5db255b9 100644 --- a/packages/combobox/package.json +++ b/packages/combobox/package.json @@ -1,6 +1,6 @@ { "name": "@leafygreen-ui/combobox", - "version": "12.2.1", + "version": "12.3.0", "description": "leafyGreen UI Kit Combobox", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/packages/confirmation-modal/CHANGELOG.md b/packages/confirmation-modal/CHANGELOG.md index abc94cadd3..e54c419cdd 100644 --- a/packages/confirmation-modal/CHANGELOG.md +++ b/packages/confirmation-modal/CHANGELOG.md @@ -1,5 +1,19 @@ # @leafygreen-ui/confirmation-modal +## 10.2.3 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/modal@20.3.3 + - @leafygreen-ui/typography@22.2.3 + - @leafygreen-ui/text-input@16.2.2 + ## 10.2.2 ### Patch Changes diff --git a/packages/confirmation-modal/package.json b/packages/confirmation-modal/package.json index 92a2cc4b99..2d169bc72a 100644 --- a/packages/confirmation-modal/package.json +++ b/packages/confirmation-modal/package.json @@ -1,6 +1,6 @@ { "name": "@leafygreen-ui/confirmation-modal", - "version": "10.2.2", + "version": "10.2.3", "description": "leafyGreen UI Kit Confirmation Modal", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/packages/context-drawer/CHANGELOG.md b/packages/context-drawer/CHANGELOG.md index 74d87476fa..3952fd2426 100644 --- a/packages/context-drawer/CHANGELOG.md +++ b/packages/context-drawer/CHANGELOG.md @@ -1,5 +1,15 @@ # @leafygreen-ui/context-drawer +## 0.2.8 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + ## 0.2.7 ### Patch Changes diff --git a/packages/context-drawer/package.json b/packages/context-drawer/package.json index 30027cbc1c..51744f644d 100644 --- a/packages/context-drawer/package.json +++ b/packages/context-drawer/package.json @@ -1,6 +1,6 @@ { "name": "@leafygreen-ui/context-drawer", - "version": "0.2.7", + "version": "0.2.8", "description": "LeafyGreen UI Kit Context Drawer", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/packages/copyable/CHANGELOG.md b/packages/copyable/CHANGELOG.md index 533b0391a0..cd9c39fead 100644 --- a/packages/copyable/CHANGELOG.md +++ b/packages/copyable/CHANGELOG.md @@ -1,5 +1,19 @@ # @leafygreen-ui/copyable +## 12.0.2 + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [f7a63e2] +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/tooltip@14.3.0 + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/typography@22.2.3 + ## 12.0.1 ### Patch Changes diff --git a/packages/copyable/package.json b/packages/copyable/package.json index 1a203aadb4..0a54443240 100644 --- a/packages/copyable/package.json +++ b/packages/copyable/package.json @@ -1,6 +1,6 @@ { "name": "@leafygreen-ui/copyable", - "version": "12.0.1", + "version": "12.0.2", "description": "leafyGreen UI Kit Copyable", "main": "./dist/umd/index.js", "module": "./dist/esm/index.js", diff --git a/packages/date-picker/CHANGELOG.md b/packages/date-picker/CHANGELOG.md index ca9002a340..5c88837be6 100644 --- a/packages/date-picker/CHANGELOG.md +++ b/packages/date-picker/CHANGELOG.md @@ -1,5 +1,32 @@ # @leafygreen-ui/date-picker +## 4.1.1 + +### Patch Changes + +- 0c42aba: [LG-3879](https://jira.mongodb.org/browse/LG-3879) + Updates ARIA labels for DatePicker menu previous/next buttons, and year/month select elements. + Hides calendar cell text, so screen-readers only read the cell's `aria-value`. + +## 4.1.0 + +### Minor Changes + +- c7d6e62: Export `Align` and `Justify` enums + +### Patch Changes + +- cb31ce6: fix: remove unexpected @emotion imports from icon package dependency +- Updated dependencies [43810b4] +- Updated dependencies [ec4fad8] +- Updated dependencies [cb31ce6] + - @leafygreen-ui/icon@14.7.1 + - @leafygreen-ui/tokens@4.1.0 + - @leafygreen-ui/form-field@4.0.8 + - @leafygreen-ui/icon-button@17.1.4 + - @leafygreen-ui/select@17.0.2 + - @leafygreen-ui/typography@22.2.3 + ## 4.0.12 ### Patch Changes diff --git a/packages/date-picker/package.json b/packages/date-picker/package.json index ab1613e544..865ee2241c 100644 --- a/packages/date-picker/package.json +++ b/packages/date-picker/package.json @@ -1,6 +1,6 @@ { "name": "@leafygreen-ui/date-picker", - "version": "4.0.12", + "version": "4.1.1", "description": "LeafyGreen UI Kit Date Picker", "license": "Apache-2.0", "main": "./dist/umd/index.js", diff --git a/packages/date-picker/src/DatePicker/DatePicker.testutils.tsx b/packages/date-picker/src/DatePicker/DatePicker.testutils.tsx index 29e99ca5ef..fcabc963f2 100644 --- a/packages/date-picker/src/DatePicker/DatePicker.testutils.tsx +++ b/packages/date-picker/src/DatePicker/DatePicker.testutils.tsx @@ -114,12 +114,14 @@ export const renderDatePicker = ( const calendarGrid = withinElement(menuContainerEl)?.queryByRole('grid'); const calendarCells = withinElement(menuContainerEl)?.getAllByRole('gridcell'); - const leftChevron = - withinElement(menuContainerEl)?.queryByLabelText('Previous month') || - withinElement(menuContainerEl)?.queryByLabelText('Previous valid month'); - const rightChevron = - withinElement(menuContainerEl)?.queryByLabelText('Next month') || - withinElement(menuContainerEl)?.queryByLabelText('Next valid month'); + + // TODO: date-picker test harnesses https://jira.mongodb.org/browse/LG-4176 + const leftChevron = withinElement(menuContainerEl)?.queryByTestId( + 'lg-date_picker-menu-prev_month_button', + ); + const rightChevron = withinElement(menuContainerEl)?.queryByTestId( + 'lg-date_picker-menu-next_month_button', + ); const monthSelect = withinElement(menuContainerEl)?.queryByLabelText( 'Select month', { diff --git a/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenu.spec.tsx b/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenu.spec.tsx index 47104dc0b0..d619d04801 100644 --- a/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenu.spec.tsx +++ b/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenu.spec.tsx @@ -123,16 +123,32 @@ describe('packages/date-picker/date-picker-menu', () => { expect(grid).toHaveAttribute('aria-label', 'September 2023'); }); test('chevrons have aria labels', () => { - const { getByLabelText } = renderDatePickerMenu(); - const leftChevron = getByLabelText('Previous month'); - const rightChevron = getByLabelText('Next month'); + const { getByTestId } = renderDatePickerMenu(); + const leftChevron = getByTestId('lg-date_picker-menu-prev_month_button'); + const rightChevron = getByTestId('lg-date_picker-menu-next_month_button'); expect(leftChevron).toBeInTheDocument(); expect(rightChevron).toBeInTheDocument(); + expect(leftChevron).toHaveAttribute( + 'aria-label', + expect.stringContaining('Previous month'), + ); + expect(rightChevron).toHaveAttribute( + 'aria-label', + expect.stringContaining('Next month'), + ); }); test('select menu triggers have aria labels', () => { const { monthSelect, yearSelect } = renderDatePickerMenu(); expect(monthSelect).toBeInTheDocument(); expect(yearSelect).toBeInTheDocument(); + expect(monthSelect).toHaveAttribute( + 'aria-label', + expect.stringContaining('Select month'), + ); + expect(yearSelect).toHaveAttribute( + 'aria-label', + expect.stringContaining('Select year'), + ); }); test('select menus have correct values', () => { const { monthSelect, yearSelect } = renderDatePickerMenu(); diff --git a/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuHeader/DatePickerMenuHeader.tsx b/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuHeader/DatePickerMenuHeader.tsx index 3b9663c3df..fad629432c 100644 --- a/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuHeader/DatePickerMenuHeader.tsx +++ b/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuHeader/DatePickerMenuHeader.tsx @@ -1,9 +1,14 @@ import React, { forwardRef, MouseEventHandler } from 'react'; -import { isSameUTCMonth, setUTCMonth } from '@leafygreen-ui/date-utils'; +import { + getMonthName, + isSameUTCMonth, + setUTCMonth, +} from '@leafygreen-ui/date-utils'; import { SupportedLocales } from '@leafygreen-ui/date-utils'; import { Icon } from '@leafygreen-ui/icon'; import { IconButton } from '@leafygreen-ui/icon-button'; +import { isDefined } from '@leafygreen-ui/lib'; import { useSharedDatePickerContext } from '../../../shared/context'; import { useDatePickerContext } from '../../DatePickerContext'; @@ -42,6 +47,12 @@ export const DatePickerMenuHeader = forwardRef< const isIsoFormat = locale === SupportedLocales.ISO_8601; + const formatMonth = (date: Date) => { + const monthName = getMonthName(date.getUTCMonth(), locale); + const year = date.getUTCFullYear().toString(); + return `${monthName.long} ${year}`; + }; + /** * If the month is not in range and is not the last valid month * e.g. @@ -63,6 +74,48 @@ export const DatePickerMenuHeader = forwardRef< return !isDateInRange && !isOnLastValidMonth; }; + /** + * Given a direction (left/right), computes the nearest valid adjacent month + * + * @example + * max: new Date(Date.UTC(2038, Month.January, 19)); + * current month date: new Date(Date.UTC(2038, Month.March, 19)); + * `left` chevron will change the month back to January 2038 + * + * @example + * min: new Date(Date.UTC(1970, Month.January, 1)); + * current month date: new Date(Date.UTC(1969, Month.November, 19)); + * "right" chevron will change the month back to January 1970 + */ + const getNewMonth = (dir: 'left' | 'right'): Date => { + if (isMonthInvalid(dir)) { + const closestValidDate = dir === 'left' ? max : min; + const newMonthIndex = closestValidDate.getUTCMonth(); + const newMonth = setUTCMonth(closestValidDate, newMonthIndex); + return newMonth; + } else { + const increment = dir === 'left' ? -1 : 1; + const newMonthIndex = month.getUTCMonth() + increment; + const newMonth = setUTCMonth(month, newMonthIndex); + return newMonth; + } + }; + + const getChevronButtonLabel = (dir: 'left' | 'right') => { + const dirLabel = dir === 'left' ? 'Previous' : 'Next'; + const isNewMonthInvalid = isMonthInvalid(dir); + const newMonth = getNewMonth(dir); + const newMonthString = formatMonth(newMonth); + return [ + dirLabel, + isNewMonthInvalid ? 'valid' : undefined, + 'month', + `(${newMonthString})`, + ] + .filter(isDefined) + .join(' '); + }; + /** * Calls the `updateMonth` helper with the appropriate month when a Chevron is clicked */ @@ -71,35 +124,16 @@ export const DatePickerMenuHeader = forwardRef< e => { e.stopPropagation(); e.preventDefault(); - - // e.g. - // max: new Date(Date.UTC(2038, Month.January, 19)); - // current month date: new Date(Date.UTC(2038, Month.March, 19)); - // left chevron will change the month back to January 2038 - // e.g. - // min: new Date(Date.UTC(1970, Month.January, 1)); - // current month date: new Date(Date.UTC(1969, Month.November, 19)); - // right chevron will change the month back to January 1970 - if (isMonthInvalid(dir)) { - const closestValidDate = dir === 'left' ? max : min; - const newMonthIndex = closestValidDate.getUTCMonth(); - const newMonth = setUTCMonth(closestValidDate, newMonthIndex); - updateMonth(newMonth); - } else { - const increment = dir === 'left' ? -1 : 1; - const newMonthIndex = month.getUTCMonth() + increment; - const newMonth = setUTCMonth(month, newMonthIndex); - updateMonth(newMonth); - } + const newMonth = getNewMonth(dir); + updateMonth(newMonth); }; return (
@@ -120,7 +154,8 @@ export const DatePickerMenuHeader = forwardRef<
diff --git a/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuSelect/DatePickerMenuSelectMonth.tsx b/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuSelect/DatePickerMenuSelectMonth.tsx index 2d4b6e77db..8dbd1b9c27 100644 --- a/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuSelect/DatePickerMenuSelectMonth.tsx +++ b/packages/date-picker/src/DatePicker/DatePickerMenu/DatePickerMenuSelect/DatePickerMenuSelectMonth.tsx @@ -1,6 +1,10 @@ import React, { useCallback } from 'react'; -import { getLocaleMonths, setUTCMonth } from '@leafygreen-ui/date-utils'; +import { + getLocaleMonths, + getMonthName, + setUTCMonth, +} from '@leafygreen-ui/date-utils'; import { cx } from '@leafygreen-ui/emotion'; import { Option, Select } from '@leafygreen-ui/select'; @@ -40,16 +44,18 @@ export const DatePickerMenuSelectMonth = ({ updateMonth(newMonth); }; + const monthString = getMonthName(month.getUTCMonth(), locale); + return (