diff --git a/.changeset/README.md b/.changeset/README.md index 4f3b76b096..6c68425e59 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -6,3 +6,58 @@ find the full documentation for it [in our repository](https://github.com/change We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md) + +--- + +## Backstage UI Changesets + +For `@backstage/ui` changesets, use this format: + +```markdown +--- +'@backstage/ui': patch +--- + +Brief summary + +Optional description with code examples. + +**Migration:** + +Migration instructions (breaking changes only). + +**Affected components:** button, card +``` + +**Required:** + +- End with `**Affected components:**` + comma-separated component names +- For breaking changes: Add `**Migration:**` section +- No headings (`##`, `###`) inside - use bold markers + +**Examples:** + +```markdown +Fixed button hover state + +**Affected components:** button +``` + +````markdown +**BREAKING**: New Table API + +**Migration:** + +Update imports: + +```diff +- import { Table } from '@backstage/ui'; ++ import { Table, type ColumnConfig } from '@backstage/ui'; +``` +```` + +**Affected components:** table + +``` + +``` diff --git a/.changeset/action-filtering-feature.md b/.changeset/action-filtering-feature.md deleted file mode 100644 index a397d8d11a..0000000000 --- a/.changeset/action-filtering-feature.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -'@backstage/backend-defaults': minor ---- - -Added action filtering support with glob patterns and attribute constraints. - -The `ActionsService` now supports filtering actions based on configuration. This allows controlling which actions are exposed to consumers like the MCP backend. - -Configuration example: - -```yaml -backend: - actions: - pluginSources: - - catalog - - scaffolder - filter: - include: - - id: 'catalog:*' - attributes: - destructive: false - - id: 'scaffolder:*' - exclude: - - id: '*:delete-*' - - attributes: - readOnly: false -``` - -Filtering logic: - -- `include`: Rules for actions to include. Each rule can specify an `id` glob pattern and/or `attributes` constraints. An action must match at least one rule to be included. If no include rules are specified, all actions are included by default. -- `exclude`: Rules for actions to exclude. Takes precedence over include rules. -- Each rule combines `id` and `attributes` with AND logic (both must match if specified). diff --git a/.changeset/afraid-rats-invent.md b/.changeset/afraid-rats-invent.md new file mode 100644 index 0000000000..babf0e9d7a --- /dev/null +++ b/.changeset/afraid-rats-invent.md @@ -0,0 +1,14 @@ +--- +'@backstage/ui': patch +--- + +Added a new `FullPage` component that fills the remaining viewport height below the `PluginHeader`. + +```tsx + + + {/* content fills remaining height */} + +``` + +**Affected components:** FullPage diff --git a/.changeset/alert-remove-surface.md b/.changeset/alert-remove-surface.md new file mode 100644 index 0000000000..fcdd9303e5 --- /dev/null +++ b/.changeset/alert-remove-surface.md @@ -0,0 +1,14 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Alert no longer accepts a `surface` prop + +The Alert component's background is now driven entirely by its `status` prop. The `surface` prop has been removed. + +```diff +- ++ +``` + +**Affected components:** Alert diff --git a/.changeset/angry-hornets-clap.md b/.changeset/angry-hornets-clap.md new file mode 100644 index 0000000000..bd590e71d9 --- /dev/null +++ b/.changeset/angry-hornets-clap.md @@ -0,0 +1,30 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Removed gray scale tokens and renamed background surface tokens to neutral tokens + +The `--bui-gray-1` through `--bui-gray-8` tokens have been removed. The `--bui-bg-surface-*` and `--bui-bg-neutral-on-surface-*` tokens have been replaced by a unified `--bui-bg-neutral-*` scale. + +**Migration:** + +Replace surface tokens directly: + +```diff +- background: var(--bui-bg-surface-0); ++ background: var(--bui-bg-neutral-0); +``` + +Replace on-surface tokens shifted by +1: + +```diff +- background: var(--bui-bg-neutral-on-surface-0); ++ background: var(--bui-bg-neutral-1); +``` + +Replace gray tokens 1-4 with neutral equivalents (`--bui-gray-5` through `--bui-gray-8` have no direct replacement): + +```diff +- background: var(--bui-gray-1); ++ background: var(--bui-bg-neutral-1); +``` diff --git a/.changeset/api-override-conflict-error-defaults.md b/.changeset/api-override-conflict-error-defaults.md new file mode 100644 index 0000000000..703f76fb6e --- /dev/null +++ b/.changeset/api-override-conflict-error-defaults.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': minor +--- + +**BREAKING**: The `API_FACTORY_CONFLICT` warning is now treated as an error and will prevent the app from starting. diff --git a/.changeset/api-override-conflict-error.md b/.changeset/api-override-conflict-error.md new file mode 100644 index 0000000000..ed88452ccf --- /dev/null +++ b/.changeset/api-override-conflict-error.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': minor +--- + +**BREAKING**: Updated the behavior of the new API override logic to reject the override and block app startup instead of just logging a deprecation warning. diff --git a/.changeset/api-override-test-utils.md b/.changeset/api-override-test-utils.md new file mode 100644 index 0000000000..6ea60f9e1b --- /dev/null +++ b/.changeset/api-override-test-utils.md @@ -0,0 +1,41 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Added an `apis` option to `createExtensionTester`, `renderInTestApp`, and `renderTestApp` to override APIs when testing extensions. Use the `mockApis` helpers to create mock implementations: + +```typescript +import { identityApiRef } from '@backstage/frontend-plugin-api'; +import { mockApis } from '@backstage/frontend-test-utils'; + +// Override APIs in createExtensionTester +const tester = createExtensionTester(myExtension, { + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], +}); + +// Override APIs in renderInTestApp +renderInTestApp(, { + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], +}); + +// Override APIs in renderTestApp +renderTestApp({ + extensions: [myExtension], + apis: [ + [ + identityApiRef, + mockApis.identity({ userEntityRef: 'user:default/guest' }), + ], + ], +}); +``` diff --git a/.changeset/app-visualizer-subpages.md b/.changeset/app-visualizer-subpages.md new file mode 100644 index 0000000000..164dcaa126 --- /dev/null +++ b/.changeset/app-visualizer-subpages.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-visualizer': minor +--- + +Migrated to use `SubPageBlueprint` for tabbed navigation and added a copy-tree-as-JSON plugin header action using `PluginHeaderActionBlueprint`. The plugin now specifies a `title` and `icon`. diff --git a/.changeset/backend-process-listener-cleanup.md b/.changeset/backend-process-listener-cleanup.md new file mode 100644 index 0000000000..eb4dc52937 --- /dev/null +++ b/.changeset/backend-process-listener-cleanup.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Fixed memory leak by properly cleaning up process event listeners on backend shutdown. diff --git a/.changeset/backend-test-utils-create-service-mock.md b/.changeset/backend-test-utils-create-service-mock.md new file mode 100644 index 0000000000..b35b647fd8 --- /dev/null +++ b/.changeset/backend-test-utils-create-service-mock.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': minor +--- + +Added `createServiceMock`, a public utility for creating `ServiceMock` instances for custom service refs. This allows plugin authors to define mock creators for their own services following the same pattern as the built-in `mockServices` mocks. diff --git a/.changeset/backend-test-utils-extension-points.md b/.changeset/backend-test-utils-extension-points.md new file mode 100644 index 0000000000..fb26a282e3 --- /dev/null +++ b/.changeset/backend-test-utils-extension-points.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Updated `startTestBackend` to support factory-based extension points (v1.1 format) in addition to the existing direct implementation format. diff --git a/.changeset/beige-crabs-share.md b/.changeset/beige-crabs-share.md new file mode 100644 index 0000000000..6a1a42ffd8 --- /dev/null +++ b/.changeset/beige-crabs-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': minor +--- + +**BREAKING**: Removed the `TestApiRegistry` class, use `TestApiProvider` directly instead, storing reused APIs in a variable, e.g. `const apis = [...] as const`. diff --git a/.changeset/better-eggs-slide.md b/.changeset/better-eggs-slide.md new file mode 100644 index 0000000000..cf3856bb86 --- /dev/null +++ b/.changeset/better-eggs-slide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Allow setting optional description on group creation diff --git a/.changeset/better-ghosts-tell.md b/.changeset/better-ghosts-tell.md deleted file mode 100644 index af66b4fc6e..0000000000 --- a/.changeset/better-ghosts-tell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixes app background color on dark mode. diff --git a/.changeset/big-rabbits-think.md b/.changeset/big-rabbits-think.md new file mode 100644 index 0000000000..bd0ae32b5e --- /dev/null +++ b/.changeset/big-rabbits-think.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Fixes a bug where the `EntityListProvider` would not correctly hydrate query parameters if more than 20 were provided for the same key. diff --git a/.changeset/breezy-moles-sin.md b/.changeset/breezy-moles-sin.md deleted file mode 100644 index c2e7c36d80..0000000000 --- a/.changeset/breezy-moles-sin.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch -'@backstage/cli-common': patch ---- - -Move some of the symlink resolution to `isChildPath` diff --git a/.changeset/bright-pans-greet.md b/.changeset/bright-pans-greet.md new file mode 100644 index 0000000000..89ac3a8bc6 --- /dev/null +++ b/.changeset/bright-pans-greet.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-visualizer': patch +--- + +Bump react-aria-components to v1.14.0 diff --git a/.changeset/bright-pumas-cut.md b/.changeset/bright-pumas-cut.md deleted file mode 100644 index 3cb4d7d0b5..0000000000 --- a/.changeset/bright-pumas-cut.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-app-backend': patch -'@backstage/plugin-app-node': patch ---- - -Updated plugin metadata. diff --git a/.changeset/brown-grapes-fold.md b/.changeset/brown-grapes-fold.md new file mode 100644 index 0000000000..d8ab0a23f3 --- /dev/null +++ b/.changeset/brown-grapes-fold.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-auth0-provider': patch +--- + +Add support for organizational invites in auth0 strategy diff --git a/.changeset/brown-grapes-fry.md b/.changeset/brown-grapes-fry.md deleted file mode 100644 index c2fd1fbe2a..0000000000 --- a/.changeset/brown-grapes-fry.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Added indeterminate state support to the Checkbox component for handling partial selection scenarios like table header checkboxes. - -Affected components: Checkbox diff --git a/.changeset/bumpy-carrots-stare.md b/.changeset/bumpy-carrots-stare.md deleted file mode 100644 index 9b3c39fcf1..0000000000 --- a/.changeset/bumpy-carrots-stare.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Added missing `aria-label` attributes to `SearchField` components in `Select`, `MenuAutocomplete`, and `MenuAutocompleteListbox` to fix accessibility warnings. - -Affected components: Select, MenuAutocomplete, MenuAutocompleteListbox diff --git a/.changeset/busy-hairs-attend.md b/.changeset/busy-hairs-attend.md deleted file mode 100644 index 523396a5d4..0000000000 --- a/.changeset/busy-hairs-attend.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Updated documentation for `createApiRef` to clarify the role of the ID in specifying the owning plugin of an API. diff --git a/.changeset/calm-knives-glow.md b/.changeset/calm-knives-glow.md new file mode 100644 index 0000000000..f2e1d5805c --- /dev/null +++ b/.changeset/calm-knives-glow.md @@ -0,0 +1,6 @@ +--- +'@backstage/integration': patch +'@backstage/plugin-catalog-backend-module-azure': patch +--- + +Added support for `{org}.visualstudio.com` domains used by Azure DevOps diff --git a/.changeset/catalog-graph-test-deps.md b/.changeset/catalog-graph-test-deps.md new file mode 100644 index 0000000000..3c47695696 --- /dev/null +++ b/.changeset/catalog-graph-test-deps.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-org': patch +--- + +Added `@backstage/frontend-test-utils` dev dependency. diff --git a/.changeset/catalog-modules-update-extension-points.md b/.changeset/catalog-modules-update-extension-points.md new file mode 100644 index 0000000000..dba229482f --- /dev/null +++ b/.changeset/catalog-modules-update-extension-points.md @@ -0,0 +1,23 @@ +--- +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/plugin-catalog-backend-module-azure': patch +'@backstage/plugin-catalog-backend-module-backstage-openapi': patch +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch +'@backstage/plugin-catalog-backend-module-bitbucket-server': patch +'@backstage/plugin-catalog-backend-module-gcp': patch +'@backstage/plugin-catalog-backend-module-gerrit': patch +'@backstage/plugin-catalog-backend-module-gitea': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-catalog-backend-module-github-org': patch +'@backstage/plugin-catalog-backend-module-gitlab': patch +'@backstage/plugin-catalog-backend-module-gitlab-org': patch +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +'@backstage/plugin-catalog-backend-module-ldap': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +'@backstage/plugin-catalog-backend-module-openapi': patch +'@backstage/plugin-catalog-backend-module-puppetdb': patch +'@backstage/plugin-catalog-backend-module-scaffolder-entity-model': patch +--- + +Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. diff --git a/.changeset/catalog-node-extension-points-to-stable.md b/.changeset/catalog-node-extension-points-to-stable.md new file mode 100644 index 0000000000..7e8e22dfc5 --- /dev/null +++ b/.changeset/catalog-node-extension-points-to-stable.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-catalog-node': minor +--- + +Promoted stable catalog extension points from alpha to main export. The following extension points are now exported from `@backstage/plugin-catalog-node` instead of `@backstage/plugin-catalog-node/alpha`: + +- `catalogLocationsExtensionPoint` and `CatalogLocationsExtensionPoint` +- `catalogProcessingExtensionPoint` and `CatalogProcessingExtensionPoint` +- `catalogAnalysisExtensionPoint` and `CatalogAnalysisExtensionPoint` + +The old alpha exports for these extension points are now deprecated with `@deprecated` markers pointing to the new stable exports. Please update your imports from `@backstage/plugin-catalog-node/alpha` to `@backstage/plugin-catalog-node`. + +Note: The `catalogModelExtensionPoint`, `catalogPermissionExtensionPoint`, and related types remain in alpha. diff --git a/.changeset/catalog-node-use-create-service-mock.md b/.changeset/catalog-node-use-create-service-mock.md new file mode 100644 index 0000000000..5032484ebb --- /dev/null +++ b/.changeset/catalog-node-use-create-service-mock.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-node': patch +--- + +Updated `catalogServiceMock.mock` to use `createServiceMock` from `@backstage/backend-test-utils`, replacing the internal copy of `simpleMock`. Added `@backstage/backend-test-utils` as an optional peer dependency. diff --git a/.changeset/catalog-provider-module-failures.md b/.changeset/catalog-provider-module-failures.md new file mode 100644 index 0000000000..c7cb5a3571 --- /dev/null +++ b/.changeset/catalog-provider-module-failures.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Failures to connect catalog providers are now attributed to the module that provided the failing provider. This means that such failures will be reported as module startup failures rather than a failure to start the catalog plugin, and will therefore respect `onPluginModuleBootFailure` configuration instead. diff --git a/.changeset/catalog-react-catalog-api-mock-shorthand.md b/.changeset/catalog-react-catalog-api-mock-shorthand.md new file mode 100644 index 0000000000..0761dffa0e --- /dev/null +++ b/.changeset/catalog-react-catalog-api-mock-shorthand.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +The `catalogApiMock` test utility now returns a `MockWithApiFactory`, allowing it to be passed directly to test utilities like `renderTestApp` and `TestApiProvider` without needing the `[catalogApiRef, catalogApiMock()]` tuple. diff --git a/.changeset/chatty-tips-stop.md b/.changeset/chatty-tips-stop.md new file mode 100644 index 0000000000..7b4c435ffc --- /dev/null +++ b/.changeset/chatty-tips-stop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-auth0-provider': minor +--- + +feat: Added organization option to authorization params of the strategy diff --git a/.changeset/chilly-geese-carry.md b/.changeset/chilly-geese-carry.md deleted file mode 100644 index 5d19251360..0000000000 --- a/.changeset/chilly-geese-carry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-msgraph': patch ---- - -Changed the logger level from 'warning' to 'debug' when we are unable to load the user's photo. diff --git a/.changeset/chilly-maps-heal.md b/.changeset/chilly-maps-heal.md deleted file mode 100644 index f4f583c9ee..0000000000 --- a/.changeset/chilly-maps-heal.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixes disabled state in primary and secondary buttons in Backstage UI. diff --git a/.changeset/chubby-suns-film.md b/.changeset/chubby-suns-film.md new file mode 100644 index 0000000000..440d65d707 --- /dev/null +++ b/.changeset/chubby-suns-film.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Fixed dark theme `--bui-fg-secondary` and `--bui-fg-disabled` tokens using black-based `oklch(0% ...)` instead of white-based `oklch(100% ...)`, making secondary and disabled text visible on dark backgrounds. diff --git a/.changeset/clean-bags-occur.md b/.changeset/clean-bags-occur.md new file mode 100644 index 0000000000..ea4ae25aa6 --- /dev/null +++ b/.changeset/clean-bags-occur.md @@ -0,0 +1,94 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Replaced `Surface` / `onSurface` system with new provider/consumer background system + +The old `Surface` type (`'0'`–`'3'`, `'auto'`) and its associated props (`surface`, `onSurface`) have been replaced by a provider/consumer `bg` architecture. + +**Types:** + +- `ContainerBg` — `'neutral-1'` | `'neutral-2'` | `'neutral-3'` | `'danger'` | `'warning'` | `'success'` +- `ProviderBg` — `ContainerBg | 'neutral-auto'` + +Consumer components (e.g. Button) inherit the parent's `bg` via `data-on-bg`, and CSS handles the visual step-up. See "Neutral level capping" below for details on how levels are bounded. + +**Hooks:** + +- `useBgProvider(bg?)` — for provider components. Returns `{ bg: undefined }` when no `bg` is given (transparent). Supports `'neutral-auto'` to auto-increment from the parent context. +- `useBgConsumer()` — for consumer components. Returns the parent container's `bg` unchanged. + +**Component roles:** + +- **Provider-only** (Box, Flex, Grid): set `data-bg`, wrap children in `BgProvider`. **Transparent by default** — they do _not_ auto-increment; pass `bg="neutral-auto"` explicitly if you want automatic neutral stepping. +- **Consumer-only** (Button, ButtonIcon, ButtonLink): set `data-on-bg`, inherit the parent container's `bg` unchanged. +- **Provider + Consumer** (Card): sets both `data-bg` and `data-on-bg`, wraps children. Card passes `bg="neutral-auto"` to its inner Box, so it auto-increments from the parent context. + +**Neutral level capping:** + +Provider components cap at `neutral-3`. There is no `neutral-4` prop value. The `neutral-4` level exists only in consumer component CSS — for example, a Button sitting on a `neutral-3` surface uses `neutral-4` tokens internally via `data-on-bg`. + +**Migration:** + +Rename the `surface` prop to `bg` on provider components and update values: + +```diff +- ++ + +- ++ + +- ++ + +- ++ +``` + +Remove `onSurface` from consumer components — they now always inherit from the parent container: + +```diff +- + ); + + case 'type': + return ; + + case 'default': + return '-'; + + case 'description': + return description ? ( + {description} + ) : null; + + case 'responsive': + return {spacingGroup.responsive ? 'Yes' : 'No'}; + + default: + return null; + } + }; + + return ( + <> + + {columns.map(col => ( + + {renderCell(col.key)} + + ))} + + {isExpanded && ( + + +
+
+ {/* Padding Column */} + {paddingProps.length > 0 && ( +
+
Padding
+
+ {paddingProps.map(prop => ( +
+
+ {prop.name} +
+
+ {prop.description} +
+ {prop.default && ( +
+ Default: {prop.default} +
+ )} +
+ ))} +
+
+ )} + + {/* Margin Column */} + {marginProps.length > 0 && ( +
+
Margin
+
+ {marginProps.map(prop => ( +
+
+ {prop.name} +
+
+ {prop.description} +
+ {prop.default && ( +
+ Default: {prop.default} +
+ )} +
+ ))} +
+
+ )} +
+
+
+
+ )} + + ); +}; diff --git a/docs-ui/src/components/PropsTable/SpacingPopup.module.css b/docs-ui/src/components/PropsTable/SpacingPopup.module.css new file mode 100644 index 0000000000..892bc14bc6 --- /dev/null +++ b/docs-ui/src/components/PropsTable/SpacingPopup.module.css @@ -0,0 +1,77 @@ +.button { + display: inline-flex; + align-items: center; + font-family: monospace; + font-size: 13px; + border-radius: 6px; + padding: 0px 8px; + height: 24px; + margin-right: 4px; + background-color: #f0f0f0; + color: #5d5d5d; + cursor: pointer; + border: none; + outline: none; + box-shadow: none; + transition: background-color 150ms ease; +} + +.button:hover { + background-color: #e0e0e0; +} + +[data-theme-mode='dark'] .button { + background-color: #2c2c2c; + color: #fff; +} + +[data-theme-mode='dark'] .button:hover { + background-color: #3c3c3c; +} + +.popover { + border: 1px solid var(--border); + box-shadow: 0 8px 20px rgba(0 0 0 / 0.1); + border-radius: 6px; + background: var(--bg); + color: var(--primary); + outline: none; + transition: transform 200ms, opacity 200ms; + padding: 1rem; + margin-top: 6px; + max-width: 400px; + --origin: translateY(-8px); + + &[data-entering], + &[data-exiting] { + transform: var(--origin); + opacity: 0; + } +} + +.arrow svg { + display: block; + fill: var(--bg); + stroke: var(--border); + stroke-width: 1px; + transform: rotate(180deg); +} + +.title { + font-size: 14px; + font-weight: 500; + margin-bottom: 0.75rem; +} + +.grid { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + margin-bottom: 0.75rem; +} + +.note { + font-size: 12px; + color: var(--text-secondary); + font-style: italic; +} diff --git a/docs-ui/src/components/PropsTable/SpacingPopup.tsx b/docs-ui/src/components/PropsTable/SpacingPopup.tsx new file mode 100644 index 0000000000..9515ec4e5a --- /dev/null +++ b/docs-ui/src/components/PropsTable/SpacingPopup.tsx @@ -0,0 +1,47 @@ +import { Chip } from '../Chip'; +import { + Button, + Dialog, + DialogTrigger, + OverlayArrow, + Popover, +} from 'react-aria-components'; +import styles from './SpacingPopup.module.css'; + +interface SpacingPopupProps { + values: string[]; +} + +export const SpacingPopup = ({ values }: SpacingPopupProps) => { + // Display abbreviated list: first, second, ..., last + const firstValue = values[0]; + const secondValue = values[1]; + const lastValue = values[values.length - 1]; + + return ( +
+ {firstValue} + {secondValue} + + + + + + + + + +
All spacing values
+
+ {values.map(value => ( + {value} + ))} +
+
Also accepts custom string values
+
+
+
+ {lastValue} +
+ ); +}; diff --git a/docs-ui/src/components/ReactAriaLink/ReactAriaLink.tsx b/docs-ui/src/components/ReactAriaLink/ReactAriaLink.tsx new file mode 100644 index 0000000000..37656c482d --- /dev/null +++ b/docs-ui/src/components/ReactAriaLink/ReactAriaLink.tsx @@ -0,0 +1,26 @@ +import styles from './styles.module.css'; + +interface ReactAriaLinkProps { + /** The React Aria component name (e.g., "Cell", "Table") */ + component: string; + /** The documentation URL */ + href: string; +} + +/** + * Displays a standardized note linking to React Aria documentation. + * + * Usage: + * + */ +export function ReactAriaLink({ component, href }: ReactAriaLinkProps) { + return ( +

+ Inherits all{' '} + + React Aria {component} + {' '} + props. +

+ ); +} diff --git a/docs-ui/src/components/ReactAriaLink/index.ts b/docs-ui/src/components/ReactAriaLink/index.ts new file mode 100644 index 0000000000..8fa7b70e73 --- /dev/null +++ b/docs-ui/src/components/ReactAriaLink/index.ts @@ -0,0 +1 @@ +export { ReactAriaLink } from './ReactAriaLink'; diff --git a/docs-ui/src/components/ReactAriaLink/styles.module.css b/docs-ui/src/components/ReactAriaLink/styles.module.css new file mode 100644 index 0000000000..b74436fee3 --- /dev/null +++ b/docs-ui/src/components/ReactAriaLink/styles.module.css @@ -0,0 +1,15 @@ +.note { + font-size: 1rem; + color: var(--secondary); + margin-top: 0.5rem; + margin-bottom: 1rem; +} + +.note a { + color: var(--link); + text-decoration: none; +} + +.note a:hover { + text-decoration: underline; +} diff --git a/docs-ui/src/components/Snippet/client.tsx b/docs-ui/src/components/Snippet/client.tsx index 04284c3249..2041218a12 100644 --- a/docs-ui/src/components/Snippet/client.tsx +++ b/docs-ui/src/components/Snippet/client.tsx @@ -12,6 +12,7 @@ interface SnippetProps { py?: number; open?: boolean; height?: string | number; + layout?: 'stacked' | 'side-by-side'; } export const SnippetClient = ({ @@ -22,9 +23,28 @@ export const SnippetClient = ({ py = 2, open = false, height = 'auto', + layout = 'stacked', }: SnippetProps) => { const [isOpen, setIsOpen] = useState(open); + if (layout === 'side-by-side') { + return ( +
+
+
{codeContent}
+
+
+
+ {preview} +
+
+
+ ); + } + return ( { return ( ); }; diff --git a/docs-ui/src/components/Snippet/styles.module.css b/docs-ui/src/components/Snippet/styles.module.css index ed5bd22def..c6270670a7 100644 --- a/docs-ui/src/components/Snippet/styles.module.css +++ b/docs-ui/src/components/Snippet/styles.module.css @@ -7,7 +7,7 @@ .preview { border-radius: 8px; box-shadow: inset 0 0 0 1px var(--border); - background-color: var(--bui-bg-surface-0); + background-color: var(--bui-bg-app); padding: 1px; position: relative; } @@ -60,3 +60,43 @@ opacity: 1; } } + +/* Side-by-side layout */ +.sideBySide { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + align-items: stretch; +} + +.sideBySideCode { + min-width: 0; + overflow: auto; + display: flex; + flex-direction: column; +} + +.codeWrapper { + flex: 1; + display: flex; + flex-direction: column; +} + +.codeWrapper > div { + flex: 1; + margin-bottom: 0; +} + +.sideBySidePreview { + border-radius: 8px; + box-shadow: inset 0 0 0 1px var(--border); + background-color: var(--bui-bg-app); + padding: 1px; + min-width: 0; + display: flex; + flex-direction: column; +} + +.sideBySidePreview > .previewContent { + flex: 1; +} diff --git a/docs-ui/src/components/Table/Table.tsx b/docs-ui/src/components/Table/Table.tsx index 4d0e48dbce..a43bcd766a 100644 --- a/docs-ui/src/components/Table/Table.tsx +++ b/docs-ui/src/components/Table/Table.tsx @@ -45,12 +45,14 @@ export const Row = ({ children }: { children: ReactNode }) => { export const Cell = ({ children, style, + colSpan, }: { children: ReactNode; style?: CSSProperties; + colSpan?: number; }) => { return ( - + {children} ); diff --git a/docs-ui/src/components/TableOfContents/TableOfContents.tsx b/docs-ui/src/components/TableOfContents/TableOfContents.tsx index d18265415b..429d677b80 100644 --- a/docs-ui/src/components/TableOfContents/TableOfContents.tsx +++ b/docs-ui/src/components/TableOfContents/TableOfContents.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useLayoutEffect } from 'react'; import { usePathname } from 'next/navigation'; import styles from './TableOfContents.module.css'; @@ -17,6 +17,29 @@ export function TableOfContents() { const [indicatorHeight, setIndicatorHeight] = useState(0); const pathname = usePathname(); + // Update indicator position when activeId changes + useLayoutEffect(() => { + if (!activeId) return; + + // Use requestAnimationFrame to defer setState call + const rafId = requestAnimationFrame(() => { + const activeElement = document.querySelector( + `[data-toc-id="${activeId}"]`, + ) as HTMLElement; + if (activeElement) { + const list = activeElement.closest('ul'); + if (list) { + const listRect = list.getBoundingClientRect(); + const elementRect = activeElement.getBoundingClientRect(); + setIndicatorTop(elementRect.top - listRect.top); + setIndicatorHeight(elementRect.height); + } + } + }); + + return () => cancelAnimationFrame(rafId); + }, [activeId, headings]); + useEffect(() => { // Extract all H2 and H3 headings from the document const elements = Array.from( @@ -29,18 +52,6 @@ export function TableOfContents() { level: parseInt(element.tagName.substring(1)), })); - setHeadings(headingData); - - // Set initial active heading (first visible heading or first heading) - if (headingData.length > 0) { - const viewportTop = window.scrollY + 100; // offset for header - const visibleHeading = elements.find(element => { - const rect = element.getBoundingClientRect(); - return rect.top + window.scrollY >= viewportTop - 200; - }); - setActiveId(visibleHeading?.id || headingData[0].id); - } - // Set up IntersectionObserver to track visible headings const observerOptions = { rootMargin: '-80px 0px -80% 0px', @@ -62,29 +73,26 @@ export function TableOfContents() { elements.forEach(element => observer.observe(element)); + // Initialize headings and active ID after observer is set up + requestAnimationFrame(() => { + setHeadings(headingData); + + // Set initial active heading (first visible heading or first heading) + if (headingData.length > 0) { + const viewportTop = window.scrollY + 100; // offset for header + const visibleHeading = elements.find(element => { + const rect = element.getBoundingClientRect(); + return rect.top + window.scrollY >= viewportTop - 200; + }); + setActiveId(visibleHeading?.id || headingData[0].id); + } + }); + return () => { elements.forEach(element => observer.unobserve(element)); }; }, [pathname]); - // Update indicator position when activeId changes - useEffect(() => { - if (activeId) { - const activeElement = document.querySelector( - `[data-toc-id="${activeId}"]`, - ) as HTMLElement; - if (activeElement) { - const list = activeElement.closest('ul'); - if (list) { - const listRect = list.getBoundingClientRect(); - const elementRect = activeElement.getBoundingClientRect(); - setIndicatorTop(elementRect.top - listRect.top); - setIndicatorHeight(elementRect.height); - } - } - } - }, [activeId, headings]); - const handleClick = (id: string) => { const element = document.getElementById(id); if (element) { diff --git a/docs-ui/src/components/Theming/index.tsx b/docs-ui/src/components/Theming/index.tsx index d06f51348b..9f67c3a111 100644 --- a/docs-ui/src/components/Theming/index.tsx +++ b/docs-ui/src/components/Theming/index.tsx @@ -1,11 +1,16 @@ -import { MDXRemote } from 'next-mdx-remote-client/rsc'; -import { formattedMDXComponents } from '@/mdx-components'; -import type { - ComponentDefinition, - DataAttributeValues, -} from '../../../../packages/ui/src/types'; +'use client'; -export function Theming({ definition }: { definition: ComponentDefinition }) { +import { formattedMDXComponents } from '@/mdx-components'; +import type { DataAttributeValues } from '../../../../packages/ui/src/types'; + +interface ThemingProps { + definition: { + classNames: Record; + dataAttributes?: Record; + }; +} + +export function Theming({ definition }: ThemingProps) { const classNames = definition.classNames; const dataAttributes = definition.dataAttributes; @@ -33,15 +38,28 @@ export function Theming({ definition }: { definition: ComponentDefinition }) { ...Object.values(classNames).slice(1), ]; + // Use the same styled components from MDX, with fallbacks to HTML elements + const H2 = formattedMDXComponents.h2 || 'h2'; + const P = formattedMDXComponents.p || 'p'; + const Ul = formattedMDXComponents.ul || 'ul'; + const Li = formattedMDXComponents.li || 'li'; + const Code = formattedMDXComponents.code || 'code'; + return ( - `- \`${selector}\``).join('\n')} - `} - /> +
+

Theming

+

+ Our theming system is based on a mix between CSS classes, CSS variables + and data attributes. If you want to customise this component, you can + use one of these class names below. +

+
    + {classNamesArray.map((selector, index) => ( +
  • + {selector} +
  • + ))} +
+
); } diff --git a/docs-ui/src/components/Toolbar/Toolbar.module.css b/docs-ui/src/components/Toolbar/Toolbar.module.css index 581fc599b3..4f067bd7bd 100644 --- a/docs-ui/src/components/Toolbar/Toolbar.module.css +++ b/docs-ui/src/components/Toolbar/Toolbar.module.css @@ -10,12 +10,10 @@ } } -.breadcrumb { +.left { display: flex; align-items: center; gap: 0.5rem; - font-size: 0.875rem; - font-weight: 500; } .logoMobile { @@ -26,39 +24,6 @@ } } -.breadcrumbDesktop { - display: none; - - @media (min-width: 768px) { - display: flex; - align-items: center; - gap: 0.5rem; - } -} - -.breadcrumbLink { - color: var(--secondary); - text-decoration: none; - cursor: pointer; - - &:hover { - color: var(--primary); - text-decoration: underline; - transition: color 0.2s ease-in-out; - text-decoration-thickness: 1px; - text-underline-offset: 4px; - } -} - -.breadcrumbSeparator { - color: var(--secondary); - flex-shrink: 0; -} - -.breadcrumbCurrent { - color: var(--primary); -} - .actions { display: none; @@ -119,6 +84,51 @@ } } +.searchButton { + display: none; + align-items: center; + gap: 6px; + background-color: var(--bg); + border: 1px solid var(--border); + border-radius: 32px; + padding-inline: 16px 12px; + min-width: 180px; + color: var(--secondary); + font-size: 0.8125rem; + font-weight: 500; + height: 32px; + cursor: pointer; + font-family: inherit; + white-space: nowrap; + + &:hover { + background-color: var(--action); + color: var(--primary); + transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; + } + + @media (min-width: 768px) { + display: flex; + } +} + +.searchLabel { + display: inline; + flex: 1; + text-align: left; +} + +.searchKbd { + display: inline; + font-family: inherit; + font-size: 0.6875rem; + font-weight: 500; + background-color: var(--action); + border-radius: 4px; + padding: 2px 5px; + color: var(--secondary); +} + .buttonGroup { display: flex; align-items: center; diff --git a/docs-ui/src/components/Toolbar/Toolbar.tsx b/docs-ui/src/components/Toolbar/Toolbar.tsx index e7defd5e8a..4a071bf45e 100644 --- a/docs-ui/src/components/Toolbar/Toolbar.tsx +++ b/docs-ui/src/components/Toolbar/Toolbar.tsx @@ -1,10 +1,11 @@ 'use client'; +import { useState, useEffect } from 'react'; import { RiArrowDownSLine, - RiArrowRightSLine, RiGithubLine, RiMoonLine, + RiSearchLine, RiSunLine, } from '@remixicon/react'; import { @@ -19,10 +20,8 @@ import { } from 'react-aria-components'; import styles from './Toolbar.module.css'; import { usePlayground } from '@/utils/playground-context'; -import { usePathname } from 'next/navigation'; -import Link from 'next/link'; -import { components, layoutComponents } from '@/utils/data'; import { Logo } from '@/components/Sidebar/Logo'; +import { CommandPalette } from '@/components/CommandPalette'; interface ToolbarProps { version: string; @@ -42,75 +41,36 @@ export const Toolbar = ({ version }: ToolbarProps) => { setSelectedThemeName, } = usePlayground(); - const pathname = usePathname(); + const [isCommandPaletteOpen, setIsCommandPaletteOpen] = useState(false); - // Determine breadcrumb content based on current path - const getBreadcrumb = () => { - const allComponents = [...components, ...layoutComponents]; + useEffect(() => { + const isMac = /mac(os|intosh)/i.test(navigator.userAgent); + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'k' && (isMac ? e.metaKey : e.ctrlKey)) { + e.preventDefault(); + setIsCommandPaletteOpen(prev => !prev); + } + }; - // Root page - if (pathname === '/') { - return { section: null, title: 'Getting Started' }; - } - - // Components index page - if (pathname === '/components') { - return { section: null, title: 'Components' }; - } - - // Component detail pages - if (pathname?.startsWith('/components/')) { - const slug = pathname.split('/components/')[1]; - const component = allComponents.find(c => c.slug === slug); - return { - section: 'Components', - sectionLink: '/components', - title: component?.title || slug, - }; - } - - // Tokens page - if (pathname === '/tokens') { - return { section: null, title: 'Tokens' }; - } - - // Changelog page - if (pathname === '/changelog') { - return { section: null, title: 'Changelog' }; - } - - return { section: null, title: '' }; - }; - - const breadcrumb = getBreadcrumb(); + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, []); return (
-
+
-
- {breadcrumb.section && breadcrumb.sectionLink ? ( - <> - - {breadcrumb.section} - - - - {breadcrumb.title} - - - ) : ( - {breadcrumb.title} - )} -
+