@baseline-ui/mcp 1.2.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,4 @@
1
- #!/usr/bin/env node
2
- 'use strict';var mcp_js=require('@modelcontextprotocol/sdk/server/mcp.js'),stdio_js=require('@modelcontextprotocol/sdk/server/stdio.js'),zod=require('zod');/**
3
- * Copyright (c) 2023-2024 PSPDFKit GmbH. All rights reserved.
4
- *
5
- * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW
6
- * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT.
7
- * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES.
8
- * This notice may not be removed from this file.
9
- *
10
- */
11
- var o={BarChart:{id:"charts-barchart",breadcrumb:"Charts/BarChart",importStatement:`import { BarChart } from "@baseline-ui/charts";
1
+ 'use strict';var mcp_js=require('@modelcontextprotocol/sdk/server/mcp.js'),zod=require('zod');var t={BarChart:{id:"charts-barchart",breadcrumb:"Charts/BarChart",importStatement:`import { BarChart } from "@baseline-ui/charts";
12
2
  import { Box } from "@baseline-ui/core";`,description:"The `BarChart` component renders a responsive bar chart for categorical data. It is built on top of [Recharts](https://recharts.org/) and styled with Baseline UI design tokens so it picks up the active theme automatically.",documentation:`The \`BarChart\` component renders a responsive bar chart for categorical data. It is built on top of [Recharts](https://recharts.org/) and styled with Baseline UI design tokens so it picks up the active theme automatically.
13
3
 
14
4
  * Renders one or more bar series from a single dataset, keyed by \`xAxisDataKey\` and the per-bar \`dataKey\`.
@@ -992,7 +982,7 @@ export const AccordionWithDisabledItemsExample: React.FC<
992
982
  Omit<React.ComponentProps<typeof AccordionExample>, "disabledKeys">
993
983
  > = (props) => {
994
984
  return <AccordionExample {...props} disabledKeys={new Set(["item-1"])} />;
995
- };`},similarTo:[],figmaUrl:null},ActionButton:{id:"core-buttons-actionbutton",breadcrumb:"Core/Buttons/ActionButton",importStatement:'import { ActionButton, Menu, VariantViewer } from "@baseline-ui/core";',description:"`ActionButton` is a labeled button that triggers an action when activated by mouse, touch, or keyboard. Use it for primary user actions such as submitting a form, confirming a choice, or invoking a command.",documentation:'`ActionButton` is a labeled button that triggers an action when activated by mouse, touch, or keyboard. Use it for primary user actions such as submitting a form, confirming a choice, or invoking a command.\n\n* Mouse and touch event handling, and press state management\n* Keyboard focus management and cross browser normalization\n* Keyboard event support for Space and Enter keys\n\nYou can import the ActionButton component like so:\n\n```jsx\nimport { ActionButton } from "@baseline-ui/core";\n\nexport default function App() {\n return <ActionButton label="Click Me" />;\n}\n```\n\nIf you want to use a button that can be toggled on and off, you can use the `ToggleButton` component.\n\nThere are nine variants of the button: `primary`, `secondary`, `tertiary`, `popover`, `toolbar`, `ghost`, `success`, `warning` and `error`. The default variant is `primary`. You can change the variant by passing the `variant` prop.\n\n```jsx\n<ActionButton label="Primary" />\n<ActionButton label="Secondary" variant="secondary" />\n<ActionButton label="Tertiary" variant="tertiary" />\n<ActionButton label={"Toolbar"} variant={"toolbar"} />\n<ActionButton label="Ghost" variant="ghost" />\n<ActionButton label="Popover" variant="popover" />\n<ActionButton label="Success" variant="success" />\n<ActionButton label="Warning" variant="warning" />\n<ActionButton label="Error" variant="error" />\n```\n\nActionButton supports three sizes: `sm` (default), `md`, and `lg`. You can change the size by passing the `size` prop.\n\n```jsx\n<ActionButton label="Small (default)" size="sm" />\n<ActionButton label="Medium" size="md" />\n<ActionButton label="Large" size="lg" />\n```\n\nYou can disable a button by passing the `isDisabled` prop. This will disable all mouse, touch, and keyboard interactions.\n\n```jsx\n<ActionButton label="Primary" isDisabled />\n<ActionButton label="Secondary" variant="secondary" isDisabled />\n<ActionButton label="Tertiary" variant="tertiary" isDisabled />\n<ActionButton label="Ghost" variant="ghost" isDisabled />\n<ActionButton label="Toolbar" variant="toolbar" isDisabled />\n<ActionButton label="Popover" variant="popover" isDisabled />\n<ActionButton label="Success" variant="success" isDisabled />\n<ActionButton label="Warning" variant="warning" isDisabled />\n<ActionButton label="Error" variant="error" isDisabled />\n```\n\nYou can place an icon before or after the label by passing the `iconStart` or `iconEnd` props. Choose the icon entrypoint that matches your button size: `@baseline-ui/icons/16` for `sm`, `@baseline-ui/icons/20` for `md`, and `@baseline-ui/icons/24` for `lg`.\n\n```jsx\nimport { EllipseIcon } from "@baseline-ui/icons/16";\n\n<ActionButton label="Label" iconStart={EllipseIcon} />\n<ActionButton label="Label" iconEnd={EllipseIcon} />\n```\n\nYou can listen for events by passing the `onPress` prop. The `onPress` prop will fire when the button is activated by mouse, touch, or keyboard interactions.\n\n```jsx\n<ActionButton label="Click Me" onPress={() => alert("Hello World")} />\n```\n\n| Selector | Description |\n| -------------------- | -------------------------------------------------------------- |\n| \\[data-disabled] | Whether the button is disabled. |\n| \\[data-focused] | Whether the button is focused, either via a mouse or keyboard. |\n| \\[data-hovered] | Whether the button is currently hovered with a mouse. |\n| \\[data-focus-visible] | Whether the button is keyboard focused. |\n| \\[data-pressed] | Whether the button is currently pressed. |\n\n| Key | Function |\n| ------- | --------------------- |\n| `Space` | Activates the button. |\n| `Enter` | Activates the button. |',props:`interface ActionButtonProps {
985
+ };`},similarTo:[],figmaUrl:null},ActionButton:{id:"core-buttons-actionbutton",breadcrumb:"Core/Buttons/ActionButton",importStatement:'import { ActionButton, Menu, VariantViewer } from "@baseline-ui/core";',description:"`ActionButton` is a labeled button that triggers an action when activated by mouse, touch, or keyboard. Use it for primary user actions such as submitting a form, confirming a choice, or invoking a command.",documentation:'`ActionButton` is a labeled button that triggers an action when activated by mouse, touch, or keyboard. Use it for primary user actions such as submitting a form, confirming a choice, or invoking a command.\n\n* Mouse and touch event handling, and press state management\n* Keyboard focus management and cross browser normalization\n* Keyboard event support for Space and Enter keys\n\nYou can import the ActionButton component like so:\n\n```jsx\nimport { ActionButton } from "@baseline-ui/core";\n\nexport default function App() {\n return <ActionButton label="Click Me" />;\n}\n```\n\nIf you want to use a button that can be toggled on and off, you can use the `ToggleButton` component.\n\nThe button has six neutral variants: `primary`, `secondary`, `tertiary`, `popover`, `toolbar` and `ghost`. The default variant is `primary`. You can change the variant by passing the `variant` prop.\n\n```jsx\n<ActionButton label="Primary" />\n<ActionButton label="Secondary" variant="secondary" />\n<ActionButton label="Tertiary" variant="tertiary" />\n<ActionButton label={"Toolbar"} variant={"toolbar"} />\n<ActionButton label="Ghost" variant="ghost" />\n<ActionButton label="Popover" variant="popover" />\n```\n\n`success`, `warning` and `error` carry a support colour for actions whose outcome matters \u2014 confirming, acknowledging a risk, or deleting. Each comes in the same three emphasis levels as the neutral variants, so a support-coloured button can sit at whatever weight the surrounding layout calls for:\n\n* `{name}` \u2014 filled, for the primary action in its context\n* `{name}-secondary` \u2014 tinted fill, for a lower-emphasis action\n* `{name}-tertiary` \u2014 outline that fills with the support colour on hover and on keyboard focus\n\nWhen disabled, all three levels drop the support colour and render in the neutral disabled treatment.\n\n```jsx\n<ActionButton label="Success" variant="success" />\n<ActionButton label="Success" variant="success-secondary" />\n<ActionButton label="Success" variant="success-tertiary" />\n<ActionButton label="Warning" variant="warning" />\n<ActionButton label="Warning" variant="warning-secondary" />\n<ActionButton label="Warning" variant="warning-tertiary" />\n<ActionButton label="Error" variant="error" />\n<ActionButton label="Error" variant="error-secondary" />\n<ActionButton label="Error" variant="error-tertiary" />\n```\n\nActionButton supports three sizes: `sm` (default), `md`, and `lg`. You can change the size by passing the `size` prop.\n\n```jsx\n<ActionButton label="Small (default)" size="sm" />\n<ActionButton label="Medium" size="md" />\n<ActionButton label="Large" size="lg" />\n```\n\nYou can disable a button by passing the `isDisabled` prop. This will disable all mouse, touch, and keyboard interactions. The `-secondary` and `-tertiary` support variants are omitted below because they render identically to disabled `secondary` and `tertiary`.\n\n```jsx\n<ActionButton label="Primary" isDisabled />\n<ActionButton label="Secondary" variant="secondary" isDisabled />\n<ActionButton label="Tertiary" variant="tertiary" isDisabled />\n<ActionButton label="Ghost" variant="ghost" isDisabled />\n<ActionButton label="Toolbar" variant="toolbar" isDisabled />\n<ActionButton label="Popover" variant="popover" isDisabled />\n<ActionButton label="Success" variant="success" isDisabled />\n<ActionButton label="Warning" variant="warning" isDisabled />\n<ActionButton label="Error" variant="error" isDisabled />\n```\n\nYou can place an icon before or after the label by passing the `iconStart` or `iconEnd` props. Choose the icon entrypoint that matches your button size: `@baseline-ui/icons/16` for `sm`, `@baseline-ui/icons/20` for `md`, and `@baseline-ui/icons/24` for `lg`.\n\n```jsx\nimport { EllipseIcon } from "@baseline-ui/icons/16";\n\n<ActionButton label="Label" iconStart={EllipseIcon} />\n<ActionButton label="Label" iconEnd={EllipseIcon} />\n```\n\nYou can listen for events by passing the `onPress` prop. The `onPress` prop will fire when the button is activated by mouse, touch, or keyboard interactions.\n\n```jsx\n<ActionButton label="Click Me" onPress={() => alert("Hello World")} />\n```\n\n| Selector | Description |\n| -------------------- | -------------------------------------------------------------- |\n| \\[data-disabled] | Whether the button is disabled. |\n| \\[data-focused] | Whether the button is focused, either via a mouse or keyboard. |\n| \\[data-hovered] | Whether the button is currently hovered with a mouse. |\n| \\[data-focus-visible] | Whether the button is keyboard focused. |\n| \\[data-pressed] | Whether the button is currently pressed. |\n\n| Key | Function |\n| ------- | --------------------- |\n| `Space` | Activates the button. |\n| `Enter` | Activates the button. |',props:`interface ActionButtonProps {
996
986
  /**
997
987
  * The button's class name.
998
988
  */
@@ -1010,6 +1000,13 @@ slot?: string | null
1010
1000
  /**
1011
1001
  * The button's variant.
1012
1002
  *
1003
+ * The \`success\`, \`warning\` and \`error\` variants carry a support colour. Each
1004
+ * comes in three emphasis levels, mirroring \`primary\`/\`secondary\`/\`tertiary\`:
1005
+ * the bare name is the filled treatment, \`-secondary\` is a tinted fill and
1006
+ * \`-tertiary\` is an outline that fills with the support colour on hover and
1007
+ * on keyboard focus. When disabled, all three drop the support colour and
1008
+ * render in the neutral disabled treatment.
1009
+ *
1013
1010
  * @default primary
1014
1011
  */
1015
1012
  variant?: | "primary"
@@ -1019,8 +1016,14 @@ variant?: | "primary"
1019
1016
  | "popover"
1020
1017
  | "toolbar"
1021
1018
  | "success"
1019
+ | "success-secondary"
1020
+ | "success-tertiary"
1022
1021
  | "warning"
1022
+ | "warning-secondary"
1023
+ | "warning-tertiary"
1023
1024
  | "error"
1025
+ | "error-secondary"
1026
+ | "error-tertiary"
1024
1027
  /**
1025
1028
  * The button's label.
1026
1029
  */
@@ -1207,6 +1210,114 @@ size?: "sm" | "md" | "lg"
1207
1210
  { variant: "error", size: "lg", iconStart: E24 },
1208
1211
  { variant: "error", size: "lg", iconEnd: E24 },
1209
1212
  ],
1213
+ "Success Secondary": [
1214
+ { variant: "success-secondary" },
1215
+ { variant: "success-secondary", isDisabled: true },
1216
+ { variant: "success-secondary", iconStart: EllipseIcon },
1217
+ { variant: "success-secondary", iconEnd: EllipseIcon },
1218
+ ],
1219
+ "Success Secondary Medium": [
1220
+ { variant: "success-secondary", size: "md" },
1221
+ { variant: "success-secondary", size: "md", isDisabled: true },
1222
+ { variant: "success-secondary", size: "md", iconStart: E20 },
1223
+ { variant: "success-secondary", size: "md", iconEnd: E20 },
1224
+ ],
1225
+ "Success Secondary Large": [
1226
+ { variant: "success-secondary", size: "lg" },
1227
+ { variant: "success-secondary", size: "lg", isDisabled: true },
1228
+ { variant: "success-secondary", size: "lg", iconStart: E24 },
1229
+ { variant: "success-secondary", size: "lg", iconEnd: E24 },
1230
+ ],
1231
+ "Success Tertiary": [
1232
+ { variant: "success-tertiary" },
1233
+ { variant: "success-tertiary", isDisabled: true },
1234
+ { variant: "success-tertiary", iconStart: EllipseIcon },
1235
+ { variant: "success-tertiary", iconEnd: EllipseIcon },
1236
+ ],
1237
+ "Success Tertiary Medium": [
1238
+ { variant: "success-tertiary", size: "md" },
1239
+ { variant: "success-tertiary", size: "md", isDisabled: true },
1240
+ { variant: "success-tertiary", size: "md", iconStart: E20 },
1241
+ { variant: "success-tertiary", size: "md", iconEnd: E20 },
1242
+ ],
1243
+ "Success Tertiary Large": [
1244
+ { variant: "success-tertiary", size: "lg" },
1245
+ { variant: "success-tertiary", size: "lg", isDisabled: true },
1246
+ { variant: "success-tertiary", size: "lg", iconStart: E24 },
1247
+ { variant: "success-tertiary", size: "lg", iconEnd: E24 },
1248
+ ],
1249
+ "Warning Secondary": [
1250
+ { variant: "warning-secondary" },
1251
+ { variant: "warning-secondary", isDisabled: true },
1252
+ { variant: "warning-secondary", iconStart: EllipseIcon },
1253
+ { variant: "warning-secondary", iconEnd: EllipseIcon },
1254
+ ],
1255
+ "Warning Secondary Medium": [
1256
+ { variant: "warning-secondary", size: "md" },
1257
+ { variant: "warning-secondary", size: "md", isDisabled: true },
1258
+ { variant: "warning-secondary", size: "md", iconStart: E20 },
1259
+ { variant: "warning-secondary", size: "md", iconEnd: E20 },
1260
+ ],
1261
+ "Warning Secondary Large": [
1262
+ { variant: "warning-secondary", size: "lg" },
1263
+ { variant: "warning-secondary", size: "lg", isDisabled: true },
1264
+ { variant: "warning-secondary", size: "lg", iconStart: E24 },
1265
+ { variant: "warning-secondary", size: "lg", iconEnd: E24 },
1266
+ ],
1267
+ "Warning Tertiary": [
1268
+ { variant: "warning-tertiary" },
1269
+ { variant: "warning-tertiary", isDisabled: true },
1270
+ { variant: "warning-tertiary", iconStart: EllipseIcon },
1271
+ { variant: "warning-tertiary", iconEnd: EllipseIcon },
1272
+ ],
1273
+ "Warning Tertiary Medium": [
1274
+ { variant: "warning-tertiary", size: "md" },
1275
+ { variant: "warning-tertiary", size: "md", isDisabled: true },
1276
+ { variant: "warning-tertiary", size: "md", iconStart: E20 },
1277
+ { variant: "warning-tertiary", size: "md", iconEnd: E20 },
1278
+ ],
1279
+ "Warning Tertiary Large": [
1280
+ { variant: "warning-tertiary", size: "lg" },
1281
+ { variant: "warning-tertiary", size: "lg", isDisabled: true },
1282
+ { variant: "warning-tertiary", size: "lg", iconStart: E24 },
1283
+ { variant: "warning-tertiary", size: "lg", iconEnd: E24 },
1284
+ ],
1285
+ "Error Secondary": [
1286
+ { variant: "error-secondary" },
1287
+ { variant: "error-secondary", isDisabled: true },
1288
+ { variant: "error-secondary", iconStart: EllipseIcon },
1289
+ { variant: "error-secondary", iconEnd: EllipseIcon },
1290
+ ],
1291
+ "Error Secondary Medium": [
1292
+ { variant: "error-secondary", size: "md" },
1293
+ { variant: "error-secondary", size: "md", isDisabled: true },
1294
+ { variant: "error-secondary", size: "md", iconStart: E20 },
1295
+ { variant: "error-secondary", size: "md", iconEnd: E20 },
1296
+ ],
1297
+ "Error Secondary Large": [
1298
+ { variant: "error-secondary", size: "lg" },
1299
+ { variant: "error-secondary", size: "lg", isDisabled: true },
1300
+ { variant: "error-secondary", size: "lg", iconStart: E24 },
1301
+ { variant: "error-secondary", size: "lg", iconEnd: E24 },
1302
+ ],
1303
+ "Error Tertiary": [
1304
+ { variant: "error-tertiary" },
1305
+ { variant: "error-tertiary", isDisabled: true },
1306
+ { variant: "error-tertiary", iconStart: EllipseIcon },
1307
+ { variant: "error-tertiary", iconEnd: EllipseIcon },
1308
+ ],
1309
+ "Error Tertiary Medium": [
1310
+ { variant: "error-tertiary", size: "md" },
1311
+ { variant: "error-tertiary", size: "md", isDisabled: true },
1312
+ { variant: "error-tertiary", size: "md", iconStart: E20 },
1313
+ { variant: "error-tertiary", size: "md", iconEnd: E20 },
1314
+ ],
1315
+ "Error Tertiary Large": [
1316
+ { variant: "error-tertiary", size: "lg" },
1317
+ { variant: "error-tertiary", size: "lg", isDisabled: true },
1318
+ { variant: "error-tertiary", size: "lg", iconStart: E24 },
1319
+ { variant: "error-tertiary", size: "lg", iconEnd: E24 },
1320
+ ],
1210
1321
  }}
1211
1322
  />
1212
1323
  );`},{id:"core-buttons-actionbutton--disabled",name:"Disabled",snippet:'const Disabled = () => <ActionButton onPress={fn()} label="Label" isDisabled />;'},{id:"core-buttons-actionbutton--with-icon",name:"With Icon",snippet:'const WithIcon = () => <ActionButton onPress={fn()} label="Label" iconStart={EllipseIcon} />;'},{id:"core-buttons-actionbutton--with-both-icons",name:"With Both Icons",snippet:`const WithBothIcons = () => <ActionButton
@@ -2219,7 +2330,7 @@ isPrimaryActionDisabled?: boolean
2219
2330
  * The props that are passed to the text input. If this is provided, a text
2220
2331
  * input will be displayed at the bottom of the dialog.
2221
2332
  */
2222
- textInputProps?: TextInputProps
2333
+ textInputProps?: SingleLineTextInputProps | MultiLineTextInputProps
2223
2334
  /**
2224
2335
  * The icon that is displayed at the top of the dialog. This is typically used
2225
2336
  * to display an icon that represents the type of alert that is being shown.
@@ -3827,7 +3938,7 @@ children: React.ReactNode
3827
3938
  "test": "cross-env BABEL_ENV=test jest",
3828
3939
  "test:e2e": "cross-env BABEL_ENV=test jest --testPathPattern=e2e --testPathIgnorePatterns='examples,/packages/components/,/packages/react/'"
3829
3940
  }
3830
- }\`}</Code>;`}],implementation:""},similarTo:[],figmaUrl:null},ColorInput:{id:"core-forms-colorinput",breadcrumb:"Core/Forms/ColorInput",importStatement:'import { ColorInput, CustomTriggerButton, IndeterminateExample } from "@baseline-ui/core";',description:"`ColorInput` is a form control that opens a popover with preset swatches and a custom picker so users can choose or define a color. Use it when a user needs to pick a fill, stroke, highlight, or any color value, with optional alpha and persisted custom colors.",documentation:'`ColorInput` is a form control that opens a popover with preset swatches and a custom picker so users can choose or define a color. Use it when a user needs to pick a fill, stroke, highlight, or any color value, with optional alpha and persisted custom colors.\n\n* Includes custom color picker with color area, hue slider, and optional alpha slider\n* Supports alpha channel\n* Exposed to screen readers using ARIA attributes\n* Supports keyboard, touch and mouse interaction\n* Supports disabled and indeterminate states\n* Supports HEX and RGB color modes\n* Persists custom colors in local storage\n* Supports lazy picker mode for adding custom colors without live updates\n\n```jsx\nimport { ColorInput } from "@baseline-ui/core";\n\nconst presets = [\n { label: "Red", color: "#ff0000" },\n { label: "Green", color: "#00ff00" },\n { label: "Blue", color: "#0000ff" },\n { label: "Yellow", color: "#ffff00" },\n { label: "Cyan", color: "#00ffff" },\n { label: "Magenta", color: "#ff00ff" },\n { label: "Black", color: "#000000" },\n { label: "White", color: "#ffffff" },\n { label: "Gray", color: "#808080" },\n { label: "Orange", color: "#ffa500" },\n { label: "Brown", color: "#a52a2a" },\n { label: "Purple", color: "#800080" },\n];\n\n<ColorInput presets={presets} label="Color" />;\n```\n\nBy default, the label is placed above the trigger. Use `labelPosition="start"` to place it inline.\n\n```jsx\n<ColorInput\n presets={presets}\n label="Label"\n labelPosition="start"\n defaultValue="#ff0000"\n/>\n```\n\nHide the visible color name text next to the swatch by setting `colorLabel={false}`.\n\n```jsx\n<ColorInput presets={presets} colorLabel={false} aria-label="Color" />\n```\n\nShow only the preset list without the custom color picker by setting `includePicker={false}`.\n\n```jsx\n<ColorInput presets={presets} includePicker={false} label="Color" />\n```\n\nShow only the custom color picker without any presets.\n\n```jsx\n<ColorInput presets={[]} label="Color" />\n```\n\nYou can enable the alpha channel in the color picker by setting the `allowAlpha` prop to `true`.\n\n```jsx\n<ColorInput presets={presets} allowAlpha />\n```\n\nDisable the alpha slider by setting `allowAlpha={false}`.\n\n```jsx\n<ColorInput presets={presets} allowAlpha={false} label="Color" />\n```\n\n```jsx\n<ColorInput presets={presets} isDisabled label="Color" />\n```\n\nThe `ColorInput` component supports an indeterminate state. This is useful when you want to show a loading state or an unknown state. This property is always controlled and only makes a visual difference. If set to true, the color input trigger button will show "Indeterminate" as the color name. Apart from this, all the other functionality will work as expected.\n\n```jsx\n<ColorInput isIndeterminate presets={presets} />\n```\n\nBy default, you can add colors picked from the picker to the list of custom color presets. These are persisted in local storage under the key specified by the `storePickedColorKey` prop (defaults to `"baselinePickedColor"`). To use a separate storage key per instance, pass a unique value:\n\n```jsx\n<ColorInput storePickedColorKey="my-custom-key" />\n```\n\nBy default, you cannot unset the color. You can enable the ability to unset the color by setting the `allowRemoval` prop to `true`.\n\n```jsx\n<ColorInput presets={presets} allowRemoval />\n```\n\nYou can set the default color by setting the `defaultValue` prop to a color value.\n\n```jsx\n<ColorInput presets={presets} defaultValue="#ff0000" />\n```\n\nYou can make the `ColorInput` component controlled by setting the `value` prop to a color value. You can use the `onChange` prop to update the value.\n\n```jsx\n<ColorInput presets={presets} value="#ff0000" onChange={console.log} />\n```\n\nYou can use the `renderTriggerButton` prop to render a custom trigger.\n\n```jsx\n<ColorInput\n label="label"\n renderTriggerButton={({ colorName, ref, triggerProps }) => (\n <ActionIconButton\n {...triggerProps}\n aria-label={typeof colorName === "string" ? colorName : "Color"}\n icon={EllipseIcon}\n ref={ref}\n aria-haspopup="true"\n />\n )}\n/>\n```\n\nYou can use the `pickerMode="lazy"` prop to render the color picker only to add a custom color to the list of custom color presets. This is useful when you want to prevent the `onChange` event from firing while the user is picking a color from the picker. In case of mobile the picker opens in a modal.\n\n```jsx\n<ColorInput presets={presets} pickerMode="lazy" aria-label="Color" />\n```\n\nThe following CSS class selectors and data attributes are available for styling:\n\n| Selector | Description |\n| -------------------------------------------- | ------------------------------------------------------------------------- |\n| `.BaselineUI-ColorInput-Trigger` | The outer wrapper containing the label and trigger button |\n| `.BaselineUI-ColorInputButton` | The trigger button |\n| `.BaselineUI-ColorInput-Popover` | The popover container |\n| `.BaselineUI-ColorInput-ColorArea` | The color area (saturation/lightness) |\n| `.BaselineUI-ColorInput-ColorAreaThumb` | The draggable thumb on the color area |\n| `.BaselineUI-ColorInput-ColorSlider` | The hue/alpha slider |\n| `.BaselineUI-ColorInput-ColorSliderThumb` | The draggable thumb on a slider |\n| `.BaselineUI-ColorInput-FieldInput` | The hex/RGB text input |\n| `.BaselineUI-ColorInput-Presets` | The preset color list |\n| `.BaselineUI-ColorInput-CustomColors` | The custom colors header |\n| `.BaselineUI-ColorInput-CustomColorsListBox` | The custom colors list |\n| `[data-disabled]` | Applied when the button is disabled |\n| `[data-hovered]` | Applied when the button is hovered |\n| `[data-pressed]` | Applied when the button is pressed |\n| `[data-focus-visible]` | Applied when the button has keyboard focus |\n| `[data-color-mode="hexa"]` | Applied to hex field input when alpha is enabled |\n| `[data-color-mode="hex"]` | Applied to hex field input when alpha is disabled |\n| `[data-color-mode="rgba"]` | Applied to RGB field inputs when alpha is enabled |\n| `[data-color-mode="rgb"]` | Applied to RGB field inputs when alpha is disabled |\n| `[data-channel]` | Applied to color sliders, value is the channel name (e.g. `hue`, `alpha`) |\n\n| Key | Description |\n| ---------- | -------------------------------------------------------------------- |\n| Enter | Opens the popover or selects the focused color if popover is open |\n| Space | Opens the popover or selects the focused color if popover is open |\n| Escape | Closes the popover if open |\n| ArrowRight | Moves focus to the next preset color in the list |\n| ArrowLeft | Moves focus to the previous preset color in the list |\n| Tab | Moves focus between the color area, sliders, fields, and preset list |\n\nThe following strings are used by the `ColorInput` component and can be overridden via `I18nProvider`:\n\n| Key | Default (en) |\n| ------------------------------ | ---------------- |\n| `bui.colorInput.addColor` | Add Color |\n| `bui.colorInput.removeColor` | Remove Color |\n| `bui.colorInput.customColors` | Custom Colors |\n| `bui.colorInput.noColor` | None |\n| `bui.colorInput.transparent` | Transparent |\n| `bui.colorInput.add` | Add |\n| `bui.colorInput.cancel` | Cancel |\n| `bui.colorInput.colorFormat` | Color Format |\n| `bui.colorInput.colorPresets` | Color Presets |\n| `bui.colorInput.newColor` | New Custom Color |\n| `bui.colorInput.indeterminate` | Indeterminate |\n\n> **Note:** `bui.colorInput.indeterminate` is not present in the bundled locale JSON files \u2014 it relies on its `defaultMessage` as fallback. Override it via `I18nProvider.messages` when you need a custom string for the indeterminate state.\n\n```jsx\nimport { I18nProvider, ColorInput } from "@baseline-ui/core";\n\n<I18nProvider\n locale="en"\n messages={{\n en: {\n "bui.colorInput.addColor": "Pick a Color",\n "bui.colorInput.cancel": "Dismiss",\n },\n }}\n>\n <ColorInput />\n</I18nProvider>;\n```\n\nThe `IconColorInput` component is a wrapper around the `ColorInput` component that allows you to render an icon next to the color input. This basically overrides the `renderTriggerButton` prop of the `ColorInput` component to\nprovide a predefined trigger button with an icon.\n\n```jsx\nimport { IconColorInput } from "@baseline-ui/core";\nimport { BorderColorIcon } from "@baseline-ui/icons/24";\n\n<IconColorInput icon={BorderColorIcon} aria-label="Color Picker" />;\n```\n\nYou can use the `variant` prop to change the appearance of the `IconColorInput` component. The `variant` prop accepts the following values: `standard` and `compact`.\n\n```jsx\n<IconColorInput icon={BorderColorIcon} aria-label="Color Picker" isDisabled />\n```\n\nThe `IconColorInput` component supports adding tooltip to the trigger button which is enabled by default. The tooltip will be the same as the `aria-label` of the trigger button. If you want to disable the tooltip, you can set the `tooltip` and `iconTooltip` props to `false`.\n\nThe `ColorSwatch` component is used to display a color swatch. The `ColorSwatch` component is used in the `ColorInput` component to display the selected color.\n\n```jsx\nimport { ColorSwatch } from "@baseline-ui/core";\n\n<ColorSwatch color="#ff0000" />;\n```',props:`interface ColorInputProps {
3941
+ }\`}</Code>;`}],implementation:""},similarTo:[],figmaUrl:null},ColorInput:{id:"core-forms-colorinput",breadcrumb:"Core/Forms/ColorInput",importStatement:'import { ColorInput, CustomTriggerButton, IndeterminateExample } from "@baseline-ui/core";',description:"`ColorInput` is a form control that opens a popover with preset swatches and a custom picker so users can choose or define a color. Use it when a user needs to pick a fill, stroke, highlight, or any color value, with optional alpha and persisted custom colors.",documentation:'`ColorInput` is a form control that opens a popover with preset swatches and a custom picker so users can choose or define a color. Use it when a user needs to pick a fill, stroke, highlight, or any color value, with optional alpha and persisted custom colors.\n\n* Includes custom color picker with color area, hue slider, and optional alpha slider\n* Supports alpha channel\n* Exposed to screen readers using ARIA attributes\n* Supports keyboard, touch and mouse interaction\n* Supports disabled and indeterminate states\n* Supports HEX and RGB color modes\n* Persists custom colors in local storage\n* Supports lazy picker mode for adding custom colors without live updates\n\n```jsx\nimport { ColorInput } from "@baseline-ui/core";\n\nconst presets = [\n { label: "Red", color: "#ff0000" },\n { label: "Green", color: "#00ff00" },\n { label: "Blue", color: "#0000ff" },\n { label: "Yellow", color: "#ffff00" },\n { label: "Cyan", color: "#00ffff" },\n { label: "Magenta", color: "#ff00ff" },\n { label: "Black", color: "#000000" },\n { label: "White", color: "#ffffff" },\n { label: "Gray", color: "#808080" },\n { label: "Orange", color: "#ffa500" },\n { label: "Brown", color: "#a52a2a" },\n { label: "Purple", color: "#800080" },\n];\n\n<ColorInput presets={presets} label="Color" />;\n```\n\nBy default, the label is placed above the trigger. Use `labelPosition="start"` to place it inline.\n\n```jsx\n<ColorInput\n presets={presets}\n label="Label"\n labelPosition="start"\n defaultValue="#ff0000"\n/>\n```\n\nHide the visible color name text next to the swatch by setting `colorLabel={false}`.\n\n```jsx\n<ColorInput presets={presets} colorLabel={false} aria-label="Color" />\n```\n\nShow only the preset list without the custom color picker by setting `includePicker={false}`.\n\n```jsx\n<ColorInput presets={presets} includePicker={false} label="Color" />\n```\n\nShow only the custom color picker without any presets.\n\n```jsx\n<ColorInput presets={[]} label="Color" />\n```\n\nYou can enable the alpha channel in the color picker by setting the `allowAlpha` prop to `true`.\n\n```jsx\n<ColorInput presets={presets} allowAlpha />\n```\n\nDisable the alpha slider by setting `allowAlpha={false}`.\n\n```jsx\n<ColorInput presets={presets} allowAlpha={false} label="Color" />\n```\n\n```jsx\n<ColorInput presets={presets} isDisabled label="Color" />\n```\n\nThe `ColorInput` component supports an indeterminate state. This is useful when you want to show a loading state or an unknown state. This property is always controlled and only makes a visual difference. If set to true, the color input trigger button will show "Indeterminate" as the color name. Apart from this, all the other functionality will work as expected.\n\n```jsx\n<ColorInput isIndeterminate presets={presets} />\n```\n\nBy default, you can add colors picked from the picker to the list of custom color presets. These are persisted in local storage under the key specified by the `storePickedColorKey` prop (defaults to `"baselinePickedColor"`). To use a separate storage key per instance, pass a unique value:\n\n```jsx\n<ColorInput storePickedColorKey="my-custom-key" />\n```\n\nBy default, you cannot unset the color. You can enable the ability to unset the color by setting the `allowRemoval` prop to `true`.\n\n```jsx\n<ColorInput presets={presets} allowRemoval />\n```\n\nYou can set the default color by setting the `defaultValue` prop to a color value.\n\n```jsx\n<ColorInput presets={presets} defaultValue="#ff0000" />\n```\n\nYou can make the `ColorInput` component controlled by setting the `value` prop to a color value. You can use the `onChange` prop to update the value.\n\n```jsx\n<ColorInput presets={presets} value="#ff0000" onChange={console.log} />\n```\n\nYou can use the `renderTriggerButton` prop to render a custom trigger.\n\n```jsx\n<ColorInput\n label="label"\n renderTriggerButton={({ colorName, ref, triggerProps }) => (\n <ActionIconButton\n {...triggerProps}\n aria-label={typeof colorName === "string" ? colorName : "Color"}\n icon={EllipseIcon}\n ref={ref}\n aria-haspopup="true"\n />\n )}\n/>\n```\n\nYou can use the `pickerMode="lazy"` prop to render the color picker only to add a custom color to the list of custom color presets. This is useful when you want to prevent the `onChange` event from firing while the user is picking a color from the picker. In case of mobile the picker opens in a modal.\n\n```jsx\n<ColorInput presets={presets} pickerMode="lazy" aria-label="Color" />\n```\n\nThe following CSS class selectors and data attributes are available for styling:\n\n| Selector | Description |\n| -------------------------------------------- | ------------------------------------------------------------------------- |\n| `.BaselineUI-ColorInput-Trigger` | The outer wrapper containing the label and trigger button |\n| `.BaselineUI-ColorInputButton` | The trigger button |\n| `.BaselineUI-ColorInput-Popover` | The popover container |\n| `.BaselineUI-ColorInput-ColorArea` | The color area (saturation/lightness) |\n| `.BaselineUI-ColorInput-ColorAreaThumb` | The draggable thumb on the color area |\n| `.BaselineUI-ColorInput-ColorSlider` | The hue/alpha slider |\n| `.BaselineUI-ColorInput-ColorSliderThumb` | The draggable thumb on a slider |\n| `.BaselineUI-ColorInput-FieldInput` | The hex/RGB text input |\n| `.BaselineUI-ColorInput-Presets` | The preset color list |\n| `.BaselineUI-ColorInput-CustomColors` | The custom colors header |\n| `.BaselineUI-ColorInput-CustomColorsListBox` | The custom colors list |\n| `[data-disabled]` | Applied when the button is disabled |\n| `[data-hovered]` | Applied when the button is hovered |\n| `[data-pressed]` | Applied when the button is pressed |\n| `[data-focus-visible]` | Applied when the button has keyboard focus |\n| `[data-color-mode="hexa"]` | Applied to hex field input when alpha is enabled |\n| `[data-color-mode="hex"]` | Applied to hex field input when alpha is disabled |\n| `[data-color-mode="rgba"]` | Applied to RGB field inputs when alpha is enabled |\n| `[data-color-mode="rgb"]` | Applied to RGB field inputs when alpha is disabled |\n| `[data-channel]` | Applied to color sliders, value is the channel name (e.g. `hue`, `alpha`) |\n\n| Key | Description |\n| ---------- | -------------------------------------------------------------------- |\n| Enter | Opens the popover or selects the focused color if popover is open |\n| Space | Opens the popover or selects the focused color if popover is open |\n| Escape | Closes the popover if open |\n| ArrowRight | Moves focus to the next preset color in the list |\n| ArrowLeft | Moves focus to the previous preset color in the list |\n| Tab | Moves focus between the color area, sliders, fields, and preset list |\n\nThe following strings are used by the `ColorInput` component and can be overridden via `I18nProvider`:\n\n| Key | Default (en) |\n| ------------------------------ | ---------------- |\n| `bui.colorInput.addColor` | Add Color |\n| `bui.colorInput.removeColor` | Remove Color |\n| `bui.colorInput.customColors` | Custom Colors |\n| `bui.colorInput.noColor` | None |\n| `bui.colorInput.transparent` | Transparent |\n| `bui.colorInput.add` | Add |\n| `bui.colorInput.cancel` | Cancel |\n| `bui.colorInput.colorFormat` | Color Format |\n| `bui.colorInput.colorPresets` | Color Presets |\n| `bui.colorInput.newColor` | New Custom Color |\n| `bui.colorInput.indeterminate` | Indeterminate |\n\n```jsx\nimport { I18nProvider, ColorInput } from "@baseline-ui/core";\n\n<I18nProvider\n locale="en"\n messages={{\n en: {\n "bui.colorInput.addColor": "Pick a Color",\n "bui.colorInput.cancel": "Dismiss",\n },\n }}\n>\n <ColorInput />\n</I18nProvider>;\n```\n\nThe `IconColorInput` component is a wrapper around the `ColorInput` component that allows you to render an icon next to the color input. This basically overrides the `renderTriggerButton` prop of the `ColorInput` component to\nprovide a predefined trigger button with an icon.\n\n```jsx\nimport { IconColorInput } from "@baseline-ui/core";\nimport { BorderColorIcon } from "@baseline-ui/icons/24";\n\n<IconColorInput icon={BorderColorIcon} aria-label="Color Picker" />;\n```\n\nYou can use the `variant` prop to change the appearance of the `IconColorInput` component. The `variant` prop accepts the following values: `standard` and `compact`.\n\n```jsx\n<IconColorInput icon={BorderColorIcon} aria-label="Color Picker" isDisabled />\n```\n\nThe `IconColorInput` component supports adding tooltip to the trigger button which is enabled by default. The tooltip will be the same as the `aria-label` of the trigger button. If you want to disable the tooltip, you can set the `tooltip` and `iconTooltip` props to `false`.\n\nThe `ColorSwatch` component is used to display a color swatch. The `ColorSwatch` component is used in the `ColorInput` component to display the selected color.\n\n```jsx\nimport { ColorSwatch } from "@baseline-ui/core";\n\n<ColorSwatch color="#ff0000" />;\n```',props:`interface ColorInputProps {
3831
3942
  /**
3832
3943
  * Whether the overlay is open by default (controlled).
3833
3944
  */
@@ -3948,19 +4059,19 @@ storePickedColorKey?: string
3948
4059
  /**
3949
4060
  * The label to show on the add color button.
3950
4061
  *
3951
- * @default Add color
4062
+ * @default Add Color
3952
4063
  */
3953
4064
  addColorButtonLabel?: string
3954
4065
  /**
3955
4066
  * The label to show on the remove color button.
3956
4067
  *
3957
- * @default Remove color
4068
+ * @default Remove Color
3958
4069
  */
3959
4070
  removeColorButtonLabel?: string
3960
4071
  /**
3961
4072
  * The label to show on the custom colors section.
3962
4073
  *
3963
- * @default Custom colors
4074
+ * @default Custom Colors
3964
4075
  */
3965
4076
  customColorsLabel?: string
3966
4077
  /**
@@ -4576,6 +4687,28 @@ By default the ComboBox current value cannot be set to values not included in it
4576
4687
  />
4577
4688
  \`\`\`
4578
4689
 
4690
+ \`renderOption\` replaces the entire contents of each option \u2014 including the default selection checkmark, so render your own affordance from \`isSelected\` if you need one. Selection and keyboard behavior are unchanged. \`optionClassName\` and \`optionStyle\` accept either a value or a function of the item and its render state, and apply to the option element itself.
4691
+
4692
+ Keep all three referentially stable. The collection caches its elements per option and rebuilds every one of them whenever one of these changes identity, so an inline arrow rebuilds the list on each render.
4693
+
4694
+ \`\`\`jsx
4695
+ const renderOption = React.useCallback(
4696
+ (item) => (
4697
+ <span>
4698
+ {item.label}
4699
+ <span style={{ color: color.text.secondary }}> \u2014 custom</span>
4700
+ </span>
4701
+ ),
4702
+ [],
4703
+ );
4704
+
4705
+ <ComboBox
4706
+ label="Custom option rendering"
4707
+ items={items}
4708
+ renderOption={renderOption}
4709
+ />;
4710
+ \`\`\`
4711
+
4579
4712
  The \`ComboBox\` input field filters visible items in the list to show only the ones that match the typed text. By default, only items that contain that text are shown. A custom filter function can be provided to modify this behavior.
4580
4713
 
4581
4714
  This example provides a filter that will only show items that match exactly the typed text, case sensitive.
@@ -4695,7 +4828,10 @@ inputStyle?: React.CSSProperties
4695
4828
  */
4696
4829
  inputClassName?: string
4697
4830
  /**
4698
- * Optional label for the toggle button
4831
+ * Accessible name for the toggle button. Not rendered visually.
4832
+ *
4833
+ * A blank string is treated as unset, keeping the built-in name. Setting this
4834
+ * overrides any \`aria-labelledby\` the toggle would otherwise inherit.
4699
4835
  */
4700
4836
  toggleLabel?: string
4701
4837
  filterItems?: boolean
@@ -4740,13 +4876,15 @@ filter?: ComboBoxStateOptions<ListItem>["defaultFilter"]
4740
4876
  */
4741
4877
  inputType?: "text" | "number"
4742
4878
  /**
4743
- * The minimum value for the input field. This is only valid when \`inputType\`
4744
- * is set to \`number\`.
4879
+ * The minimum value for the input field. Only applies when \`inputType="number"\`.
4880
+ *
4881
+ * Requires \`onValueChange\` or \`onInputSubmit\` to receive and apply the clamped value.
4745
4882
  */
4746
4883
  minValue?: number
4747
4884
  /**
4748
- * The maximum value for the input field. This is only valid when \`inputType\`
4749
- * is set to \`number\`.
4885
+ * The maximum value for the input field. Only applies when \`inputType="number"\`.
4886
+ *
4887
+ * Requires \`onValueChange\` or \`onInputSubmit\` to receive and apply the clamped value.
4750
4888
  */
4751
4889
  maxValue?: number
4752
4890
  /**
@@ -4754,7 +4892,17 @@ maxValue?: number
4754
4892
  */
4755
4893
  onInputSubmit?: (value: string) => void
4756
4894
  /**
4757
- * Listener to call when the selected value changes
4895
+ * Listener to call when the selected value changes.
4896
+ *
4897
+ * Also called when the selected item's label changes after selection (e.g. the consumer
4898
+ * relabels the item, or an async collection resolves), so the reported \`value\` converges on
4899
+ * the item's current label once the field settles. While the user has unsubmitted text, a
4900
+ * label-only change is deferred; it is reported on the next selection, submit, or blur that
4901
+ * settles the input back onto the selection. With a controlled \`inputValue\` the consumer owns
4902
+ * that sync, so a deferred label is only reported once they settle the input themselves.
4903
+ *
4904
+ * Do not relabel the selected item from inside this callback \u2014 the new label re-triggers the
4905
+ * listener, which loops unless the relabel is idempotent.
4758
4906
  */
4759
4907
  onValueChange?: (option: { value?: string; key?: string | null }) => void
4760
4908
  }`,stories:{usage:[{id:"core-forms-combobox--basic",name:"Basic",snippet:`const Basic = () => <ComboBox
@@ -4965,7 +5113,33 @@ onValueChange?: (option: { value?: string; key?: string | null }) => void
4965
5113
  defaultInputValue="42"
4966
5114
  allowsCustomValue
4967
5115
  inputType="number"
4968
- minValue={0} />;`},{id:"core-forms-combobox--with-custom-filter",name:"With Custom Filter",snippet:`const WithCustomFilter = () => <ComboBox
5116
+ minValue={0} />;`},{id:"core-forms-combobox--with-min-max-value",name:"With Min Max Value",snippet:`const WithMinMaxValue = () => {
5117
+ const [inputValue, setInputValue] = React.useState("");
5118
+
5119
+ return (
5120
+ <ComboBox
5121
+ items={[
5122
+ { id: "10", label: "10" },
5123
+ { id: "25", label: "25" },
5124
+ { id: "50", label: "50" },
5125
+ ]}
5126
+ aria-label="ComboBox"
5127
+ label="Type a number outside 10-50 range and blur"
5128
+ description="Values are clamped to minValue=10, maxValue=50 on blur"
5129
+ placeholder="Enter a number"
5130
+ allowsCustomValue
5131
+ inputType="number"
5132
+ minValue={10}
5133
+ maxValue={50}
5134
+ inputValue={inputValue}
5135
+ onInputChange={setInputValue}
5136
+ onValueChange={({ value }) => {
5137
+ if (value !== undefined) {
5138
+ setInputValue(value);
5139
+ }
5140
+ }} />
5141
+ );
5142
+ };`},{id:"core-forms-combobox--with-custom-filter",name:"With Custom Filter",snippet:`const WithCustomFilter = () => <ComboBox
4969
5143
  items={[
4970
5144
  { id: "pear", label: "Pear" },
4971
5145
  { id: "apple", label: "Apple" },
@@ -4978,7 +5152,17 @@ onValueChange?: (option: { value?: string; key?: string | null }) => void
4978
5152
  <AddItemDecorator>
4979
5153
  {(items) => <ComboBox label="Select a fruit" {...args} items={items} />}
4980
5154
  </AddItemDecorator>
4981
- );`},{id:"core-forms-combobox--with-tooltip",name:"With Tooltip",snippet:`const WithTooltip = () => <ComboBox
5155
+ );`},{id:"core-forms-combobox--with-render-option",name:"With Render Option",snippet:`const WithRenderOption = () => <ComboBox
5156
+ items={items}
5157
+ aria-label="ComboBox"
5158
+ label="Custom option rendering"
5159
+ placeholder="Placeholder"
5160
+ renderOption={(item) => (
5161
+ <span style={{ ...typography.label.md.regular }}>
5162
+ {item.label}
5163
+ <span style={{ color: color.text.secondary }}> \u2014 custom</span>
5164
+ </span>
5165
+ )} />;`},{id:"core-forms-combobox--with-tooltip",name:"With Tooltip",snippet:`const WithTooltip = () => <ComboBox
4982
5166
  items={items}
4983
5167
  aria-label="This is aria-label"
4984
5168
  tooltip
@@ -4996,6 +5180,8 @@ import { ComboBox } from "..";
4996
5180
  import { Box } from "../../Box";
4997
5181
 
4998
5182
  import type { ListItem, ListOption } from "../../shared/types/List";
5183
+ import type { ComboBoxProps } from "../ComboBox.types";
5184
+ import type { Key } from "react-aria";
4999
5185
 
5000
5186
  export const items = [
5001
5187
  { id: "pears", label: "Pears" },
@@ -5047,11 +5233,36 @@ export const itemsWithSections = [
5047
5233
  ] as ListItem[];
5048
5234
 
5049
5235
  export const ComboBoxExample: React.FC<
5050
- Omit<React.ComponentProps<typeof ComboBox>, "items">
5051
- > = (args) => {
5236
+ Omit<React.ComponentProps<typeof ComboBox>, "items"> & {
5237
+ items?: ListItem[];
5238
+ /** Must stay focusable \u2014 the outside-click test asserts focus lands on it. */
5239
+ withSiblingField?: boolean;
5240
+ onNonFocusingPress?: () => void;
5241
+ }
5242
+ > = ({
5243
+ items: comboBoxItems = items,
5244
+ withSiblingField,
5245
+ onNonFocusingPress,
5246
+ ...args
5247
+ }) => {
5052
5248
  return (
5053
5249
  <Box style={{ width: "100%", height: 200 }}>
5054
- <ComboBox items={items} aria-label="Combo box" {...args} />
5250
+ {/* Above the ComboBox: the popover opens downwards and would otherwise cover
5251
+ the spot a test means to click "outside" on. */}
5252
+ {withSiblingField ? <input aria-label="Sibling field" /> : null}
5253
+ {onNonFocusingPress ? (
5254
+ <button
5255
+ // Stands in for \`preventFocusOnPress\`: the press lands but the input
5256
+ // never blurs.
5257
+ onMouseDown={(e) => {
5258
+ e.preventDefault();
5259
+ }}
5260
+ onClick={onNonFocusingPress}
5261
+ >
5262
+ Toolbar action
5263
+ </button>
5264
+ ) : null}
5265
+ <ComboBox items={comboBoxItems} aria-label="Combo box" {...args} />
5055
5266
  </Box>
5056
5267
  );
5057
5268
  };
@@ -5076,6 +5287,155 @@ export const NumberComboBoxExample: React.FC<
5076
5287
  );
5077
5288
  };
5078
5289
 
5290
+ export const DynamicLabelComboBoxExample: React.FC<{
5291
+ onValueChange?: ComboBoxProps["onValueChange"];
5292
+ allowsCustomValue?: boolean;
5293
+ inputValue?: string;
5294
+ /** Relabels the selected item from "(selected)" to "(renamed)" when this exact text is typed. */
5295
+ relabelOnInput?: string;
5296
+ }> = ({ onValueChange, allowsCustomValue, inputValue, relabelOnInput }) => {
5297
+ const [value, setValue] = React.useState<Key | null>(null);
5298
+ // One-way: relabelling on every keystroke would feed the sync back into onInputChange and loop.
5299
+ const [isRelabelled, setIsRelabelled] = React.useState(false);
5300
+
5301
+ const suffix = isRelabelled ? "(renamed)" : "(selected)";
5302
+ const dynamicItems = items.map((item) =>
5303
+ item.id === value ? { ...item, label: \`\${item.label} \${suffix}\` } : item,
5304
+ );
5305
+
5306
+ return (
5307
+ <Box style={{ width: "100%", height: 200 }}>
5308
+ <ComboBox
5309
+ items={dynamicItems}
5310
+ aria-label="Combo box"
5311
+ value={value}
5312
+ onChange={setValue}
5313
+ onValueChange={onValueChange}
5314
+ allowsCustomValue={allowsCustomValue}
5315
+ inputValue={inputValue}
5316
+ onInputChange={(text) => {
5317
+ if (text === relabelOnInput) {
5318
+ setIsRelabelled(true);
5319
+ }
5320
+ }}
5321
+ />
5322
+ <span data-testid="relabel-probe">
5323
+ {dynamicItems.find((item) => item.id === value)?.label ?? ""}
5324
+ </span>
5325
+ </Box>
5326
+ );
5327
+ };
5328
+
5329
+ export const AsyncItemsComboBoxExample: React.FC<{
5330
+ initiallyLoaded?: boolean;
5331
+ onValueChange?: ComboBoxProps["onValueChange"];
5332
+ allowsCustomValue?: boolean;
5333
+ /** Unloads and reloads the items when this exact text is typed, without moving focus. */
5334
+ refreshOnInput?: string;
5335
+ }> = ({
5336
+ initiallyLoaded = true,
5337
+ onValueChange,
5338
+ allowsCustomValue,
5339
+ refreshOnInput,
5340
+ }) => {
5341
+ const [loadState, setLoadState] = React.useState<
5342
+ "loaded" | "unloaded" | "refreshing"
5343
+ >(initiallyLoaded ? "loaded" : "unloaded");
5344
+ const [refreshCount, setRefreshCount] = React.useState(0);
5345
+ const [value, setValue] = React.useState<Key | null>(
5346
+ initiallyLoaded ? null : "pears",
5347
+ );
5348
+ const isLoaded = loadState === "loaded";
5349
+
5350
+ // The unloaded collection must reach the DOM for a render before the items come back,
5351
+ // otherwise there is no transient for the refresh to be mistaken for a new selection.
5352
+ React.useEffect(() => {
5353
+ if (loadState === "refreshing") {
5354
+ setLoadState("loaded");
5355
+ setRefreshCount((count) => count + 1);
5356
+ }
5357
+ }, [loadState]);
5358
+
5359
+ return (
5360
+ <Box style={{ width: "100%", height: 200 }}>
5361
+ <ComboBox
5362
+ items={isLoaded ? items : []}
5363
+ aria-label="Combo box"
5364
+ value={value}
5365
+ onChange={setValue}
5366
+ onValueChange={onValueChange}
5367
+ allowsCustomValue={allowsCustomValue}
5368
+ onInputChange={(text) => {
5369
+ if (text === refreshOnInput) {
5370
+ setLoadState("refreshing");
5371
+ }
5372
+ }}
5373
+ />
5374
+ <button
5375
+ type="button"
5376
+ onClick={() => {
5377
+ setLoadState((state) => (state === "loaded" ? "unloaded" : "loaded"));
5378
+ }}
5379
+ >
5380
+ {isLoaded ? "Unload items" : "Load items"}
5381
+ </button>
5382
+ <span data-testid="refresh-probe">{refreshCount}</span>
5383
+ </Box>
5384
+ );
5385
+ };
5386
+
5387
+ export const LabelledByComboBoxExample: React.FC<
5388
+ Omit<React.ComponentProps<typeof ComboBox>, "items" | "aria-labelledby">
5389
+ > = (args) => {
5390
+ return (
5391
+ <Box style={{ width: "100%", height: 200 }}>
5392
+ <span id="external-label">External</span>
5393
+ <ComboBox items={items} {...args} aria-labelledby="external-label" />
5394
+ </Box>
5395
+ );
5396
+ };
5397
+
5398
+ export const CustomOptionComboBoxExample: React.FC = () => {
5399
+ return (
5400
+ <ComboBoxExample
5401
+ renderOption={(item) => <span>{item.label} \u2014 custom</span>}
5402
+ optionClassName={(item) => \`custom-option-\${item.id}\`}
5403
+ optionStyle={(_item, { isSelected }) => ({
5404
+ outline: isSelected
5405
+ ? "1px solid rgb(0, 128, 0)"
5406
+ : "1px solid rgb(255, 0, 0)",
5407
+ })}
5408
+ />
5409
+ );
5410
+ };
5411
+
5412
+ export const CountingOptionComboBoxExample: React.FC<{
5413
+ withSections?: boolean;
5414
+ }> = ({ withSections = false }) => {
5415
+ const [count, setCount] = React.useState(0);
5416
+
5417
+ return (
5418
+ <>
5419
+ <button
5420
+ onClick={() => {
5421
+ setCount(count + 1);
5422
+ }}
5423
+ >
5424
+ Increment
5425
+ </button>
5426
+ <ComboBoxExample
5427
+ items={withSections ? itemsWithSections : items}
5428
+ renderOption={(item) => (
5429
+ <span>
5430
+ {item.label} #{count}
5431
+ </span>
5432
+ )}
5433
+ optionClassName={() => \`count-\${count}\`}
5434
+ />
5435
+ </>
5436
+ );
5437
+ };
5438
+
5079
5439
  export const SectionComboBoxExample: React.FC = () => {
5080
5440
  return (
5081
5441
  <Box style={{ width: "100%", height: 300 }}>
@@ -7710,6 +8070,10 @@ children?: ReactNode
7710
8070
  * Enables the selection of directories instead of individual files.
7711
8071
  */
7712
8072
  acceptDirectory?: boolean
8073
+ /**
8074
+ * The id applied to the root element of the component.
8075
+ */
8076
+ id?: string
7713
8077
  /**
7714
8078
  * The className applied to the root element of the component.
7715
8079
  */
@@ -8005,6 +8369,7 @@ disabledKeys?: Iterable<string> | "all"
8005
8369
  );
8006
8370
  };`}],implementation:`import React from "react";
8007
8371
 
8372
+ import { ActionButton } from "../../ActionButton";
8008
8373
  import { FileList } from "../FileList";
8009
8374
 
8010
8375
  import type { FileListItem } from "../FileList.types";
@@ -8059,6 +8424,32 @@ export const manyItems: FileListItem[] = Array.from({ length: 12 }, (_, i) => ({
8059
8424
  name: \`attachment-\${i + 1}.pdf\`,
8060
8425
  }));
8061
8426
 
8427
+ // Swaps in a new array whose items carry new content, which is the only way a caller can
8428
+ // legitimately invalidate the memoised options.
8429
+ export const UploadingFileList: React.FC = () => {
8430
+ const [items, setItems] = React.useState<FileListItem[]>(loadingItems);
8431
+
8432
+ return (
8433
+ <>
8434
+ <ActionButton
8435
+ label="Finish upload"
8436
+ onPress={() => {
8437
+ setItems((prev) =>
8438
+ prev.map((item) => ({ ...item, isLoading: false })),
8439
+ );
8440
+ }}
8441
+ />
8442
+ <FileList
8443
+ aria-label="Uploaded files"
8444
+ items={items}
8445
+ onRemove={(id) => {
8446
+ setItems((prev) => prev.filter((item) => item.id !== id));
8447
+ }}
8448
+ />
8449
+ </>
8450
+ );
8451
+ };
8452
+
8062
8453
  interface BasicProps {
8063
8454
  onRemove?: (id: string) => void;
8064
8455
  disabledKeys?: Iterable<string> | "all";
@@ -9816,9 +10207,9 @@ children: React.ReactNode
9816
10207
  This example shows how to use \`I18nProvider\` to override the default locale.
9817
10208
 
9818
10209
  \`\`\`jsx
9819
- import { I18nProvider } from "@storybook/core";
10210
+ import { I18nProvider } from "@baseline-ui/core";
9820
10211
 
9821
- export const App = () => (
10212
+ export const Root = () => (
9822
10213
  <I18nProvider locale="en">
9823
10214
  <App />
9824
10215
  </I18nProvider>
@@ -9829,14 +10220,18 @@ The \`App\` component will receive the new locale through \`useLocale\`.
9829
10220
 
9830
10221
  \`\`\`jsx
9831
10222
  // App.js
9832
- import { useLocale } from "@storybook/core";
10223
+ import { useLocale } from "@baseline-ui/core";
9833
10224
 
9834
10225
  const App = () => {
9835
- const locale = useLocale();
10226
+ const { locale } = useLocale();
9836
10227
 
9837
10228
  return <div>Current locale: {locale}</div>;
9838
10229
  };
9839
- \`\`\``,props:`interface I18nProviderProps {
10230
+ \`\`\`
10231
+
10232
+ \`locale\` only switches date, number and collation formatting and text direction. To
10233
+ translate the strings Baseline UI renders, pass a catalog through \`messages\` \u2014 see
10234
+ [Internationalization](/?path=/docs/internationalization--docs#loading-a-translation-catalog).`,props:`interface I18nProviderProps {
9840
10235
  /**
9841
10236
  * Contents that should have the locale applied.
9842
10237
  */
@@ -9847,7 +10242,12 @@ children: React.ReactNode
9847
10242
  locale?: string
9848
10243
  messages?: LocalizedStrings
9849
10244
  /**
9850
- * @default true
10245
+ * Whether to \`console.warn\` about message ids that resolve through the
10246
+ * deprecated bare-id fallback or fall all the way through to their
10247
+ * \`defaultMessage\`. These warnings are development guidance, so they are off
10248
+ * in production builds \u2014 pass \`true\` to opt back in.
10249
+ *
10250
+ * @default process.env.NODE_ENV !== "production"
9851
10251
  */
9852
10252
  shouldLogMissingMessages?: boolean
9853
10253
  }`,stories:{usage:[{id:"core-utilities-i18nprovider--string-format",name:"String Format",snippet:`const StringFormat = ({ children }) => {
@@ -9987,7 +10387,6 @@ The following strings are used by the \`ImageDropZone\` component and can be ove
9987
10387
  | Key | Default (en) |
9988
10388
  | ------------------------------- | ------------ |
9989
10389
  | \`bui.imageDropZone.selectImage\` | Select Image |
9990
- | \`bui.imageDropZone.delete\` | Delete |
9991
10390
 
9992
10391
  \`\`\`jsx
9993
10392
  import { I18nProvider, ImageDropZone } from "@baseline-ui/core";
@@ -9997,7 +10396,6 @@ import { I18nProvider, ImageDropZone } from "@baseline-ui/core";
9997
10396
  messages={{
9998
10397
  en: {
9999
10398
  "bui.imageDropZone.selectImage": "Choose Image",
10000
- "bui.imageDropZone.delete": "Remove",
10001
10399
  },
10002
10400
  }}
10003
10401
  >
@@ -10420,6 +10818,8 @@ renderImage?: (
10420
10818
  imageContainerStyle?: React.CSSProperties
10421
10819
  /**
10422
10820
  * The dimensions of the image.
10821
+ *
10822
+ * @default { width: imageWidth, aspectRatio }
10423
10823
  */
10424
10824
  imageDimensions?: | Dimension
10425
10825
  | ((
@@ -10603,6 +11003,10 @@ import { Box } from "../../Box";
10603
11003
  import { I18nProvider } from "../../I18nProvider";
10604
11004
  import { NumberInput } from "../../NumberInput";
10605
11005
  import { Text } from "../../Text";
11006
+ import {
11007
+ VIRTUALIZER_LAYOUT_DEFAULT_OPTIONS,
11008
+ Virtualizer,
11009
+ } from "../../Virtualizer";
10606
11010
  import { ImageGallery, ImageSize } from "../ImageGallery";
10607
11011
  import { items } from "./data";
10608
11012
 
@@ -10878,6 +11282,35 @@ export function ScrollIntoViewImageGalleryExample() {
10878
11282
  );
10879
11283
  }
10880
11284
 
11285
+ // Counts \`renderImage\` calls so a spec can prove that a re-render which changes nothing
11286
+ // the gallery reads does not throw away the list box's cached options. \`Report\` publishes
11287
+ // the tally taken *before* its own render, so two clicks bracket exactly one re-render.
11288
+ export function RenderCountingImageGalleryExample() {
11289
+ const calls = React.useRef(0);
11290
+ const [reported, setReported] = React.useState(0);
11291
+
11292
+ const renderImage = React.useCallback<
11293
+ Exclude<React.ComponentProps<typeof ImageGallery>["renderImage"], undefined>
11294
+ >((item) => {
11295
+ calls.current += 1;
11296
+
11297
+ return <img src={item.data?.src} alt={item.data?.alt} />;
11298
+ }, []);
11299
+
11300
+ return (
11301
+ <Box display="flex" flexDirection="column" gap="lg">
11302
+ <ActionButton
11303
+ label="Report"
11304
+ onPress={() => {
11305
+ setReported(calls.current);
11306
+ }}
11307
+ />
11308
+ <Text data-testid="render-count">{reported.toString()}</Text>
11309
+ <ImageGallery defaultItems={items} renderImage={renderImage} />
11310
+ </Box>
11311
+ );
11312
+ }
11313
+
10881
11314
  export function RTLImageGalleryExample(
10882
11315
  props: Omit<React.ComponentProps<typeof ImageGallery>, "onDelete">,
10883
11316
  ) {
@@ -10886,6 +11319,43 @@ export function RTLImageGalleryExample(
10886
11319
  <ImageGallery {...props} />
10887
11320
  </I18nProvider>
10888
11321
  );
11322
+ }
11323
+
11324
+ /**
11325
+ * Driven by a window event rather than a button, because clicking anything would move focus
11326
+ * and end the keyboard drag by itself.
11327
+ */
11328
+ export function RerenderOnDemandImageGalleryExample({
11329
+ isVirtualized = false,
11330
+ ...props
11331
+ }: Omit<React.ComponentProps<typeof ImageGallery>, "onDelete"> & {
11332
+ isVirtualized?: boolean;
11333
+ }) {
11334
+ const [, rerender] = React.useReducer((tick: number) => tick + 1, 0);
11335
+
11336
+ React.useEffect(() => {
11337
+ const onRerender = () => {
11338
+ rerender();
11339
+ };
11340
+
11341
+ window.addEventListener("repro:rerender", onRerender);
11342
+
11343
+ return () => {
11344
+ window.removeEventListener("repro:rerender", onRerender);
11345
+ };
11346
+ }, []);
11347
+
11348
+ const gallery = <ImageGallery defaultItems={items} {...props} />;
11349
+
11350
+ if (!isVirtualized) {
11351
+ return gallery;
11352
+ }
11353
+
11354
+ return (
11355
+ <Virtualizer {...VIRTUALIZER_LAYOUT_DEFAULT_OPTIONS.IMAGE_GALLERY}>
11356
+ {gallery}
11357
+ </Virtualizer>
11358
+ );
10889
11359
  }`},similarTo:[],figmaUrl:null},InlineAlert:{id:"core-status-inlinealert",breadcrumb:"Core/Status/InlineAlert",importStatement:'import { InlineAlert } from "@baseline-ui/core";',description:"`InlineAlert` is a status message anchored next to related content, with success, warning, error, or info styling and optional action and dismiss buttons. Use it for persistent, in-context feedback that should stay visible without interrupting the user's flow.",documentation:`\`InlineAlert\` is a status message anchored next to related content, with success, warning, error, or info styling and optional action and dismiss buttons. Use it for persistent, in-context feedback that should stay visible without interrupting the user's flow.
10890
11360
 
10891
11361
  * Exposed to assistive technology with \`role="alert"\`.
@@ -12882,27 +13352,45 @@ console.log(greet('Markdown Maverick'));
12882
13352
  2. Selection options: The component can be configured to allow single, multiple, or no selection.
12883
13353
  3. Disabled items support: Certain menu items can be disabled, preventing user interaction.
12884
13354
  4. Sections support: Related items can be grouped into sections for better organization and navigation.
12885
- 5. Keyboard navigation: Users can navigate through the menu using their keyboard, including arrow keys, home/end, and page up/down.
12886
- 6. Auto-scroll: The menu automatically scrolls as the user navigates through it with their keyboard.
12887
- 7. Keyboard menu opening: The menu can be opened using the keyboard, and automatically focuses on the first or last item.
12888
- 8. Typeahead: Users can quickly navigate to a menu item by typing the first few letters of the item's label.
12889
- 9. Virtualized scrolling: For long lists, performance is improved by only rendering the visible items.
13355
+ 5. Submenus: Any option can nest an \`items\` array to open a cascading submenu of arbitrary depth.
13356
+ 6. Keyboard navigation: Users can navigate through the menu using their keyboard, including arrow keys, home/end, and page up/down.
13357
+ 7. Auto-scroll: The menu automatically scrolls as the user navigates through it with their keyboard.
13358
+ 8. Keyboard menu opening: The menu can be opened using the keyboard, and automatically focuses on the first or last item.
13359
+ 9. Typeahead: Users can quickly navigate to a menu item by typing the first few letters of the item's label.
13360
+ 10. Virtualized scrolling: For long lists, performance is improved by only rendering the visible items.
13361
+
13362
+ Pass the list of options via \`items\` and a \`triggerLabel\` for the default
13363
+ \`ActionButton\` trigger. \`Menu\` renders its own trigger and popover \u2014 it does not
13364
+ take children.
12890
13365
 
12891
13366
  \`\`\`jsx
12892
- import { ActionButton } from "../../utils";
12893
- import { Menu } from "@storybook/addon-docs/blocks";
13367
+ import { Menu } from "@baseline-ui/core";
12894
13368
 
12895
13369
  const items = [
12896
- {
12897
- label: "Cut",
12898
- id: "cut",
12899
- keyboardShortcut: "\u2318X",
12900
- },
13370
+ { id: "cut", label: "Cut", keyboardShortcut: "\u2318X" },
13371
+ { id: "copy", label: "Copy", keyboardShortcut: "\u2318C" },
13372
+ { id: "paste", label: "Paste", keyboardShortcut: "\u2318V" },
12901
13373
  ];
12902
13374
 
12903
- <Menu items={items}>
12904
- <ActionButton label="Label" />
12905
- </Menu>;
13375
+ export default function App() {
13376
+ return <Menu items={items} triggerLabel="Edit" onAction={(id) => {}} />;
13377
+ }
13378
+ \`\`\`
13379
+
13380
+ To render a custom trigger, pass \`renderTrigger\` instead of \`triggerLabel\`. If
13381
+ you need on/off state rather than a command list, use \`ToggleButton\`; to pick a
13382
+ value from a set with an always-visible field, use \`Select\`.
13383
+
13384
+ Each option accepts an optional leading \`icon\` (a \`@baseline-ui/icons\` component)
13385
+ and a \`keyboardShortcut\` string rendered at the trailing edge of the item.
13386
+
13387
+ \`\`\`jsx
13388
+ import { CopyIcon, TrashIcon } from "@baseline-ui/icons/16";
13389
+
13390
+ const items = [
13391
+ { id: "copy", label: "Copy", keyboardShortcut: "\u2318C", icon: CopyIcon },
13392
+ { id: "delete", label: "Delete", keyboardShortcut: "\u232B", icon: TrashIcon },
13393
+ ];
12906
13394
  \`\`\`
12907
13395
 
12908
13396
  The \`Menu\` component can be configured to allow single, multiple, or no selection. The default is no selection. To allow single selection, set the \`selectionMode\` prop to \`single\`. To allow multiple selection, set the \`selectionMode\` prop to \`multiple\`.
@@ -12930,9 +13418,93 @@ const items = [
12930
13418
  ];
12931
13419
  \`\`\`
12932
13420
 
12933
- The \`Menu\` component can be controlled or uncontrolled. When uncontrolled, the component manages its own state internally. When controlled, the state is managed by the parent component. To control the component, set the \`selectedKeys\` prop to an array of item IDs. The value of the selected keys must match the \`id\` prop of the items.
13421
+ Pass the object form of \`selectionMode\` to make individual sections independent
13422
+ selection groups. \`selectedKeys\` and \`defaultSelectedKeys\` are then keyed by
13423
+ section id, and \`onSelectionChange\` receives the changed section's selection
13424
+ plus its \`sectionId\`.
12934
13425
 
12935
- The \`Menu\` component can be controlled or uncontrolled. When uncontrolled, the component manages its own state internally. When controlled, the state is managed by the parent component. To control the component, set the \`isOpen\` prop to a boolean value.`,props:`interface MenuProps {
13426
+ \`\`\`tsx
13427
+ <Menu
13428
+ items={itemsWithSections}
13429
+ selectionMode={{ edit: "single", theme: "multiple" }}
13430
+ selectedKeys={{ edit: editKeys, theme: themeKeys }}
13431
+ onSelectionChange={(keys, meta) => {
13432
+ if (meta) setSelection((prev) => ({ ...prev, [meta.sectionId]: keys }));
13433
+ }}
13434
+ />
13435
+ \`\`\`
13436
+
13437
+ Menu-wide selection (a string \`selectionMode\`) and per-section selection are
13438
+ mutually exclusive within one menu. Enter activation closes the menu. Pointer
13439
+ activation closes single-select menus and keeps multi-select menus open, while
13440
+ Space activation keeps both selection modes open.
13441
+
13442
+ Give any option an \`items\` array to turn it into a submenu trigger. The option
13443
+ renders with a trailing chevron and opens a nested menu on hover or when
13444
+ activated with \`ArrowRight\` / \`Enter\`. Submenus nest arbitrarily and may
13445
+ themselves contain leaf options, sections, or deeper submenus.
13446
+
13447
+ \`\`\`jsx
13448
+ const items = [
13449
+ { id: "new", label: "New" },
13450
+ {
13451
+ id: "share",
13452
+ label: "Share",
13453
+ items: [
13454
+ { id: "email", label: "Email link" },
13455
+ { id: "sms", label: "SMS" },
13456
+ {
13457
+ id: "more",
13458
+ label: "More services",
13459
+ items: [
13460
+ { id: "slack", label: "Slack" },
13461
+ { id: "teams", label: "Teams" },
13462
+ ],
13463
+ },
13464
+ ],
13465
+ },
13466
+ ];
13467
+ \`\`\`
13468
+
13469
+ \`onAction\` fires for the activated leaf at any depth (with that leaf's \`id\`);
13470
+ activating a submenu trigger does not fire \`onAction\`. Selecting a leaf closes
13471
+ the whole cascade. A single flat \`disabledKeys\` array disables triggers and
13472
+ nested items at any level. In RTL locales the chevron and the submenu's opening
13473
+ direction flip automatically.
13474
+
13475
+ Selection can be controlled or uncontrolled. When uncontrolled, the component manages its own state internally via \`defaultSelectedKeys\`. To control it, pass \`selectedKeys\` and handle \`onSelectionChange\`. The keys must match the \`id\` of the items. See the [Single Selection](#single-selection) example above.
13476
+
13477
+ The open state can likewise be controlled or uncontrolled. When uncontrolled, use \`defaultOpen\`; to control it, pass \`isOpen\` and handle \`onOpenChange\`.
13478
+
13479
+ Each menu item exposes the standard React Aria interaction-state attributes, and Baseline UI adds class hooks on the trigger, popover, and item subparts.
13480
+
13481
+ | Selector | Description |
13482
+ | ---------------------------------- | ------------------------------------------------------- |
13483
+ | \\[data-focused] | Whether the item is focused. |
13484
+ | \\[data-focus-visible] | Whether the item is keyboard focused. |
13485
+ | \\[data-hovered] | Whether the item is currently hovered with a mouse. |
13486
+ | \\[data-pressed] | Whether the item is currently pressed. |
13487
+ | \\[data-selected] | Whether the item is selected (selection modes). |
13488
+ | \\[data-disabled] | Whether the item is disabled. |
13489
+ | \\[data-has-submenu] | Whether the item opens a submenu. |
13490
+ | \\[data-open] | Whether the item's submenu is open. |
13491
+ | .BaselineUI-Menu-Trigger | The trigger button. |
13492
+ | .BaselineUI-Menu-Popover | The root popover container. |
13493
+ | .BaselineUI-Menu-SubmenuPopover | A nested submenu popover container. |
13494
+ | .BaselineUI-Menu-SubmenuChevron | The trailing chevron on a submenu trigger. |
13495
+ | .BaselineUI-Menu-OptionDescription | The trailing text of an item (e.g. \`keyboardShortcut\`). |
13496
+
13497
+ | Key | Function |
13498
+ | --------------------- | ---------------------------------------------------------------------------------- |
13499
+ | \`Enter\` / \`Space\` | Opens the menu from the trigger; activates the focused item. |
13500
+ | \`ArrowDown\` | Opens the menu focusing the first item; moves focus down. |
13501
+ | \`ArrowUp\` | Opens the menu focusing the last item; moves focus up. |
13502
+ | \`Home\` / \`End\` | Moves focus to the first / last item. |
13503
+ | \`PageUp\` / \`PageDown\` | Moves focus up / down by a page. |
13504
+ | \`A\`\u2013\`Z\` | Typeahead \u2014 focuses the next item starting with the typed letters. |
13505
+ | \`ArrowRight\` | Opens the focused submenu (\`ArrowLeft\` in RTL). |
13506
+ | \`ArrowLeft\` | Closes the current submenu and returns focus to its trigger (\`ArrowRight\` in RTL). |
13507
+ | \`Escape\` | Closes the menu (or the current submenu level). |`,props:`interface MenuProps {
12936
13508
  /**
12937
13509
  * Whether the overlay is open by default (controlled).
12938
13510
  */
@@ -12964,39 +13536,18 @@ contentClassName?: string
12964
13536
  */
12965
13537
  itemClassName?: string
12966
13538
  /**
12967
- * A list of items to render in the menu. Items have the following shape:
12968
- *
12969
- * \`\`\`ts
12970
- * export type MenuOption = {
12971
- * id: string;
12972
- * label: string;
12973
- * keyboardShortcut?: string;
12974
- * icon?: React.FC<IconProps>;
12975
- * };
12976
- *
12977
- * export type MenuSection = {
12978
- * id: string;
12979
- * title?: string;
12980
- * type: "section";
12981
- * children: MenuOption[];
12982
- * };
13539
+ * A list of items to render in the menu. See \`MenuItem\`.
12983
13540
  *
12984
- * export type MenuItem = MenuOption | MenuSection;
12985
- * \`\`\`
13541
+ * A \`MenuSection\` may opt into independent selection by listing its id in the
13542
+ * object form of \`selectionMode\` (e.g. \`selectionMode={{ view: "single" }}\`).
12986
13543
  */
12987
13544
  items: MenuItem[]
12988
13545
  /**
12989
13546
  * A function that renders the trigger element of the component. The default
12990
13547
  * implementation renders an \`ActionButton\` component.
12991
- *
12992
- * \`\`\`tsx
12993
- * <Menu renderTrigger={({ buttonProps, ref }) => <ActionButton {...buttonProps} label="Label" ref={ref} />
12994
- * \`\`\`
12995
13548
  */
12996
13549
  renderTrigger?: (options: {
12997
- buttonProps: ActionButtonProps & {
12998
- isOpen: boolean;
12999
- };
13550
+ buttonProps: ActionButtonProps & { isOpen: boolean };
13000
13551
  ref: React.RefObject<HTMLButtonElement>;
13001
13552
  }) => React.ReactNode
13002
13553
  /**
@@ -13004,6 +13555,33 @@ renderTrigger?: (options: {
13004
13555
  * function that accepts a boolean indicating whether the menu is open.
13005
13556
  */
13006
13557
  triggerLabel?: React.ReactNode | ((isOpen: boolean) => React.ReactNode)
13558
+ /**
13559
+ * \`"single"\`/\`"multiple"\` select the whole menu. A record keyed by section id
13560
+ * makes each listed section an independent selection group.
13561
+ */
13562
+ selectionMode?: | "none"
13563
+ | "single"
13564
+ | "multiple"
13565
+ | Record<string, "single" | "multiple">
13566
+ /**
13567
+ * A menu-wide selection (\`"all"\` or an iterable of keys) or a per-section
13568
+ * record of \`Selection\`. The iterable form preserves the pre-existing
13569
+ * \`AriaMenuProps.selectedKeys\` shape (arrays stay assignable).
13570
+ */
13571
+ selectedKeys?: "all" | Iterable<Key> | Record<string, Selection>
13572
+ /**
13573
+ * Initial selection, in the same flat-or-per-section shape as \`selectedKeys\`.
13574
+ */
13575
+ defaultSelectedKeys?: | "all"
13576
+ | Iterable<Key>
13577
+ | Record<string, "all" | Iterable<Key>>
13578
+ /**
13579
+ * Fires on user selection. In per-section mode, the second argument carries
13580
+ * the \`sectionId\` of the section whose selection changed; it is \`undefined\`
13581
+ * in menu-wide mode. Typed optional so existing one-arg handlers stay
13582
+ * assignable.
13583
+ */
13584
+ onSelectionChange?: (keys: Selection, meta?: { sectionId: string }) => void
13007
13585
  placement?: any
13008
13586
  }`,stories:{usage:[{id:"core-collections-menu--basic",name:"Basic",snippet:'const Basic = () => <Menu triggerLabel="Menu Trigger" items={items} />;'},{id:"core-collections-menu--with-sections",name:"With Sections",snippet:'const WithSections = () => <Menu triggerLabel="Menu Trigger" items={itemsWithSections} />;'},{id:"core-collections-menu--controlled-open",name:"Controlled Open",snippet:'const ControlledOpen = () => <Menu triggerLabel="Menu Trigger" items={items} isOpen />;'},{id:"core-collections-menu--disabled",name:"Disabled",snippet:'const Disabled = () => <Menu triggerLabel="Menu Trigger" items={items} isDisabled />;'},{id:"core-collections-menu--with-disabled-keys",name:"With Disabled Keys",snippet:'const WithDisabledKeys = () => <Menu triggerLabel="Menu Trigger" items={items} disabledKeys={["paste"]} />;'},{id:"core-collections-menu--single-selection",name:"Single Selection",snippet:`const SingleSelection = () => {
13009
13587
  const [selectedKey, setSelectedKey] = React.useState(
@@ -13037,13 +13615,17 @@ placement?: any
13037
13615
  };`},{id:"core-collections-menu--with-selected-keys-controlled",name:"With Selected Keys Controlled",snippet:`const WithSelectedKeysControlled = () => <Menu
13038
13616
  triggerLabel="Menu Trigger"
13039
13617
  items={items}
13040
- selectedKeys={["light"]}
13618
+ selectedKeys={new Set(["light"])}
13041
13619
  selectionMode="single" />;`},{id:"core-collections-menu--long-menu",name:"Long Menu",snippet:`const LongMenu = () => <Menu
13042
13620
  triggerLabel="Menu Trigger"
13043
13621
  items={longListItems}
13044
13622
  selectionMode="single"
13045
13623
  defaultOpen
13046
- autoFocus="first" />;`},{id:"core-collections-menu--with-custom-trigger",name:"With Custom Trigger",snippet:`const WithCustomTrigger = () => <Menu
13624
+ autoFocus="first" />;`},{id:"core-collections-menu--with-submenu",name:"With Submenu",snippet:'const WithSubmenu = () => <Menu triggerLabel="Menu Trigger" items={itemsWithSubmenu} defaultOpen />;'},{id:"core-collections-menu--with-icons",name:"With Icons",snippet:'const WithIcons = () => <Menu triggerLabel="Menu Trigger" items={itemsWithIcons} defaultOpen />;'},{id:"core-collections-menu--with-disabled-submenu",name:"With Disabled Submenu",snippet:`const WithDisabledSubmenu = () => <Menu
13625
+ triggerLabel="Menu Trigger"
13626
+ items={itemsWithSubmenu}
13627
+ disabledKeys={["share"]}
13628
+ defaultOpen />;`},{id:"core-collections-menu--with-long-labels",name:"With Long Labels",snippet:'const WithLongLabels = () => <Menu triggerLabel="Menu Trigger" items={longLabelItems} defaultOpen />;'},{id:"core-collections-menu--with-custom-trigger",name:"With Custom Trigger",snippet:`const WithCustomTrigger = () => <Menu
13047
13629
  triggerLabel="Menu Trigger"
13048
13630
  renderTrigger={({ buttonProps, ref }) => (
13049
13631
  <ActionButton
@@ -13052,12 +13634,32 @@ placement?: any
13052
13634
  size="sm"
13053
13635
  ref={ref}
13054
13636
  />
13055
- )} />;`}],implementation:`import React from "react";
13637
+ )} />;`},{id:"core-collections-menu--section-level-selection",name:"Section Level Selection",snippet:`const SectionLevelSelection = () => {
13638
+ const [sel, setSel] = React.useState<Record<string, Selection>>({
13639
+ edit: new Set<Key>(["copy"]),
13640
+ theme: new Set<Key>(["light", "dark"]),
13641
+ });
13642
+
13643
+ return (
13644
+ <Menu
13645
+ triggerLabel="Menu Trigger"
13646
+ items={itemsWithSections}
13647
+ isOpen
13648
+ selectionMode={{ edit: "single", theme: "multiple" }}
13649
+ selectedKeys={{ edit: sel.edit, theme: sel.theme }}
13650
+ onSelectionChange={(keys, meta) => {
13651
+ if (meta) setSel((prev) => ({ ...prev, [meta.sectionId]: keys }));
13652
+ }} />
13653
+ );
13654
+ };`}],implementation:`import React from "react";
13056
13655
 
13656
+ import { I18nProvider } from "../../I18nProvider/I18nProvider";
13057
13657
  import { Menu } from "../Menu";
13058
- import { items } from "./data";
13658
+ import { items, itemsWithSections, itemsWithSubmenu } from "./data";
13059
13659
 
13060
13660
  import type { MenuProps } from "../Menu.types";
13661
+ import type { Key } from "react-aria";
13662
+ import type { Selection } from "react-stately";
13061
13663
 
13062
13664
  export const MenuExample: React.FC<
13063
13665
  MenuProps & {
@@ -13087,26 +13689,181 @@ export const MenuActionValueExample: React.FC<{ label: string }> = ({
13087
13689
  <div data-testid="menu-action-value">{received}</div>
13088
13690
  </>
13089
13691
  );
13090
- };`},similarTo:[],figmaUrl:null},MessageFormat:{id:"core-utilities-messageformat",breadcrumb:"Core/Utilities/MessageFormat",importStatement:'import { MessageFormat } from "@baseline-ui/core";',description:"`MessageFormat` is a component for formatting messages. This is built on top of [react-intl](https://formatjs.io/docs/react-intl/).",documentation:`\`MessageFormat\` is a component for formatting messages. This is built on top of [react-intl](https://formatjs.io/docs/react-intl/).
13692
+ };
13091
13693
 
13092
- \`\`\`jsx
13093
- import { MessageFormat, I18nProvider } from "../../utils";
13694
+ export const MenuSelectionExample: React.FC<{
13695
+ label: string;
13696
+ selectionMode: "single" | "multiple";
13697
+ }> = ({ label, selectionMode }) => {
13698
+ const [selected, setSelected] = React.useState<Selection>(new Set());
13699
+ return (
13700
+ <>
13701
+ <Menu
13702
+ items={items}
13703
+ triggerLabel={label}
13704
+ selectionMode={selectionMode}
13705
+ selectedKeys={selected}
13706
+ onSelectionChange={setSelected}
13707
+ />
13708
+ <div data-testid="menu-selection">
13709
+ {selected === "all" ? "all" : [...selected].map(String).join(",")}
13710
+ </div>
13711
+ </>
13712
+ );
13713
+ };
13094
13714
 
13095
- const messages = {
13096
- en: {
13097
- hello: "Hello {name}!",
13098
- },
13099
- fr: {
13100
- hello: "Bonjour {name}!",
13101
- },
13715
+ export const MenuOpenChangeExample: React.FC<{ label: string }> = ({
13716
+ label,
13717
+ }) => {
13718
+ const [log, setLog] = React.useState<string[]>([]);
13719
+ return (
13720
+ <>
13721
+ <Menu
13722
+ items={items}
13723
+ triggerLabel={label}
13724
+ onOpenChange={(isOpen) => {
13725
+ setLog((prev) => [...prev, String(isOpen)]);
13726
+ }}
13727
+ />
13728
+ <div data-testid="menu-open-log">{log.join(",")}</div>
13729
+ </>
13730
+ );
13102
13731
  };
13103
13732
 
13104
- const App = () => (
13105
- <I18nProvider locale="en" messages={messages}>
13106
- <MessageFormat id="hello" values={{ name: "World" }} />
13733
+ export const MenuRtlSubmenuExample: React.FC<{ label: string }> = ({
13734
+ label,
13735
+ }) => (
13736
+ <I18nProvider locale="ar">
13737
+ <Menu items={itemsWithSubmenu} triggerLabel={label} />
13107
13738
  </I18nProvider>
13108
13739
  );
13109
- \`\`\``,props:`interface MessageFormatProps {
13740
+
13741
+ export const MenuFunctionLabelExample: React.FC = () => (
13742
+ <Menu
13743
+ items={items}
13744
+ triggerLabel={(isOpen) => (isOpen ? "Opened" : "Closed")}
13745
+ />
13746
+ );
13747
+
13748
+ export const MenuPerSectionTypeCheck: React.FC = () => (
13749
+ <Menu
13750
+ items={items}
13751
+ triggerLabel="types"
13752
+ selectionMode={{ a: "single", b: "multiple" }}
13753
+ selectedKeys={{ a: new Set<Key>(["x"]), b: new Set<Key>() }}
13754
+ onSelectionChange={(keys, meta) => {
13755
+ void keys;
13756
+ void meta?.sectionId;
13757
+ }}
13758
+ />
13759
+ );
13760
+
13761
+ const fmtSelection = (sel: Selection): string =>
13762
+ sel === "all" ? "all" : [...sel].map(String).join(",");
13763
+
13764
+ export const MenuSectionSelectionExample: React.FC<{
13765
+ label: string;
13766
+ disallowEmptySelection?: boolean;
13767
+ }> = ({ label, disallowEmptySelection }) => {
13768
+ const [sel, setSel] = React.useState<Record<string, Selection>>({
13769
+ edit: new Set<Key>(),
13770
+ theme: new Set<Key>(["light"]),
13771
+ });
13772
+ const [lastSection, setLastSection] = React.useState("");
13773
+ return (
13774
+ <>
13775
+ <Menu
13776
+ items={itemsWithSections}
13777
+ triggerLabel={label}
13778
+ selectionMode={{ edit: "single", theme: "multiple" }}
13779
+ selectedKeys={{ edit: sel.edit, theme: sel.theme }}
13780
+ disallowEmptySelection={disallowEmptySelection}
13781
+ onSelectionChange={(keys, meta) => {
13782
+ if (!meta) return;
13783
+ setSel((prev) => ({ ...prev, [meta.sectionId]: keys }));
13784
+ setLastSection(meta.sectionId);
13785
+ }}
13786
+ />
13787
+ <div data-testid="menu-section-selection-edit">
13788
+ {fmtSelection(sel.edit)}
13789
+ </div>
13790
+ <div data-testid="menu-section-selection-theme">
13791
+ {fmtSelection(sel.theme)}
13792
+ </div>
13793
+ <div data-testid="menu-section-selection-lastsection">{lastSection}</div>
13794
+ </>
13795
+ );
13796
+ };
13797
+
13798
+ export const MenuDisabledSectionSelectionExample: React.FC = () => (
13799
+ <Menu
13800
+ items={itemsWithSections}
13801
+ triggerLabel="Menu"
13802
+ selectionMode={{ edit: "single" }}
13803
+ disabledKeys={["copy"]}
13804
+ />
13805
+ );
13806
+
13807
+ export const MenuOneShotDisabledKeysSectionSelectionExample: React.FC = () => {
13808
+ const disabledKeys = ["copy"][Symbol.iterator]();
13809
+
13810
+ return (
13811
+ <Menu
13812
+ items={itemsWithSections}
13813
+ triggerLabel="Menu"
13814
+ selectionMode={{ edit: "single" }}
13815
+ disabledKeys={disabledKeys}
13816
+ />
13817
+ );
13818
+ };
13819
+
13820
+ export function MenuUncontrolledSectionSelectionExample() {
13821
+ const [lastSection, setLastSection] = React.useState("");
13822
+ const [themeKeys, setThemeKeys] = React.useState("");
13823
+ return (
13824
+ <>
13825
+ <Menu
13826
+ triggerLabel="Options"
13827
+ items={itemsWithSections}
13828
+ selectionMode={{ theme: "multiple" }}
13829
+ defaultSelectedKeys={{ theme: new Set<Key>(["light"]) }}
13830
+ onSelectionChange={(keys, meta) => {
13831
+ if (!meta) return;
13832
+ setLastSection(meta.sectionId);
13833
+ setThemeKeys(fmtSelection(keys));
13834
+ }}
13835
+ />
13836
+ <div data-testid="menu-uncontrolled-lastsection">{lastSection}</div>
13837
+ <div data-testid="menu-uncontrolled-keys">{themeKeys}</div>
13838
+ </>
13839
+ );
13840
+ }
13841
+
13842
+ export const MenuPartialSectionSelectionExample: React.FC<{
13843
+ label: string;
13844
+ }> = ({ label }) => {
13845
+ const [sel, setSel] = React.useState<Record<string, Selection>>({
13846
+ edit: new Set<Key>(),
13847
+ });
13848
+ const [action, setAction] = React.useState("");
13849
+ return (
13850
+ <>
13851
+ <Menu
13852
+ items={itemsWithSections}
13853
+ triggerLabel={label}
13854
+ selectionMode={{ edit: "single" }}
13855
+ selectedKeys={{ edit: sel.edit }}
13856
+ onSelectionChange={(keys, meta) => {
13857
+ if (meta) setSel((prev) => ({ ...prev, [meta.sectionId]: keys }));
13858
+ }}
13859
+ onAction={(key) => {
13860
+ setAction(String(key));
13861
+ }}
13862
+ />
13863
+ <div data-testid="menu-partial-action">{action}</div>
13864
+ </>
13865
+ );
13866
+ };`},similarTo:[],figmaUrl:null},MessageFormat:{id:"core-utilities-messageformat",breadcrumb:"Core/Utilities/MessageFormat",importStatement:'import { MessageFormat } from "@baseline-ui/core";',description:"`MessageFormat` renders a single message from the catalog supplied to `I18nProvider`. When the catalog has no entry for `id`, it renders `defaultMessage`.",documentation:'`MessageFormat` renders a single message from the catalog supplied to `I18nProvider`. When the catalog has no entry for `id`, it renders `defaultMessage`.\n\n```jsx\nimport { I18nProvider, MessageFormat } from "@baseline-ui/core";\nimport en from "@baseline-ui/core/intl/en.json";\nimport fr from "@baseline-ui/core/intl/fr.json";\n\nconst App = () => (\n <I18nProvider locale="en" messages={{ en, fr }}>\n <MessageFormat id="bui.calendar.previousMonth" />\n <MessageFormat id="myApp.hello" defaultMessage="Hello!" />\n </I18nProvider>\n);\n```',props:`interface MessageFormatProps {
13110
13867
  /**
13111
13868
  * By default \`<MessageFormat>\` will render the formatted string into a
13112
13869
  * \`<React.Fragment>\`. If you need to customize rendering, you can either wrap
@@ -13122,7 +13879,7 @@ id: string
13122
13879
  * The default message to use if the message id is not found.
13123
13880
  */
13124
13881
  defaultMessage?: string
13125
- }`,stories:{usage:[{id:"core-utilities-messageformat--basic",name:"Basic",snippet:'const Basic = () => <MessageFormat id="addSignature" elementType={Text} />;'}],implementation:""},similarTo:[],figmaUrl:null},Modal:{id:"core-overlays-modal",breadcrumb:"Core/Overlays/Modal",importStatement:'import { DialogExample, Modal } from "@baseline-ui/core";',description:"`Modal` renders content in an overlay above the page, trapping focus and blocking interaction with the background. Use it when a task or message must be addressed before the user can return to the underlying view.",documentation:`\`Modal\` renders content in an overlay above the page, trapping focus and blocking interaction with the background. Use it when a task or message must be addressed before the user can return to the underlying view.
13882
+ }`,stories:{usage:[{id:"core-utilities-messageformat--basic",name:"Basic",snippet:'const Basic = () => <MessageFormat id="myApp.addSignature" defaultMessage="Add signature" elementType={Text} />;'}],implementation:""},similarTo:[],figmaUrl:null},Modal:{id:"core-overlays-modal",breadcrumb:"Core/Overlays/Modal",importStatement:'import { DialogExample, Modal } from "@baseline-ui/core";',description:"`Modal` renders content in an overlay above the page, trapping focus and blocking interaction with the background. Use it when a task or message must be addressed before the user can return to the underlying view.",documentation:`\`Modal\` renders content in an overlay above the page, trapping focus and blocking interaction with the background. Use it when a task or message must be addressed before the user can return to the underlying view.
13126
13883
 
13127
13884
  * The content outside the modal is hidden from screen readers.
13128
13885
  * The modal can optionally be closed by clicking outside the modal or by pressing the <kbd>Esc</kbd> key.
@@ -16207,25 +16964,13 @@ export const PopoverDefaultOpenWithArrowExample = () => {
16207
16964
  );
16208
16965
  };
16209
16966
 
16210
- export const PopoverContainedFocusExample = () => {
16211
- return (
16212
- <Popover type="dialog">
16213
- <PopoverTrigger>
16214
- <ActionButton label="Open" />
16215
- </PopoverTrigger>
16216
- <PopoverContent shouldContainFocus={true}>
16217
- <Dialog
16218
- size="content"
16219
- className={sprinkles({
16220
- padding: "md",
16221
- display: "flex",
16222
- gap: "xl",
16223
- flexDirection: "column",
16224
- })}
16225
- style={{
16226
- width: 200,
16227
- }}
16228
- >
16967
+ export const PopoverContainedFocusExample = ({
16968
+ shouldContainFocus = true,
16969
+ }: {
16970
+ shouldContainFocus?: boolean;
16971
+ }) => {
16972
+ const content = (
16973
+ <>
16229
16974
  <Text type="label">The focus is contained within the popover.</Text>
16230
16975
 
16231
16976
  <TextInput
@@ -16237,10 +16982,55 @@ export const PopoverContainedFocusExample = () => {
16237
16982
  label="Button"
16238
16983
  style={{ width: "100%", justifyContent: "center" }}
16239
16984
  />
16985
+ </>
16986
+ );
16987
+ const contentClassName = sprinkles({
16988
+ padding: "md",
16989
+ display: "flex",
16990
+ gap: "xl",
16991
+ flexDirection: "column",
16992
+ });
16993
+
16994
+ const popover = (
16995
+ <Popover type="dialog">
16996
+ <PopoverTrigger>
16997
+ <ActionButton label="Open" />
16998
+ </PopoverTrigger>
16999
+ <PopoverContent
17000
+ shouldContainFocus={shouldContainFocus}
17001
+ isNonModal={!shouldContainFocus}
17002
+ >
17003
+ {shouldContainFocus ? (
17004
+ <Dialog
17005
+ size="content"
17006
+ className={contentClassName}
17007
+ style={{ width: 200 }}
17008
+ >
17009
+ {content}
16240
17010
  </Dialog>
17011
+ ) : (
17012
+ // No Dialog: useDialog opts the overlay into focus containment,
17013
+ // which disables the Tab-out restore path.
17014
+ <Box className={contentClassName} style={{ width: 200 }}>
17015
+ {content}
17016
+ </Box>
17017
+ )}
16241
17018
  </PopoverContent>
16242
17019
  </Popover>
16243
17020
  );
17021
+
17022
+ // The contained variant is the \`ContainedFocus\` story: a wrapper or sibling
17023
+ // here rebaselines its visual snapshot.
17024
+ if (shouldContainFocus) {
17025
+ return popover;
17026
+ }
17027
+
17028
+ return (
17029
+ <Box display="flex" flexDirection="column" gap="lg" alignItems="flex-start">
17030
+ {popover}
17031
+ <ActionButton label="After" />
17032
+ </Box>
17033
+ );
16244
17034
  };
16245
17035
 
16246
17036
  export const PopoverWithScrollableViewportExample: React.FC<{
@@ -18318,18 +19108,20 @@ function App() {
18318
19108
  }
18319
19109
  \`\`\`
18320
19110
 
18321
- We can group items into sections by passing the \`items\` prop an array of objects that contain a \`type\` property with a value of \`section\`. The \`children\` property of the section object should be an array of options. The \`id\` property of the section object should be unique.
19111
+ We can group items into sections by passing the \`items\` prop an array of section objects. A section is any item with both a \`title\` and a \`children\` array of options \u2014 items missing either one are treated as plain options. The \`id\` of each section must be unique.
19112
+
19113
+ Sections are visually separated by a divider above every section but the first.
18322
19114
 
18323
19115
  \`\`\`jsx
18324
19116
  const itemsWithSections = [
18325
19117
  {
18326
19118
  id: "solid",
18327
- type: "section",
19119
+ title: "Solid",
18328
19120
  children: items,
18329
19121
  },
18330
19122
  {
18331
19123
  id: "dashed",
18332
- type: "section",
19124
+ title: "Dashed",
18333
19125
  children: [
18334
19126
  {
18335
19127
  id: "ellipse-dashed",
@@ -18353,6 +19145,18 @@ const itemsWithSections = [
18353
19145
  <Select items={itemsWithSections} label="Label" />;
18354
19146
  \`\`\`
18355
19147
 
19148
+ By default each section's \`title\` is used only as the group's accessible name. Set \`showSectionHeader\` to render it as a visible heading above the section's options. This works the same way when the list is virtualized with \`Virtualizer\`.
19149
+
19150
+ <a href="?path=/story/core-forms-select--with-section-headers">View story</a>
19151
+
19152
+ \`\`\`jsx
19153
+ <Select
19154
+ items={itemsWithSections}
19155
+ showSectionHeader={true}
19156
+ aria-label="Choose an item"
19157
+ />
19158
+ \`\`\`
19159
+
18356
19160
  The \`Select\` component supports selecting multiple options by setting the \`selectionMode\` prop to \`"multiple"\`. When in multiselect mode, selected items are displayed as removable tags in the button, the popover stays open after selection, and checkboxes are shown instead of checkmarks.
18357
19161
 
18358
19162
  \`\`\`jsx
@@ -18556,14 +19360,16 @@ import { Virtualizer, ListLayout } from "@baseline-ui/core";
18556
19360
  \`\`\`
18557
19361
 
18558
19362
  | Key | Function |
18559
- | ----------- | ------------------------------------------------------------------------------------ |
19363
+ | -------------------- | ------------------------------------------------------------------------------------ |
19364
+ | \`Tab\` | Moves focus to and from the trigger. The popup itself is not a tab stop |
18560
19365
  | \`Space\` | Opens the listbox popup or toggles selection of the focused item in multiselect mode |
18561
19366
  | \`Enter\` | Opens the listbox popup or selects the focused item if the popup is open |
18562
19367
  | \`Escape\` | Closes the listbox popup |
18563
19368
  | \`ArrowDown\` | Opens the listbox popup and focuses the first item if no item is focused |
18564
19369
  | \`ArrowUp\` | Opens the listbox popup and focuses the last item if no item is focused |
18565
- | \`Home\` | focuses the first item |
18566
- | \`End\` | focuses the last item |
19370
+ | \`Home\` | Focuses the first item |
19371
+ | \`End\` | Focuses the last item |
19372
+ | Printable characters | Focuses the first item matching the typed characters |
18567
19373
 
18568
19374
  **Note:** In multiselect mode (\`selectionMode="multiple"\`), the \`Space\` key toggles selection of the focused item, and the popover remains open after selection.
18569
19375
 
@@ -18593,6 +19399,8 @@ The \`renderTrigger\` prop allows you to completely replace the default trigger
18593
19399
  | \`.BaselineUI-Select-Label\` | Label element |
18594
19400
  | \`.BaselineUI-Select-Popover\` | Popover container |
18595
19401
  | \`.BaselineUI-Select-SearchInput\` | Search input (when wrapped with \`Autocomplete\`) |
19402
+ | \`.BaselineUI-ListBox\` | Listbox inside the popover |
19403
+ | \`.BaselineUI-ListBox-Section\` | A section within the listbox |
18596
19404
  | \`[data-disabled]\` | Applied when \`isDisabled\` is true |
18597
19405
  | \`[data-readonly]\` | Applied when \`isReadOnly\` is true |
18598
19406
  | \`[data-focused]\` | Applied when the trigger is focused |
@@ -18601,6 +19409,7 @@ The \`renderTrigger\` prop allows you to completely replace the default trigger
18601
19409
  | \`[data-pressed]\` | Applied when the trigger is pressed |
18602
19410
  | \`[data-open]\` | Applied when the popover is open |
18603
19411
 
19412
+ * **ComboBox** \u2014 Use this instead when users should be able to type directly into the trigger to filter or enter a value
18604
19413
  * **IconSelect** \u2014 Select variant with an icon trigger instead of a text button
18605
19414
  * **ButtonSelect** \u2014 Select variant styled as a button
18606
19415
  * **ListBox** \u2014 Standalone listbox without the trigger/popover wrapper
@@ -18743,125 +19552,125 @@ hideSelectAll?: boolean
18743
19552
  */
18744
19553
  hideClear?: boolean
18745
19554
  }`,stories:{usage:[{id:"core-forms-select-multiselect--basic",name:"Basic",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18746
- 29 | type Story = StoryObj<typeof meta>;
18747
- 30 |
18748
- > 31 | export {
19555
+ 32 | type Story = StoryObj<typeof meta>;
19556
+ 33 |
19557
+ > 34 | export {
18749
19558
  | ^
18750
- 32 | Basic,
18751
- 33 | WithLabel,
18752
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--with-label",name:"WithLabel",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18753
- 29 | type Story = StoryObj<typeof meta>;
18754
- 30 |
18755
- > 31 | export {
19559
+ 35 | Basic,
19560
+ 36 | WithLabel,
19561
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--with-label",name:"WithLabel",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19562
+ 32 | type Story = StoryObj<typeof meta>;
19563
+ 33 |
19564
+ > 34 | export {
18756
19565
  | ^
18757
- 32 | Basic,
18758
- 33 | WithLabel,
18759
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--with-description",name:"WithDescription",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18760
- 29 | type Story = StoryObj<typeof meta>;
18761
- 30 |
18762
- > 31 | export {
19566
+ 35 | Basic,
19567
+ 36 | WithLabel,
19568
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--with-description",name:"WithDescription",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19569
+ 32 | type Story = StoryObj<typeof meta>;
19570
+ 33 |
19571
+ > 34 | export {
18763
19572
  | ^
18764
- 32 | Basic,
18765
- 33 | WithLabel,
18766
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--with-error",name:"WithError",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18767
- 29 | type Story = StoryObj<typeof meta>;
18768
- 30 |
18769
- > 31 | export {
19573
+ 35 | Basic,
19574
+ 36 | WithLabel,
19575
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--with-error",name:"WithError",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19576
+ 32 | type Story = StoryObj<typeof meta>;
19577
+ 33 |
19578
+ > 34 | export {
18770
19579
  | ^
18771
- 32 | Basic,
18772
- 33 | WithLabel,
18773
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--with-error-and-error-message",name:"WithErrorAndErrorMessage",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18774
- 29 | type Story = StoryObj<typeof meta>;
18775
- 30 |
18776
- > 31 | export {
19580
+ 35 | Basic,
19581
+ 36 | WithLabel,
19582
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--with-error-and-error-message",name:"WithErrorAndErrorMessage",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19583
+ 32 | type Story = StoryObj<typeof meta>;
19584
+ 33 |
19585
+ > 34 | export {
18777
19586
  | ^
18778
- 32 | Basic,
18779
- 33 | WithLabel,
18780
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--with-warning",name:"WithWarning",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18781
- 29 | type Story = StoryObj<typeof meta>;
18782
- 30 |
18783
- > 31 | export {
19587
+ 35 | Basic,
19588
+ 36 | WithLabel,
19589
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--with-warning",name:"WithWarning",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19590
+ 32 | type Story = StoryObj<typeof meta>;
19591
+ 33 |
19592
+ > 34 | export {
18784
19593
  | ^
18785
- 32 | Basic,
18786
- 33 | WithLabel,
18787
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--with-warning-and-warning-message",name:"WithWarningAndWarningMessage",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18788
- 29 | type Story = StoryObj<typeof meta>;
18789
- 30 |
18790
- > 31 | export {
19594
+ 35 | Basic,
19595
+ 36 | WithLabel,
19596
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--with-warning-and-warning-message",name:"WithWarningAndWarningMessage",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19597
+ 32 | type Story = StoryObj<typeof meta>;
19598
+ 33 |
19599
+ > 34 | export {
18791
19600
  | ^
18792
- 32 | Basic,
18793
- 33 | WithLabel,
18794
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--ghost",name:"Ghost",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18795
- 29 | type Story = StoryObj<typeof meta>;
18796
- 30 |
18797
- > 31 | export {
19601
+ 35 | Basic,
19602
+ 36 | WithLabel,
19603
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--ghost",name:"Ghost",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19604
+ 32 | type Story = StoryObj<typeof meta>;
19605
+ 33 |
19606
+ > 34 | export {
18798
19607
  | ^
18799
- 32 | Basic,
18800
- 33 | WithLabel,
18801
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--with-disabled",name:"WithDisabled",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18802
- 29 | type Story = StoryObj<typeof meta>;
18803
- 30 |
18804
- > 31 | export {
19608
+ 35 | Basic,
19609
+ 36 | WithLabel,
19610
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--with-disabled",name:"WithDisabled",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19611
+ 32 | type Story = StoryObj<typeof meta>;
19612
+ 33 |
19613
+ > 34 | export {
18805
19614
  | ^
18806
- 32 | Basic,
18807
- 33 | WithLabel,
18808
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--with-disabled-items",name:"WithDisabledItems",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18809
- 29 | type Story = StoryObj<typeof meta>;
18810
- 30 |
18811
- > 31 | export {
19615
+ 35 | Basic,
19616
+ 36 | WithLabel,
19617
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--with-disabled-items",name:"WithDisabledItems",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19618
+ 32 | type Story = StoryObj<typeof meta>;
19619
+ 33 |
19620
+ > 34 | export {
18812
19621
  | ^
18813
- 32 | Basic,
18814
- 33 | WithLabel,
18815
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--with-sections",name:"WithSections",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18816
- 29 | type Story = StoryObj<typeof meta>;
18817
- 30 |
18818
- > 31 | export {
19622
+ 35 | Basic,
19623
+ 36 | WithLabel,
19624
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--with-sections",name:"WithSections",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19625
+ 32 | type Story = StoryObj<typeof meta>;
19626
+ 33 |
19627
+ > 34 | export {
18819
19628
  | ^
18820
- 32 | Basic,
18821
- 33 | WithLabel,
18822
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--with-default-open",name:"WithDefaultOpen",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18823
- 29 | type Story = StoryObj<typeof meta>;
18824
- 30 |
18825
- > 31 | export {
19629
+ 35 | Basic,
19630
+ 36 | WithLabel,
19631
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--with-default-open",name:"WithDefaultOpen",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19632
+ 32 | type Story = StoryObj<typeof meta>;
19633
+ 33 |
19634
+ > 34 | export {
18826
19635
  | ^
18827
- 32 | Basic,
18828
- 33 | WithLabel,
18829
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--with-controlled-open",name:"WithControlledOpen",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18830
- 29 | type Story = StoryObj<typeof meta>;
18831
- 30 |
18832
- > 31 | export {
19636
+ 35 | Basic,
19637
+ 36 | WithLabel,
19638
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--with-controlled-open",name:"WithControlledOpen",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19639
+ 32 | type Story = StoryObj<typeof meta>;
19640
+ 33 |
19641
+ > 34 | export {
18833
19642
  | ^
18834
- 32 | Basic,
18835
- 33 | WithLabel,
18836
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--with-label-at-start",name:"WithLabelAtStart",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18837
- 29 | type Story = StoryObj<typeof meta>;
18838
- 30 |
18839
- > 31 | export {
19643
+ 35 | Basic,
19644
+ 36 | WithLabel,
19645
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--with-label-at-start",name:"WithLabelAtStart",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19646
+ 32 | type Story = StoryObj<typeof meta>;
19647
+ 33 |
19648
+ > 34 | export {
18840
19649
  | ^
18841
- 32 | Basic,
18842
- 33 | WithLabel,
18843
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--scrollable",name:"Scrollable",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18844
- 29 | type Story = StoryObj<typeof meta>;
18845
- 30 |
18846
- > 31 | export {
19650
+ 35 | Basic,
19651
+ 36 | WithLabel,
19652
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--scrollable",name:"Scrollable",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19653
+ 32 | type Story = StoryObj<typeof meta>;
19654
+ 33 |
19655
+ > 34 | export {
18847
19656
  | ^
18848
- 32 | Basic,
18849
- 33 | WithLabel,
18850
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--long-list",name:"LongList",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18851
- 29 | type Story = StoryObj<typeof meta>;
18852
- 30 |
18853
- > 31 | export {
19657
+ 35 | Basic,
19658
+ 36 | WithLabel,
19659
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--long-list",name:"LongList",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19660
+ 32 | type Story = StoryObj<typeof meta>;
19661
+ 33 |
19662
+ > 34 | export {
18854
19663
  | ^
18855
- 32 | Basic,
18856
- 33 | WithLabel,
18857
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--with-custom-trigger",name:"WithCustomTrigger",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
18858
- 29 | type Story = StoryObj<typeof meta>;
18859
- 30 |
18860
- > 31 | export {
19664
+ 35 | Basic,
19665
+ 36 | WithLabel,
19666
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--with-custom-trigger",name:"WithCustomTrigger",error:{name:"SyntaxError",message:`Expected story to be a function or variable declaration
19667
+ 32 | type Story = StoryObj<typeof meta>;
19668
+ 33 |
19669
+ > 34 | export {
18861
19670
  | ^
18862
- 32 | Basic,
18863
- 33 | WithLabel,
18864
- 34 | WithDescription,`}},{id:"core-forms-select-multiselect--read-only",name:"Read Only",snippet:`const ReadOnly = () => <Select
19671
+ 35 | Basic,
19672
+ 36 | WithLabel,
19673
+ 37 | WithDescription,`}},{id:"core-forms-select-multiselect--read-only",name:"Read Only",snippet:`const ReadOnly = () => <Select
18865
19674
  aria-label="Choose Stroke Style"
18866
19675
  items={items}
18867
19676
  placeholder="Choose Stroke Style"
@@ -18920,7 +19729,29 @@ hideClear?: boolean
18920
19729
  aria-label="Choose Stroke Style"
18921
19730
  items={items}
18922
19731
  placeholder="Choose Stroke Style"
18923
- selectionMode="multiple" />;`}],implementation:`import { EllipseIcon } from "@baseline-ui/icons/24";
19732
+ selectionMode="multiple" />;`},{id:"core-forms-select-multiselect--with-error-and-tags",name:"With Error And Tags",snippet:`const WithErrorAndTags = () => <Select
19733
+ aria-label="Choose Stroke Style"
19734
+ items={items}
19735
+ placeholder="Choose Stroke Style"
19736
+ selectionMode="multiple"
19737
+ label="Label"
19738
+ validationState="error"
19739
+ errorMessage="Pick fewer shapes"
19740
+ defaultValue={["ellipse", "square", "polygon"]}
19741
+ maxCount={3} />;`,description:"Tags competing with the status icon for trigger width."},{id:"core-forms-select-multiselect--long-labels-as-tags",name:"Long Labels As Tags",snippet:`const LongLabelsAsTags = () => <Select
19742
+ aria-label="Choose Stroke Style"
19743
+ items={items}
19744
+ placeholder="Choose Stroke Style"
19745
+ selectionMode="multiple"
19746
+ defaultValue={["long1", "long2"]} />;`},{id:"core-forms-select-multiselect--read-only-with-tags-open",name:"Read Only With Tags Open",snippet:`const ReadOnlyWithTagsOpen = () => <Select
19747
+ aria-label="Choose Stroke Style"
19748
+ items={items}
19749
+ placeholder="Choose Stroke Style"
19750
+ selectionMode="multiple"
19751
+ label="Label"
19752
+ isReadOnly
19753
+ value={["ellipse", "square"]}
19754
+ defaultOpen />;`}],implementation:`import { EllipseIcon } from "@baseline-ui/icons/24";
18924
19755
  import React from "react";
18925
19756
  import { useFilter } from "react-aria";
18926
19757
  import { Autocomplete, Virtualizer } from "react-aria-components";
@@ -18929,11 +19760,16 @@ import { useListData } from "react-stately";
18929
19760
  import { ActionButton } from "../../ActionButton";
18930
19761
  import { items } from "../../ListBox/__tests__/testComponents";
18931
19762
  import { virtualizeAutocompleteItems } from "../../Menu/__tests__/data";
18932
- import { VIRTUALIZER_LAYOUT_DEFAULT_OPTIONS } from "../../Virtualizer";
19763
+ import { itemsWithSectionTitles } from "../../UNSAFE_ListBox/__tests__/testComponents";
19764
+ import {
19765
+ ListLayout,
19766
+ VIRTUALIZER_LAYOUT_DEFAULT_OPTIONS,
19767
+ } from "../../Virtualizer";
18933
19768
  import { IconSelect } from "../IconSelect";
18934
19769
  import { Select } from "../Select";
18935
19770
 
18936
19771
  import type { IconSelectProps } from "../Select.types";
19772
+ import type { Key } from "@react-types/shared";
18937
19773
 
18938
19774
  export const SelectExample: React.FC<
18939
19775
  Omit<React.ComponentProps<typeof Select>, "items">
@@ -18941,13 +19777,28 @@ export const SelectExample: React.FC<
18941
19777
  return (
18942
19778
  <Select
18943
19779
  placeholder="Choose an item"
18944
- {...args}
18945
19780
  optionClassName={(item) => item.label}
19781
+ {...args}
18946
19782
  items={items}
18947
19783
  />
18948
19784
  );
18949
19785
  };
18950
19786
 
19787
+ // "Apples" is a substring of "Pineapples" \u2014 exercises exact string matching
19788
+ // in the tester's option() locator.
19789
+ const substringItems = [
19790
+ { id: "apples", label: "Apples" },
19791
+ { id: "pineapples", label: "Pineapples" },
19792
+ ];
19793
+
19794
+ export const SelectSubstringLabelsExample: React.FC<
19795
+ Omit<React.ComponentProps<typeof Select>, "items">
19796
+ > = (args) => {
19797
+ return (
19798
+ <Select placeholder="Choose an item" {...args} items={substringItems} />
19799
+ );
19800
+ };
19801
+
18951
19802
  export const SelectGetTargetRectExample: React.FC = () => {
18952
19803
  const [called, setCalled] = React.useState(false);
18953
19804
 
@@ -19004,19 +19855,136 @@ export const SelectCustomTriggerExample: React.FC<
19004
19855
  };
19005
19856
 
19006
19857
  export const IconSelectExample: React.FC<
19007
- Omit<IconSelectProps, "items" | "icon" | "aria-label">
19858
+ Omit<IconSelectProps, "items" | "aria-label">
19008
19859
  > = (args) => {
19009
19860
  return (
19010
19861
  <IconSelect
19011
19862
  placeholder="Choose an item"
19863
+ icon={EllipseIcon}
19012
19864
  {...args}
19013
19865
  items={items}
19014
- icon={EllipseIcon}
19015
19866
  aria-label="Aria Label"
19016
19867
  />
19017
19868
  );
19018
19869
  };
19019
19870
 
19871
+ export const VirtualSelectWithSectionHeadersExample: React.FC<
19872
+ Omit<React.ComponentProps<typeof Select>, "items">
19873
+ > = (args) => {
19874
+ return (
19875
+ <Virtualizer
19876
+ layout={ListLayout}
19877
+ layoutOptions={{
19878
+ rowHeight: 36,
19879
+ headingHeight: 40,
19880
+ }}
19881
+ >
19882
+ <Select
19883
+ aria-label="Choose an item"
19884
+ placeholder="Choose an item"
19885
+ showSectionHeader={true}
19886
+ {...args}
19887
+ items={itemsWithSectionTitles}
19888
+ defaultOpen={true}
19889
+ maxHeight={400}
19890
+ />
19891
+ </Virtualizer>
19892
+ );
19893
+ };
19894
+
19895
+ export const SelectWithSectionHeadersExample: React.FC<
19896
+ Omit<React.ComponentProps<typeof Select>, "items">
19897
+ > = (args) => {
19898
+ return (
19899
+ <Select
19900
+ aria-label="Choose an item"
19901
+ placeholder="Choose an item"
19902
+ showSectionHeader={true}
19903
+ {...args}
19904
+ items={itemsWithSectionTitles}
19905
+ defaultOpen={true}
19906
+ />
19907
+ );
19908
+ };
19909
+
19910
+ /**
19911
+ * Owns \`isOpen\` so the controlled-open contract can be driven from outside the
19912
+ * Select, with a sibling readout of the current state.
19913
+ */
19914
+ export const ControlledOpenSelectExample: React.FC<
19915
+ Omit<React.ComponentProps<typeof Select>, "items">
19916
+ > = (args) => {
19917
+ const [isOpen, setIsOpen] = React.useState(false);
19918
+
19919
+ return (
19920
+ <>
19921
+ <Select
19922
+ aria-label="Choose an item"
19923
+ placeholder="Choose an item"
19924
+ {...args}
19925
+ items={items}
19926
+ isOpen={isOpen}
19927
+ onOpenChange={setIsOpen}
19928
+ />
19929
+ <button
19930
+ data-testid="open-externally"
19931
+ onClick={() => {
19932
+ setIsOpen(true);
19933
+ }}
19934
+ >
19935
+ Open
19936
+ </button>
19937
+ <span data-testid="open-state">{String(isOpen)}</span>
19938
+ </>
19939
+ );
19940
+ };
19941
+
19942
+ /**
19943
+ * Custom trigger that renders the multi-select half of the \`renderTrigger\`
19944
+ * payload \u2014 \`selectionMode\`, \`maxCount\` and \`onRemove\` \u2014 which the default
19945
+ * \`SelectButton\` otherwise keeps to itself.
19946
+ */
19947
+ export const MultiSelectCustomTriggerExample: React.FC<
19948
+ Omit<React.ComponentProps<typeof Select>, "items">
19949
+ > = (args) => {
19950
+ return (
19951
+ <Select
19952
+ aria-label="Choose an item"
19953
+ selectionMode="multiple"
19954
+ {...args}
19955
+ items={items}
19956
+ renderTrigger={({
19957
+ buttonProps,
19958
+ ref,
19959
+ selectedValue,
19960
+ selectionMode,
19961
+ onRemove,
19962
+ maxCount,
19963
+ }) => (
19964
+ <div>
19965
+ <ActionButton
19966
+ {...buttonProps}
19967
+ ref={ref}
19968
+ label={\`\${selectionMode}|\${maxCount}|\${selectedValue?.length ?? 0}\`}
19969
+ variant="popover"
19970
+ />
19971
+ <button
19972
+ data-testid="remove-first"
19973
+ onClick={() => {
19974
+ const first = selectedValue?.[0];
19975
+ if (first) {
19976
+ onRemove?.(new Set([first.id]));
19977
+ }
19978
+ }}
19979
+ >
19980
+ Remove first
19981
+ </button>
19982
+ </div>
19983
+ )}
19984
+ />
19985
+ );
19986
+ };
19987
+
19020
19988
  export const SelectWithVirtualizeAutocompleteExample: React.FC<
19021
19989
  Omit<React.ComponentProps<typeof Select>, "items">
19022
19990
  > = (args) => {
@@ -19040,6 +20008,27 @@ export const SelectWithVirtualizeAutocompleteExample: React.FC<
19040
20008
  </Autocomplete>
19041
20009
  </Virtualizer>
19042
20010
  );
20011
+ };
20012
+
20013
+ /**
20014
+ * \`optionClassName\` reads component state, and \`items\` is module-level so React Aria's
20015
+ * per-item element cache applies. Pinned open so the state change comes from selecting an
20016
+ * option rather than from an outside press the popover would swallow.
20017
+ */
20018
+ export const CountingSelectExample: React.FC = () => {
20019
+ const [selected, setSelected] = React.useState<Key | null>(null);
20020
+
20021
+ return (
20022
+ <Select
20023
+ aria-label="Counting select"
20024
+ items={items}
20025
+ isOpen={true}
20026
+ optionClassName={(item) =>
20027
+ item.id === selected ? "option-picked" : "option-plain"
20028
+ }
20029
+ onSelectionChange={setSelected}
20030
+ />
20031
+ );
19043
20032
  };`},similarTo:[],figmaUrl:null},Separator:{id:"core-content-separator",breadcrumb:"Core/Content/Separator",importStatement:'import { Separator } from "@baseline-ui/core";',description:"`Separator` is a thin horizontal or vertical rule that divides adjacent content. Use it to create a visual break between sections, groups of controls, or items in a list.",documentation:`\`Separator\` is a thin horizontal or vertical rule that divides adjacent content. Use it to create a visual break between sections, groups of controls, or items in a list.
19044
20033
 
19045
20034
  \`\`\`jsx
@@ -23296,7 +24285,7 @@ export const TaggedPaginationExample: React.FC<
23296
24285
  {...props}
23297
24286
  />
23298
24287
  );
23299
- };`},similarTo:[],figmaUrl:null},Text:{id:"core-content-text",breadcrumb:"Core/Content/Text",importStatement:'import { Text, VariantViewer } from "@baseline-ui/core";',description:"`Text` is a typography primitive that renders strings with consistent type, size, and weight from the design system. Use it whenever you need to display textual content so that headings, body copy, values, and helper text stay visually aligned.",documentation:'`Text` is a typography primitive that renders strings with consistent type, size, and weight from the design system. Use it whenever you need to display textual content so that headings, body copy, values, and helper text stay visually aligned.\n\n```jsx\nimport { Text } from "@storybook/addon-docs/blocks";\n\n<Text type="subtitle" size="sm">\n Text\n</Text>;\n```\n\nThe `Text` component supports the following types: `title`, `subtitle`, `body`, `value` and `helper`. It\nalso supports the following sizes: `sm`, `md`, and `lg`. You can see all the variants [here](/story/core-text--variants)',props:`interface TextProps {
24288
+ };`},similarTo:[],figmaUrl:null},Text:{id:"core-content-text",breadcrumb:"Core/Content/Text",importStatement:'import { Input, Label, Text, TextField, VariantViewer } from "@baseline-ui/core";',description:"`Text` is a typography primitive that renders strings with consistent type, size, and weight from the design system. Use it whenever you need to display textual content so that headings, body copy, values, and helper text stay visually aligned.",documentation:'`Text` is a typography primitive that renders strings with consistent type, size, and weight from the design system. Use it whenever you need to display textual content so that headings, body copy, values, and helper text stay visually aligned.\n\n* Consistent type, size, and weight pulled from the design system\n* Renders as any HTML element via the `elementType` prop\n* Opt-in integration with a container\'s context through the `slot` prop for automatic `aria-describedby` / `aria-labelledby` wiring\n\n```jsx\nimport { Text } from "@baseline-ui/core";\n\n<Text type="subtitle" size="sm">\n Text\n</Text>;\n```\n\nThe `Text` component supports the following types: `title`, `subtitle`, `body`, `label`, `value`, and `helper`. It\nalso supports the following sizes: `sm`, `md`, and `lg`.\n\n`Text` can integrate with a container component\'s context. Pass a `slot` name\nand the text picks up the `id` the container provides for that slot, so it gets\nassociated via `aria-describedby` / `aria-labelledby` without manually wiring an\n`id`.\n\n```jsx\n<Text slot="description">\n This description is linked to its field automatically.\n</Text>\n```\n\nSlot integration is **opt-in**: a `Text` without a `slot` never inherits an\nambient context, so it renders identically whether or not a container is\npresent. Naming a `slot` the surrounding container does not define will throw \u2014\nonly pass slots you know the container exposes.\n\nNo Baseline container currently provides a slotted `TextContext`, so this is\ngroundwork today: the mechanism is exercisable against raw React Aria containers\n(`TextField`, `Select`, `TagGroup`, etc.) but no Baseline component wires it up yet.\n\n`Text` always emits a stable class name you can target for styling.\n\n| Selector | Description |\n| ------------------ | ------------------------------------- |\n| `.BaselineUI-Text` | Applied to the rendered text element. |',props:`interface TextProps {
23300
24289
  /**
23301
24290
  * @deprecated Do not use in new components. This is a legacy block
23302
24291
  * identifier; new components should exclude it via
@@ -23352,6 +24341,13 @@ children: React.ReactNode
23352
24341
  * @default "span"
23353
24342
  */
23354
24343
  elementType?: React.ElementType
24344
+ /**
24345
+ * A slot name that wires this text into a parent container's context \u2014
24346
+ * e.g. \`slot="description"\` or \`slot="label"\` \u2014 so the container can
24347
+ * associate it via \`aria-describedby\` / \`aria-labelledby\` without manually
24348
+ * threading an \`id\`. Pass \`null\` to opt out of an ambient slot.
24349
+ */
24350
+ slot?: string | null
23355
24351
  }`,stories:{usage:[{id:"core-content-text--basic",name:"Basic",snippet:"const Basic = () => <Text>Sample Text</Text>;"},{id:"core-content-text--variants",name:"Variants",snippet:`const Variants = (args) => (
23356
24352
  <VariantViewer<React.ComponentProps<typeof Text>>
23357
24353
  header={["Small", "Medium", "Large"]}
@@ -23386,8 +24382,24 @@ elementType?: React.ElementType
23386
24382
  }}
23387
24383
  defaultProps={args}
23388
24384
  />
23389
- );`}],implementation:`import React from "react";
24385
+ );`},{id:"core-content-text--slots",name:"Slots",snippet:`const Slots = () => (
24386
+ <TextField>
24387
+ {/* The raw React Aria Label carries no Baseline styling, so it inherits
24388
+ the storybook default (black) text and fails contrast against the dark
24389
+ theme background. Give it the themed foreground; the slot wiring below
24390
+ is the point of this story. */}
24391
+ <Label style={{ color: themeVars.color.text.primary }}>
24392
+ Display name
24393
+ </Label>
24394
+ <Input />
24395
+ <Text slot="description" type="helper" size="sm">
24396
+ This is shown to other members of your workspace.
24397
+ </Text>
24398
+ </TextField>
24399
+ );`,description:"`Text` opts into a container's context when given a `slot`. Here a raw React Aria `TextField` provides a `TextContext`, and `<Text slot=\"description\">` picks up the id the field exposes for that slot \u2014 wiring itself as the field's `aria-describedby` without a manually threaded `id`."}],implementation:`import React from "react";
24400
+ import { TextContext } from "react-aria-components";
23390
24401
 
24402
+ import { UNSAFE_ListBox } from "../../UNSAFE_ListBox";
23391
24403
  import { Text } from "../Text";
23392
24404
 
23393
24405
  export function TextWithRef() {
@@ -23408,65 +24420,61 @@ export function TextWithRef() {
23408
24420
  <span data-testid="tag-name">{tag}</span>
23409
24421
  </>
23410
24422
  );
24423
+ }
24424
+
24425
+ const SLOTTED_TEXT_CONTEXT = {
24426
+ slots: {
24427
+ description: { id: "provided-description-id" },
24428
+ },
24429
+ };
24430
+
24431
+ export function TextInSlotContext() {
24432
+ return (
24433
+ <TextContext.Provider value={SLOTTED_TEXT_CONTEXT}>
24434
+ <Text slot="description" data-block-id="slotted">
24435
+ Description
24436
+ </Text>
24437
+ </TextContext.Provider>
24438
+ );
24439
+ }
24440
+
24441
+ export function TextBareInSlotContext() {
24442
+ // A slotless <Text> inside a slotted context that has no default slot must
24443
+ // render (opt out) rather than throw "A slot prop is required".
24444
+ return (
24445
+ <TextContext.Provider value={SLOTTED_TEXT_CONTEXT}>
24446
+ <Text id="bare-id" data-testid="bare">
24447
+ Description
24448
+ </Text>
24449
+ </TextContext.Provider>
24450
+ );
24451
+ }
24452
+
24453
+ export function TextInListBoxRenderOption() {
24454
+ // A bare <Text> in renderOption lands inside RAC's ListBoxItem, which provides
24455
+ // a TextContext whose default slot maps to the option's generated label id.
24456
+ // A slotless <Text> must opt out (no inherited id) rather than silently adopt
24457
+ // that label id \u2014 otherwise two Texts in one option collide on the same id.
24458
+ return (
24459
+ <UNSAFE_ListBox
24460
+ aria-label="Options"
24461
+ items={[{ id: "opt-1", label: "Option 1" }]}
24462
+ renderOption={(item) => (
24463
+ <Text data-testid="render-option">{item.label}</Text>
24464
+ )}
24465
+ />
24466
+ );
24467
+ }
24468
+
24469
+ export function TextOptOutOfSlotContext() {
24470
+ return (
24471
+ <TextContext.Provider value={SLOTTED_TEXT_CONTEXT}>
24472
+ <Text slot={null} id="own-id" data-block-id="opt-out">
24473
+ Description
24474
+ </Text>
24475
+ </TextContext.Provider>
24476
+ );
23411
24477
  }`},similarTo:[],figmaUrl:null},TextInput:{id:"core-forms-textinput",breadcrumb:"Core/Forms/TextInput",importStatement:'import { TextInput, VariantViewer } from "@baseline-ui/core";',description:"`TextInput` is a field for capturing free-form text \u2014 a single-line `<input>` by default, or a resizable multi-line `<textarea>` when `isMultiLine` is set. Use it for values such as names, emails, or longer notes.",documentation:'`TextInput` is a field for capturing free-form text \u2014 a single-line `<input>` by default, or a resizable multi-line `<textarea>` when `isMultiLine` is set. Use it for values such as names, emails, or longer notes.\n\n* This component is built on top of the `input` element (or `textarea` in multi-line mode).\n* It provides visual and ARIA labels to the input element to make it more accessible.\n* It supports events for change, clipboard, composition, focus, and keyboard.\n* It exposes invalid states to assistive technology via ARIA.\n* It supports description and other state messages which are linked to the input element via ARIA.\n\n```jsx\nimport { TextInput } from "@baseline-ui/core";\n\n<TextInput placeholder="Enter Text" />;\n```\n\nFor numeric entry use `NumberInput`, for search fields use `SearchInput`, and for color values use `ColorInput`.\n\n`TextInput` supports two variants: `primary` (default) and `ghost`. Set the variant with the `variant` prop.\n\n```jsx\n<TextInput placeholder="Placeholder" variant="primary" />\n<TextInput placeholder="Placeholder" variant="ghost" />\n```\n\nYou can add a label to the `TextInput` by passing a `label` prop.\n\n```jsx\n<TextInput label="Label" placeholder="Placeholder" />\n```\n\nBy default, the label is positioned above the `TextInput`. You can change the\nposition of the label by passing a `labelPosition` prop.\n\n```jsx\n<TextInput\n label="Label"\n labelPosition="start"\n description="Description"\n placeholder="Placeholder"\n/>\n```\n\nYou can add a description to the `TextInput` by passing a `description` prop. A\ndescription is used to provide additional information about the `TextInput`.\n\n```jsx\n<TextInput label="Label" description="Description" placeholder="Placeholder" />\n```\n\nYou can put the `TextInput` into an error state by setting `validationState` to\n`"error"`. You can also pass `errorMessage` to provide additional information\nabout the error; passing `errorMessage` on its own is enough to render the error\nstate. When `description` is also present, it takes precedence and the error\nmessage is not shown.\n\n```jsx\n<TextInput\n label="Label"\n validationState="error"\n errorMessage="Error message"\n placeholder="Placeholder"\n/>\n```\n\nYou can put the `TextInput` into a warning state by setting `validationState` to\n`"warning"`. You can also pass `warningMessage` to provide additional\ninformation about the warning.\n\n```jsx\n<TextInput\n label="Label"\n validationState="warning"\n warningMessage="Warning message"\n placeholder="Placeholder"\n/>\n```\n\nYou can make the `TextInput` read only by passing an `isReadOnly` prop.\n\n```jsx\n<TextInput label="Label" isReadOnly defaultValue="Read-only value" />\n```\n\nYou can disable the `TextInput` by passing an `isDisabled` prop.\n\n```jsx\n<TextInput\n label="Label"\n isDisabled\n defaultValue="Disabled value"\n description="Description"\n placeholder="Placeholder"\n/>\n```\n\nYou can control the `TextInput` by passing a `value` prop together with an\n`onChange` prop. `onChange` is called with the new string value (not a DOM\nevent).\n\n```jsx\nconst [value, setValue] = useState("Controlled value");\n\n<TextInput\n label="Label"\n value={value}\n onChange={(value) => setValue(value)}\n placeholder="Placeholder"\n/>;\n```\n\nSet `isMultiLine` to render the field as a resizable `<textarea>` instead of a\nsingle-line `<input>`. The field starts at a minimum height and can be dragged\nto resize in both directions, except when it is read only or disabled. All\nstates (error, warning, disabled, read only, ghost) are supported, just like the\nsingle-line input.\n\n```jsx\n<TextInput\n label="Label"\n isMultiLine\n defaultValue="Lorem ipsum"\n description="Helper text"\n/>\n```\n\nUse the `rows` prop to set the initial number of visible text rows.\n\n```jsx\n<TextInput label="Label" isMultiLine rows={5} placeholder="Placeholder" />\n```\n\nYou can use the `TextInput` in a HTML form by passing a `name` prop. In addition, attributes such as `type`, `pattern`, `inputMode`, and others are passed through to the underlying input element.\n\n```jsx\n<form>\n <TextInput\n name="text-input"\n type="text"\n placeholder="Placeholder"\n description="Description"\n />\n</form>\n```\n\nThese selectors are applied to the underlying `<input>` / `<textarea>` element. The root wrapper is also exposed via the `BaselineUI-TextInput` class and the field element via the `BaselineUI-TextInput-Input` class.\n\n| Selector | Description |\n| ------------------------- | ------------------------------------------------------------------- |\n| `[data-readonly]` | Whether the input is read only. |\n| `[data-disabled]` | Whether the input is disabled. |\n| `[data-validation-state]` | The validation state of the input (`error`, `warning`, or `valid`). |\n| `[data-focused]` | Whether the input is focused, either via a mouse or keyboard. |\n| `[data-focus-visible]` | Whether the input is keyboard focused. |\n\n| Key | Function |\n| ---------------- | ---------------------------------------------------------------------------- |\n| <kbd>Enter</kbd> | Submits the form in single-line mode; inserts a new line in multi-line mode. |',props:`interface TextInputProps {
23412
- /**
23413
- * @deprecated Do not use in new components. This is a legacy block
23414
- * identifier; new components should exclude it via
23415
- * \`Omit<StylingProps, keyof BlockProps>\` (see \`StatusCard\` / \`Code\`).
23416
- * Retained on existing components for backward compatibility.
23417
- *
23418
- * The unique identifier for the block. This is used to identify the block in
23419
- * the DOM and in the block map. It is added as a data attribute
23420
- * \`data-block-id\` to the root element of the block if a DOM node is
23421
- * rendered.
23422
- */
23423
- data-block-id?: string
23424
- /**
23425
- * @deprecated Do not use in new components. This is a legacy block group
23426
- * marker; new components should exclude it via
23427
- * \`Omit<StylingProps, keyof BlockProps>\` (see \`StatusCard\` / \`Code\`).
23428
- * Retained on existing components for backward compatibility.
23429
- *
23430
- * Represents a data block group. This is similar to \`data-block-id\` but it
23431
- * doesn't have to be unique just like \`class\`. This is used to group blocks
23432
- * together in the DOM and in the block map. It is added as a data attribute
23433
- * \`data-block-class\` to the root element of the block if a DOM node is
23434
- * rendered.
23435
- */
23436
- data-block-class?: string
23437
- /**
23438
- * The className applied to the root element of the component.
23439
- */
23440
- className?: string
23441
- /**
23442
- * The style applied to the root element of the component.
23443
- */
23444
- style?: React.CSSProperties
23445
- /**
23446
- * The description to display below the input.
23447
- */
23448
- description?: string
23449
- /**
23450
- * The error message to display when the input is in an error state.
23451
- */
23452
- errorMessage?: string
23453
- /**
23454
- * The warning message to display when the input is in a warning state.
23455
- */
23456
- warningMessage?: string
23457
- /**
23458
- * The style object to apply to the input element
23459
- */
23460
- inputStyle?: React.CSSProperties
23461
- /**
23462
- * The class name to apply to the input element
23463
- */
23464
- inputClassName?: string
23465
- /**
23466
- * The number of visible text rows for the multi-line input. Only applies when
23467
- * \`isMultiLine\` is set.
23468
- */
23469
- rows?: number
23470
24478
  variant?: any
23471
24479
  labelPosition?: any
23472
24480
  isMultiLine?: any
@@ -26809,6 +27817,7 @@ export const VirtualizedTreeViewWithRenameExample: React.FC<
26809
27817
  VirtualizedScrollIntoViewExample,
26810
27818
  VirtualListBoxGridLayoutExample,
26811
27819
  VirtualListBoxListLayoutExample,
27820
+ VirtualListBoxWithSectionHeadersExample,
26812
27821
  VirtualListBoxWithSectionsExample,
26813
27822
  } from "@baseline-ui/core";`,description:"`UNSAFE_ListBox` is a low-level selectable list with custom option rendering, grouped sections, drag-and-drop reordering, and virtualization. Use it to build bespoke list UIs \u2014 such as font pickers, layer panels, or large catalogs \u2014 where the standard list components are too constrained.",documentation:`\`UNSAFE_ListBox\` is a low-level selectable list with custom option rendering, grouped sections, drag-and-drop reordering, and virtualization. Use it to build bespoke list UIs \u2014 such as font pickers, layer panels, or large catalogs \u2014 where the standard list components are too constrained.
26814
27823
 
@@ -26905,6 +27914,34 @@ const fonts = [
26905
27914
  />;
26906
27915
  \`\`\`
26907
27916
 
27917
+ Rendered options and sections are cached per item object. Reading component state from
27918
+ \`renderOption\`, \`optionClassName\`, \`optionStyle\`, \`renderSectionHeader\`, \`sectionClassName\`
27919
+ or \`sectionStyle\` works \u2014 the options re-render when the state changes \u2014 and toggling
27920
+ \`showSectionHeader\` or \`withSectionHeaderPadding\` does too.
27921
+
27922
+ The cost only applies when \`items\` is memoized, since an inline \`items={data.map(...)}\`
27923
+ produces new item objects every render and the cache never applies in the first place. With
27924
+ stable items, any of these props changing identity rebuilds every option and section, so
27925
+ memoize the ones that don't depend on state \u2014 including object literals passed to
27926
+ \`optionStyle\`, which are new on every render.
27927
+
27928
+ \`\`\`jsx
27929
+ const [highlighted, setHighlighted] = useState(null);
27930
+
27931
+ // Rebuilds only when the highlight moves; \`fonts\` is module-level, so the cache applies.
27932
+ const optionStyle = useCallback(
27933
+ (item) => ({ fontWeight: item.id === highlighted ? "bold" : "normal" }),
27934
+ [highlighted],
27935
+ );
27936
+
27937
+ <UNSAFE_ListBox
27938
+ items={fonts}
27939
+ aria-label="Fonts"
27940
+ optionStyle={optionStyle}
27941
+ onAction={setHighlighted}
27942
+ />;
27943
+ \`\`\`
27944
+
26908
27945
  \`\`\`jsx
26909
27946
  <UNSAFE_ListBox
26910
27947
  items={items}
@@ -27002,6 +28039,14 @@ style?: React.CSSProperties
27002
28039
  /**
27003
28040
  * The custom render function for the listbox options.
27004
28041
  *
28042
+ * Replaces the option's entire contents, including the default selection
28043
+ * indicator \u2014 render your own affordance from \`options.isSelected\` if you
28044
+ * need one. Selection and keyboard behavior are unaffected.
28045
+ *
28046
+ * Reading component state here works, at the cost of rebuilding every cached option and
28047
+ * section whenever the function's identity changes \u2014 see {@link UNSAFE_ListBoxProps.items}
28048
+ * for when that cost applies.
28049
+ *
27005
28050
  * @param item ListOption
27006
28051
  * @param options ListBoxItemRenderProps
27007
28052
  */
@@ -27010,7 +28055,10 @@ renderOption?: (
27010
28055
  options: ListBoxItemRenderProps,
27011
28056
  ) => React.ReactNode
27012
28057
  /**
27013
- * The CSS class name for the option.
28058
+ * A CSS class name added to the option's built-in classes.
28059
+ *
28060
+ * Compared by identity, so an inline function or an unmemoized value rebuilds every
28061
+ * cached option and section on each render. A plain string is compared by value.
27014
28062
  */
27015
28063
  optionClassName?: | string
27016
28064
  | ((
@@ -27019,6 +28067,9 @@ optionClassName?: | string
27019
28067
  ) => string | undefined)
27020
28068
  /**
27021
28069
  * The style of the option.
28070
+ *
28071
+ * Compared by identity, so an inline function or object literal rebuilds every cached
28072
+ * option and section on each render \u2014 memoize it to avoid that.
27022
28073
  */
27023
28074
  optionStyle?: | React.CSSProperties
27024
28075
  | ((
@@ -27028,15 +28079,25 @@ optionStyle?: | React.CSSProperties
27028
28079
  /**
27029
28080
  * The custom render function for the listbox sections.
27030
28081
  *
28082
+ * Reading component state here works, at the cost of rebuilding every cached option and
28083
+ * section whenever the function's identity changes \u2014 see {@link UNSAFE_ListBoxProps.items}
28084
+ * for when that cost applies.
28085
+ *
27031
28086
  * @param section ListSection
27032
28087
  */
27033
28088
  renderSectionHeader?: (section: ListSection) => React.ReactNode
27034
28089
  /**
27035
28090
  * The CSS class name for the section.
28091
+ *
28092
+ * Compared by identity, so an inline function or an unmemoized value rebuilds every
28093
+ * cached option and section on each render. A plain string is compared by value.
27036
28094
  */
27037
28095
  sectionClassName?: string | ((section: ListSection) => string | undefined)
27038
28096
  /**
27039
28097
  * The style of the section.
28098
+ *
28099
+ * Compared by identity, so an inline function or object literal rebuilds every cached
28100
+ * option and section on each render \u2014 memoize it to avoid that.
27040
28101
  */
27041
28102
  sectionStyle?: | React.CSSProperties
27042
28103
  | ((section: ListSection) => React.CSSProperties | undefined)
@@ -27072,6 +28133,11 @@ withSectionHeaderPadding?: boolean
27072
28133
  *
27073
28134
  * type ListItem = ListOption | ListSection;
27074
28135
  * \`\`\`
28136
+ *
28137
+ * Rendered options and sections are cached per item object. Building this array inline
28138
+ * (\`items={data.map(...)}\`) produces new item objects every render, so nothing is ever
28139
+ * reused and the customisation props cost nothing; memoize it and the cache applies,
28140
+ * which is when their identity starts to matter.
27075
28141
  */
27076
28142
  items?: ListItem[]
27077
28143
  /**
@@ -27158,7 +28224,7 @@ listBoxHandle?: React.RefObject<ListHandle>
27158
28224
  items={items}
27159
28225
  aria-label="List box"
27160
28226
  selectionMode="multiple"
27161
- defaultSelectedKeys={["item-0"]} />;`},{id:"core-collections-unsafe-listbox--virtualized-horizontal",name:"Virtualized Horizontal",snippet:'const VirtualizedHorizontal = () => <VirtualListBoxGridLayoutExample items={items} aria-label="List box" selectionMode="multiple" />;'},{id:"core-collections-unsafe-listbox--virtualized-with-sections",name:"Virtualized With Sections",snippet:'const VirtualizedWithSections = () => <VirtualListBoxWithSectionsExample items={items} aria-label="List box" selectionMode="single" />;'},{id:"core-collections-unsafe-listbox--virtualized-scroll-into-view",name:"Virtualized Scroll Into View",snippet:"const VirtualizedScrollIntoView = () => <VirtualizedScrollIntoViewExample />;"}],implementation:`import { XIcon } from "@baseline-ui/icons/16";
28227
+ defaultSelectedKeys={["item-0"]} />;`},{id:"core-collections-unsafe-listbox--virtualized-horizontal",name:"Virtualized Horizontal",snippet:'const VirtualizedHorizontal = () => <VirtualListBoxGridLayoutExample items={items} aria-label="List box" selectionMode="multiple" />;'},{id:"core-collections-unsafe-listbox--virtualized-with-sections",name:"Virtualized With Sections",snippet:'const VirtualizedWithSections = () => <VirtualListBoxWithSectionsExample items={items} aria-label="List box" selectionMode="single" />;'},{id:"core-collections-unsafe-listbox--virtualized-with-section-separators",name:"Virtualized With Section Separators",snippet:'const VirtualizedWithSectionSeparators = () => <VirtualListBoxWithSectionHeadersExample items={items} aria-label="List box" selectionMode="single" />;'},{id:"core-collections-unsafe-listbox--virtualized-scroll-into-view",name:"Virtualized Scroll Into View",snippet:"const VirtualizedScrollIntoView = () => <VirtualizedScrollIntoViewExample />;"}],implementation:`import { XIcon } from "@baseline-ui/icons/16";
27162
28228
  import {
27163
28229
  EllipseDashedIcon,
27164
28230
  EllipseIcon,
@@ -27188,7 +28254,10 @@ import {
27188
28254
  import { useDragAndDrop } from "../hooks/useDragAndDrop";
27189
28255
  import { UNSAFE_ListBox as ListBox } from "../ListBox";
27190
28256
 
27191
- import type { ListItem } from "../../shared/types/List";
28257
+ import type {
28258
+ ListItem,
28259
+ ListOption as ListOptionType,
28260
+ } from "../../shared/types/List";
27192
28261
  import type { ListHandle } from "../ListBox.types";
27193
28262
  import type { DroppableCollectionReorderEvent } from "@react-types/shared";
27194
28263
 
@@ -27298,8 +28367,27 @@ export const ListBoxWithSectionsExample: React.FC<
27298
28367
  export const DragAndDropListBoxExample: React.FC<
27299
28368
  Omit<React.ComponentProps<typeof ListBox>, "items" | "onReorder"> & {
27300
28369
  customPreview?: boolean;
28370
+ rerenderOnEvent?: boolean;
28371
+ }
28372
+ > = ({ orientation, layout, customPreview, rerenderOnEvent, ...rest }) => {
28373
+ const [, rerender] = React.useReducer((tick: number) => tick + 1, 0);
28374
+
28375
+ React.useEffect(() => {
28376
+ if (!rerenderOnEvent) {
28377
+ return;
27301
28378
  }
27302
- > = ({ orientation, layout, customPreview, ...rest }) => {
28379
+
28380
+ const onRerender = () => {
28381
+ rerender();
28382
+ };
28383
+
28384
+ window.addEventListener("repro:rerender", onRerender);
28385
+
28386
+ return () => {
28387
+ window.removeEventListener("repro:rerender", onRerender);
28388
+ };
28389
+ }, [rerenderOnEvent]);
28390
+
27303
28391
  const list = useListData({
27304
28392
  initialItems: Array.from({ length: 20 }).map((_, i) => ({
27305
28393
  id: \`item-\${i}\`,
@@ -27765,6 +28853,75 @@ export const VirtualListBoxWithSectionsExample: React.FC<
27765
28853
  );
27766
28854
  };
27767
28855
 
28856
+ export const VirtualListBoxWithSectionHeadersExample: React.FC<
28857
+ Omit<React.ComponentProps<typeof ListBox>, "items">
28858
+ > = (args) => {
28859
+ return (
28860
+ <Virtualizer
28861
+ layout={ListLayout}
28862
+ layoutOptions={{
28863
+ rowHeight: 36,
28864
+ headingHeight: 40,
28865
+ }}
28866
+ >
28867
+ <ListBox
28868
+ {...args}
28869
+ items={itemsWithSectionTitles}
28870
+ showSectionHeader={true}
28871
+ style={{
28872
+ height: 400,
28873
+ width: 300,
28874
+ overflow: "auto",
28875
+ }}
28876
+ aria-label="Virtualized list box with section headers"
28877
+ />
28878
+ </Virtualizer>
28879
+ );
28880
+ };
28881
+
28882
+ const leadingSection = {
28883
+ id: "leading",
28884
+ title: "Leading",
28885
+ children: [{ id: "leading-item", label: "Leading item" }],
28886
+ } as ListItem;
28887
+
28888
+ export const VirtualListBoxWithPrependableSectionExample: React.FC = () => {
28889
+ const [showLeadingSection, setShowLeadingSection] = React.useState(false);
28890
+
28891
+ return (
28892
+ <>
28893
+ <ActionButton
28894
+ onPress={() => {
28895
+ setShowLeadingSection(true);
28896
+ }}
28897
+ label="Prepend section"
28898
+ />
28899
+ <Virtualizer
28900
+ layout={ListLayout}
28901
+ layoutOptions={{
28902
+ rowHeight: 36,
28903
+ headingHeight: 40,
28904
+ }}
28905
+ >
28906
+ <ListBox
28907
+ items={
28908
+ showLeadingSection
28909
+ ? [leadingSection, ...itemsWithSectionTitles]
28910
+ : itemsWithSectionTitles
28911
+ }
28912
+ showSectionHeader={true}
28913
+ style={{
28914
+ height: 400,
28915
+ width: 300,
28916
+ overflow: "auto",
28917
+ }}
28918
+ aria-label="Virtualized list box with a prependable section"
28919
+ />
28920
+ </Virtualizer>
28921
+ </>
28922
+ );
28923
+ };
28924
+
27768
28925
  export const VirtualizedScrollIntoViewExample: React.FC = () => {
27769
28926
  const listBoxHandle = React.useRef<ListHandle>(null);
27770
28927
  const items = Array.from({ length: 10_000 }).map((_, i) => ({
@@ -27822,7 +28979,110 @@ export const VirtualizedScrollIntoViewExample: React.FC = () => {
27822
28979
  </Virtualizer>
27823
28980
  </Box>
27824
28981
  );
27825
- };`},similarTo:[],figmaUrl:null},Virtualizer:{id:"core-collections-virtualizer",breadcrumb:"Core/Collections/Virtualizer",importStatement:'import { Virtualizer } from "react-aria-components";',description:"`Virtualizer` renders large collections efficiently by keeping only the visible portion of the collection in the DOM. In baseline-ui it is typically paired with collection components such as `UNSAFE_ListBox`, `TreeView`, and `ImageGallery`.",documentation:`\`Virtualizer\` renders large collections efficiently by keeping only the visible portion of the collection in the DOM. In baseline-ui it is typically paired with collection components such as \`UNSAFE_ListBox\`, \`TreeView\`, and \`ImageGallery\`.
28982
+ };
28983
+
28984
+ // Each example varies exactly one prop off \`count\` and keeps the rest at stable
28985
+ // module-level identity; varying several at once would let one invalidate the cache for
28986
+ // the others, hiding a missing \`dependencies\` entry.
28987
+ const stableRenderOption = (item: ListOptionType) => <span>{item.label}</span>;
28988
+ const stableOptionClassName = () => "option-stable";
28989
+ const stableOptionStyle = () => ({ marginBlockStart: "0px" });
28990
+ const stableSectionClassName = () => "section-stable";
28991
+ const stableSectionStyle = () => ({ marginBlockStart: "0px" });
28992
+
28993
+ // Offset off the \`0px\` browser default so the pre-click assertion also proves the prop
28994
+ // reached the DOM, not just that it refreshed.
28995
+ const varyingMargin = (count: number) => ({
28996
+ marginBlockStart: \`\${count + 2}px\`,
28997
+ });
28998
+
28999
+ const CountingExample: React.FC<{
29000
+ children: (count: number) => React.ReactNode;
29001
+ }> = ({ children }) => {
29002
+ const [count, setCount] = React.useState(0);
29003
+
29004
+ return (
29005
+ <>
29006
+ <ActionButton
29007
+ label="Increment"
29008
+ onPress={() => {
29009
+ setCount(count + 1);
29010
+ }}
29011
+ />
29012
+ {children(count)}
29013
+ </>
29014
+ );
29015
+ };
29016
+
29017
+ export const CountingOptionListBoxExample: React.FC<{
29018
+ vary: "renderOption" | "optionClassName" | "optionStyle";
29019
+ // Options inside a section render through a second, nested collection that inherits the
29020
+ // root \`dependencies\` only via React Aria's collection context.
29021
+ sectioned?: boolean;
29022
+ }> = ({ vary, sectioned = false }) => (
29023
+ <CountingExample>
29024
+ {(count) => (
29025
+ <ListBox
29026
+ items={sectioned ? itemsWithSectionTitles : items}
29027
+ aria-label="Counting option list box"
29028
+ renderOption={
29029
+ vary === "renderOption"
29030
+ ? (item) => <span>{\`\${item.label} count-\${count}\`}</span>
29031
+ : stableRenderOption
29032
+ }
29033
+ optionClassName={
29034
+ vary === "optionClassName"
29035
+ ? () => \`option-count-\${count}\`
29036
+ : stableOptionClassName
29037
+ }
29038
+ optionStyle={
29039
+ vary === "optionStyle"
29040
+ ? () => varyingMargin(count)
29041
+ : stableOptionStyle
29042
+ }
29043
+ />
29044
+ )}
29045
+ </CountingExample>
29046
+ );
29047
+
29048
+ export const CountingSectionListBoxExample: React.FC<{
29049
+ vary:
29050
+ | "renderSectionHeader"
29051
+ | "sectionClassName"
29052
+ | "sectionStyle"
29053
+ | "showSectionHeader"
29054
+ | "withSectionHeaderPadding";
29055
+ }> = ({ vary }) => (
29056
+ <CountingExample>
29057
+ {(count) => (
29058
+ <ListBox
29059
+ items={itemsWithSectionTitles}
29060
+ aria-label="Counting section list box"
29061
+ // \`withSectionHeaderPadding\` only reaches the DOM through the default header, so
29062
+ // that variant must not supply a custom one.
29063
+ showSectionHeader={vary === "showSectionHeader" ? count > 0 : true}
29064
+ withSectionHeaderPadding={
29065
+ vary === "withSectionHeaderPadding" ? count > 0 : false
29066
+ }
29067
+ renderSectionHeader={
29068
+ vary === "renderSectionHeader"
29069
+ ? (section) => \`\${section.title} count-\${count}\`
29070
+ : undefined
29071
+ }
29072
+ sectionClassName={
29073
+ vary === "sectionClassName"
29074
+ ? () => \`section-count-\${count}\`
29075
+ : stableSectionClassName
29076
+ }
29077
+ sectionStyle={
29078
+ vary === "sectionStyle"
29079
+ ? () => varyingMargin(count)
29080
+ : stableSectionStyle
29081
+ }
29082
+ />
29083
+ )}
29084
+ </CountingExample>
29085
+ );`},similarTo:[],figmaUrl:null},Virtualizer:{id:"core-collections-virtualizer",breadcrumb:"Core/Collections/Virtualizer",importStatement:'import { Virtualizer } from "react-aria-components";',description:"`Virtualizer` renders large collections efficiently by keeping only the visible portion of the collection in the DOM. In baseline-ui it is typically paired with collection components such as `UNSAFE_ListBox`, `TreeView`, and `ImageGallery`.",documentation:`\`Virtualizer\` renders large collections efficiently by keeping only the visible portion of the collection in the DOM. In baseline-ui it is typically paired with collection components such as \`UNSAFE_ListBox\`, \`TreeView\`, and \`ImageGallery\`.
27826
29086
 
27827
29087
  * Efficient rendering for very large collections
27828
29088
  * Preset layout options for list boxes, tree views, and image galleries
@@ -30800,25 +32060,13 @@ export const PopoverDefaultOpenWithArrowExample = () => {
30800
32060
  );
30801
32061
  };
30802
32062
 
30803
- export const PopoverContainedFocusExample = () => {
30804
- return (
30805
- <Popover type="dialog">
30806
- <PopoverTrigger>
30807
- <ActionButton label="Open" />
30808
- </PopoverTrigger>
30809
- <PopoverContent shouldContainFocus={true}>
30810
- <Dialog
30811
- size="content"
30812
- className={sprinkles({
30813
- padding: "md",
30814
- display: "flex",
30815
- gap: "xl",
30816
- flexDirection: "column",
30817
- })}
30818
- style={{
30819
- width: 200,
30820
- }}
30821
- >
32063
+ export const PopoverContainedFocusExample = ({
32064
+ shouldContainFocus = true,
32065
+ }: {
32066
+ shouldContainFocus?: boolean;
32067
+ }) => {
32068
+ const content = (
32069
+ <>
30822
32070
  <Text type="label">The focus is contained within the popover.</Text>
30823
32071
 
30824
32072
  <TextInput
@@ -30830,10 +32078,55 @@ export const PopoverContainedFocusExample = () => {
30830
32078
  label="Button"
30831
32079
  style={{ width: "100%", justifyContent: "center" }}
30832
32080
  />
32081
+ </>
32082
+ );
32083
+ const contentClassName = sprinkles({
32084
+ padding: "md",
32085
+ display: "flex",
32086
+ gap: "xl",
32087
+ flexDirection: "column",
32088
+ });
32089
+
32090
+ const popover = (
32091
+ <Popover type="dialog">
32092
+ <PopoverTrigger>
32093
+ <ActionButton label="Open" />
32094
+ </PopoverTrigger>
32095
+ <PopoverContent
32096
+ shouldContainFocus={shouldContainFocus}
32097
+ isNonModal={!shouldContainFocus}
32098
+ >
32099
+ {shouldContainFocus ? (
32100
+ <Dialog
32101
+ size="content"
32102
+ className={contentClassName}
32103
+ style={{ width: 200 }}
32104
+ >
32105
+ {content}
30833
32106
  </Dialog>
32107
+ ) : (
32108
+ // No Dialog: useDialog opts the overlay into focus containment,
32109
+ // which disables the Tab-out restore path.
32110
+ <Box className={contentClassName} style={{ width: 200 }}>
32111
+ {content}
32112
+ </Box>
32113
+ )}
30834
32114
  </PopoverContent>
30835
32115
  </Popover>
30836
32116
  );
32117
+
32118
+ // The contained variant is the \`ContainedFocus\` story: a wrapper or sibling
32119
+ // here rebaselines its visual snapshot.
32120
+ if (shouldContainFocus) {
32121
+ return popover;
32122
+ }
32123
+
32124
+ return (
32125
+ <Box display="flex" flexDirection="column" gap="lg" alignItems="flex-start">
32126
+ {popover}
32127
+ <ActionButton label="After" />
32128
+ </Box>
32129
+ );
30837
32130
  };
30838
32131
 
30839
32132
  export const PopoverWithScrollableViewportExample: React.FC<{
@@ -31960,25 +33253,13 @@ export const PopoverDefaultOpenWithArrowExample = () => {
31960
33253
  );
31961
33254
  };
31962
33255
 
31963
- export const PopoverContainedFocusExample = () => {
31964
- return (
31965
- <Popover type="dialog">
31966
- <PopoverTrigger>
31967
- <ActionButton label="Open" />
31968
- </PopoverTrigger>
31969
- <PopoverContent shouldContainFocus={true}>
31970
- <Dialog
31971
- size="content"
31972
- className={sprinkles({
31973
- padding: "md",
31974
- display: "flex",
31975
- gap: "xl",
31976
- flexDirection: "column",
31977
- })}
31978
- style={{
31979
- width: 200,
31980
- }}
31981
- >
33256
+ export const PopoverContainedFocusExample = ({
33257
+ shouldContainFocus = true,
33258
+ }: {
33259
+ shouldContainFocus?: boolean;
33260
+ }) => {
33261
+ const content = (
33262
+ <>
31982
33263
  <Text type="label">The focus is contained within the popover.</Text>
31983
33264
 
31984
33265
  <TextInput
@@ -31990,10 +33271,55 @@ export const PopoverContainedFocusExample = () => {
31990
33271
  label="Button"
31991
33272
  style={{ width: "100%", justifyContent: "center" }}
31992
33273
  />
33274
+ </>
33275
+ );
33276
+ const contentClassName = sprinkles({
33277
+ padding: "md",
33278
+ display: "flex",
33279
+ gap: "xl",
33280
+ flexDirection: "column",
33281
+ });
33282
+
33283
+ const popover = (
33284
+ <Popover type="dialog">
33285
+ <PopoverTrigger>
33286
+ <ActionButton label="Open" />
33287
+ </PopoverTrigger>
33288
+ <PopoverContent
33289
+ shouldContainFocus={shouldContainFocus}
33290
+ isNonModal={!shouldContainFocus}
33291
+ >
33292
+ {shouldContainFocus ? (
33293
+ <Dialog
33294
+ size="content"
33295
+ className={contentClassName}
33296
+ style={{ width: 200 }}
33297
+ >
33298
+ {content}
31993
33299
  </Dialog>
33300
+ ) : (
33301
+ // No Dialog: useDialog opts the overlay into focus containment,
33302
+ // which disables the Tab-out restore path.
33303
+ <Box className={contentClassName} style={{ width: 200 }}>
33304
+ {content}
33305
+ </Box>
33306
+ )}
31994
33307
  </PopoverContent>
31995
33308
  </Popover>
31996
33309
  );
33310
+
33311
+ // The contained variant is the \`ContainedFocus\` story: a wrapper or sibling
33312
+ // here rebaselines its visual snapshot.
33313
+ if (shouldContainFocus) {
33314
+ return popover;
33315
+ }
33316
+
33317
+ return (
33318
+ <Box display="flex" flexDirection="column" gap="lg" alignItems="flex-start">
33319
+ {popover}
33320
+ <ActionButton label="After" />
33321
+ </Box>
33322
+ );
31997
33323
  };
31998
33324
 
31999
33325
  export const PopoverWithScrollableViewportExample: React.FC<{
@@ -32759,7 +34085,15 @@ iconTooltip?: ActionIconButtonProps["tooltip"]
32759
34085
  placeholder="Select"
32760
34086
  items={items}
32761
34087
  isReadOnly
32762
- value="square" />;`}],implementation:`import { EllipseIcon } from "@baseline-ui/icons/24";
34088
+ value="square" />;`},{id:"core-forms-select-iconselect--without-icon",name:"Without Icon",snippet:`const WithoutIcon = () => <IconSelect
34089
+ aria-label="Choose Stroke Style"
34090
+ placeholder="Select"
34091
+ items={items}
34092
+ icon={undefined} />;`},{id:"core-forms-select-iconselect--with-default-open",name:"With Default Open",snippet:`const WithDefaultOpen = () => <IconSelect
34093
+ aria-label="Choose Stroke Style"
34094
+ placeholder="Select"
34095
+ items={items}
34096
+ defaultOpen />;`}],implementation:`import { EllipseIcon } from "@baseline-ui/icons/24";
32763
34097
  import React from "react";
32764
34098
  import { useFilter } from "react-aria";
32765
34099
  import { Autocomplete, Virtualizer } from "react-aria-components";
@@ -32768,11 +34102,16 @@ import { useListData } from "react-stately";
32768
34102
  import { ActionButton } from "../../ActionButton";
32769
34103
  import { items } from "../../ListBox/__tests__/testComponents";
32770
34104
  import { virtualizeAutocompleteItems } from "../../Menu/__tests__/data";
32771
- import { VIRTUALIZER_LAYOUT_DEFAULT_OPTIONS } from "../../Virtualizer";
34105
+ import { itemsWithSectionTitles } from "../../UNSAFE_ListBox/__tests__/testComponents";
34106
+ import {
34107
+ ListLayout,
34108
+ VIRTUALIZER_LAYOUT_DEFAULT_OPTIONS,
34109
+ } from "../../Virtualizer";
32772
34110
  import { IconSelect } from "../IconSelect";
32773
34111
  import { Select } from "../Select";
32774
34112
 
32775
34113
  import type { IconSelectProps } from "../Select.types";
34114
+ import type { Key } from "@react-types/shared";
32776
34115
 
32777
34116
  export const SelectExample: React.FC<
32778
34117
  Omit<React.ComponentProps<typeof Select>, "items">
@@ -32780,13 +34119,28 @@ export const SelectExample: React.FC<
32780
34119
  return (
32781
34120
  <Select
32782
34121
  placeholder="Choose an item"
32783
- {...args}
32784
34122
  optionClassName={(item) => item.label}
34123
+ {...args}
32785
34124
  items={items}
32786
34125
  />
32787
34126
  );
32788
34127
  };
32789
34128
 
34129
+ // "Apples" is a substring of "Pineapples" \u2014 exercises exact string matching
34130
+ // in the tester's option() locator.
34131
+ const substringItems = [
34132
+ { id: "apples", label: "Apples" },
34133
+ { id: "pineapples", label: "Pineapples" },
34134
+ ];
34135
+
34136
+ export const SelectSubstringLabelsExample: React.FC<
34137
+ Omit<React.ComponentProps<typeof Select>, "items">
34138
+ > = (args) => {
34139
+ return (
34140
+ <Select placeholder="Choose an item" {...args} items={substringItems} />
34141
+ );
34142
+ };
34143
+
32790
34144
  export const SelectGetTargetRectExample: React.FC = () => {
32791
34145
  const [called, setCalled] = React.useState(false);
32792
34146
 
@@ -32843,19 +34197,136 @@ export const SelectCustomTriggerExample: React.FC<
32843
34197
  };
32844
34198
 
32845
34199
  export const IconSelectExample: React.FC<
32846
- Omit<IconSelectProps, "items" | "icon" | "aria-label">
34200
+ Omit<IconSelectProps, "items" | "aria-label">
32847
34201
  > = (args) => {
32848
34202
  return (
32849
34203
  <IconSelect
32850
34204
  placeholder="Choose an item"
34205
+ icon={EllipseIcon}
32851
34206
  {...args}
32852
34207
  items={items}
32853
- icon={EllipseIcon}
32854
34208
  aria-label="Aria Label"
32855
34209
  />
32856
34210
  );
32857
34211
  };
32858
34212
 
34213
+ export const VirtualSelectWithSectionHeadersExample: React.FC<
34214
+ Omit<React.ComponentProps<typeof Select>, "items">
34215
+ > = (args) => {
34216
+ return (
34217
+ <Virtualizer
34218
+ layout={ListLayout}
34219
+ layoutOptions={{
34220
+ rowHeight: 36,
34221
+ headingHeight: 40,
34222
+ }}
34223
+ >
34224
+ <Select
34225
+ aria-label="Choose an item"
34226
+ placeholder="Choose an item"
34227
+ showSectionHeader={true}
34228
+ {...args}
34229
+ items={itemsWithSectionTitles}
34230
+ defaultOpen={true}
34231
+ maxHeight={400}
34232
+ />
34233
+ </Virtualizer>
34234
+ );
34235
+ };
34236
+
34237
+ export const SelectWithSectionHeadersExample: React.FC<
34238
+ Omit<React.ComponentProps<typeof Select>, "items">
34239
+ > = (args) => {
34240
+ return (
34241
+ <Select
34242
+ aria-label="Choose an item"
34243
+ placeholder="Choose an item"
34244
+ showSectionHeader={true}
34245
+ {...args}
34246
+ items={itemsWithSectionTitles}
34247
+ defaultOpen={true}
34248
+ />
34249
+ );
34250
+ };
34251
+
34252
+ /**
34253
+ * Owns \`isOpen\` so the controlled-open contract can be driven from outside the
34254
+ * Select, with a sibling readout of the current state.
34255
+ */
34256
+ export const ControlledOpenSelectExample: React.FC<
34257
+ Omit<React.ComponentProps<typeof Select>, "items">
34258
+ > = (args) => {
34259
+ const [isOpen, setIsOpen] = React.useState(false);
34260
+
34261
+ return (
34262
+ <>
34263
+ <Select
34264
+ aria-label="Choose an item"
34265
+ placeholder="Choose an item"
34266
+ {...args}
34267
+ items={items}
34268
+ isOpen={isOpen}
34269
+ onOpenChange={setIsOpen}
34270
+ />
34271
+ <button
34272
+ data-testid="open-externally"
34273
+ onClick={() => {
34274
+ setIsOpen(true);
34275
+ }}
34276
+ >
34277
+ Open
34278
+ </button>
34279
+ <span data-testid="open-state">{String(isOpen)}</span>
34280
+ </>
34281
+ );
34282
+ };
34283
+
34284
+ /**
34285
+ * Custom trigger that renders the multi-select half of the \`renderTrigger\`
34286
+ * payload \u2014 \`selectionMode\`, \`maxCount\` and \`onRemove\` \u2014 which the default
34287
+ * \`SelectButton\` otherwise keeps to itself.
34288
+ */
34289
+ export const MultiSelectCustomTriggerExample: React.FC<
34290
+ Omit<React.ComponentProps<typeof Select>, "items">
34291
+ > = (args) => {
34292
+ return (
34293
+ <Select
34294
+ aria-label="Choose an item"
34295
+ selectionMode="multiple"
34296
+ {...args}
34297
+ items={items}
34298
+ renderTrigger={({
34299
+ buttonProps,
34300
+ ref,
34301
+ selectedValue,
34302
+ selectionMode,
34303
+ onRemove,
34304
+ maxCount,
34305
+ }) => (
34306
+ <div>
34307
+ <ActionButton
34308
+ {...buttonProps}
34309
+ ref={ref}
34310
+ label={\`\${selectionMode}|\${maxCount}|\${selectedValue?.length ?? 0}\`}
34311
+ variant="popover"
34312
+ />
34313
+ <button
34314
+ data-testid="remove-first"
34315
+ onClick={() => {
34316
+ const first = selectedValue?.[0];
34317
+ if (first) {
34318
+ onRemove?.(new Set([first.id]));
34319
+ }
34320
+ }}
34321
+ >
34322
+ Remove first
34323
+ </button>
34324
+ </div>
34325
+ )}
34326
+ />
34327
+ );
34328
+ };
34329
+
32859
34330
  export const SelectWithVirtualizeAutocompleteExample: React.FC<
32860
34331
  Omit<React.ComponentProps<typeof Select>, "items">
32861
34332
  > = (args) => {
@@ -32879,6 +34350,27 @@ export const SelectWithVirtualizeAutocompleteExample: React.FC<
32879
34350
  </Autocomplete>
32880
34351
  </Virtualizer>
32881
34352
  );
34353
+ };
34354
+
34355
+ /**
34356
+ * \`optionClassName\` reads component state, and \`items\` is module-level so React Aria's
34357
+ * per-item element cache applies. Pinned open so the state change comes from selecting an
34358
+ * option rather than from an outside press the popover would swallow.
34359
+ */
34360
+ export const CountingSelectExample: React.FC = () => {
34361
+ const [selected, setSelected] = React.useState<Key | null>(null);
34362
+
34363
+ return (
34364
+ <Select
34365
+ aria-label="Counting select"
34366
+ items={items}
34367
+ isOpen={true}
34368
+ optionClassName={(item) =>
34369
+ item.id === selected ? "option-picked" : "option-plain"
34370
+ }
34371
+ onSelectionChange={setSelected}
34372
+ />
34373
+ );
32882
34374
  };`},similarTo:[],figmaUrl:null},IconSlider:{id:"core-forms-slider-iconslider",importStatement:'import { IconSlider } from "@baseline-ui/core";',description:"",documentation:null,props:`interface IconSliderProps {
32883
34375
  /**
32884
34376
  * @deprecated Do not use in new components. This is a legacy block
@@ -32963,7 +34455,7 @@ popoverContentProps?: Omit<PopoverContentProps, "children">
32963
34455
  formatValue={(value: number) => \`\${value} pt\`}
32964
34456
  aria-label="Icon slider"
32965
34457
  onTriggerPress={fn()}
32966
- isDisabled />;`}],implementation:""},similarTo:[],figmaUrl:null}};var i={gettingStarted:`# Getting started
34458
+ isDisabled />;`}],implementation:""},similarTo:[],figmaUrl:null}};var o={gettingStarted:`# Getting started
32967
34459
 
32968
34460
  Install the packages required for using Baseline UI with the following command:
32969
34461
 
@@ -32989,6 +34481,21 @@ import { ActionButton, I18nProvider, ThemeProvider } from "@baseline-ui/core";
32989
34481
 
32990
34482
  It\u2019s important to wrap your application with the \`ThemeProvider\` and \`I18nProvider\` components. The \`ThemeProvider\` component provides the theme to all the components in the tree, and the \`I18nProvider\` component provides the locale to all the components in the tree.
32991
34483
 
34484
+ ### Translations
34485
+
34486
+ Components render English out of the box. Translations ship as one JSON file per locale so projects that only need English don\u2019t pay for the other 32 \u2014 import the ones you need and pass them to \`I18nProvider\`:
34487
+
34488
+ \`\`\`jsx
34489
+ import { I18nProvider } from "@baseline-ui/core";
34490
+ import de from "@baseline-ui/core/intl/de.json";
34491
+
34492
+ <I18nProvider locale="de-DE" messages={{ de }}>
34493
+ <YourApp />
34494
+ </I18nProvider>;
34495
+ \`\`\`
34496
+
34497
+ Any string a catalog omits falls back to English. See [Internationalization](?path=/docs/internationalization--docs) for the full list of locales and message IDs, lazy loading, and overriding individual strings.
34498
+
32992
34499
  ### Styling
32993
34500
 
32994
34501
  Import the following files at the top of your stylesheet to use the Baseline UI styles:
@@ -33257,22 +34764,24 @@ function App() {
33257
34764
  }
33258
34765
  \`\`\``,internationalization:`# Internationalization
33259
34766
 
33260
- It\u2019s important the content of your website is accessible to as many people as possible. This includes people who speak different languages and people who use screen readers. Internationalization (i18n) is the process of making your website accessible to people who speak different languages. Baseline UI supports internationalization for many of the components out of the box. This includes translation for the built-in strings, localization of dates and times, number formatting, and currency formatting.
34767
+ It\u2019s important the content of your website is accessible to as many people as possible. This includes people who speak different languages and people who use screen readers. Internationalization (i18n) is the process of making your website accessible to people who speak different languages. Baseline UI supports internationalization for many of the components out of the box: localization of dates and times, number formatting, currency formatting, collation, and text direction all follow the active locale with no setup.
34768
+
34769
+ Translated strings are the one part you opt into. Components render English until you import a catalog and pass it to \`I18nProvider\`, so an application that ships in English carries none of the other locales in its bundle. See [Loading a translation catalog](#loading-a-translation-catalog).
33261
34770
 
33262
34771
  ## Localization
33263
34772
 
33264
- Localization is the process of translating your website into different languages. Baseline UI supports localization of the built-in strings, dates and times, number/currency formatting, collating and sorting, text search, and more. It\u2019s built on top of [react-aria](https://react-spectrum.adobe.com/react-aria), which provides translations of built-in strings for many languages. You can also provide your own translations for custom strings.
34773
+ Localization is the process of translating your website into different languages. Baseline UI supports localization of the built-in strings, dates and times, number/currency formatting, collating and sorting, text search, and more. It\u2019s built on top of [react-aria](https://react-spectrum.adobe.com/react-aria), whose own strings \u2014 the announcements a date picker or a drag handle makes to a screen reader \u2014 are bundled and translated for you. Baseline UI\u2019s strings are the ones you load a catalog for, and you can add your own on top.
33265
34774
 
33266
- Baseline UI automatically detects a user\u2019s preferred language from the browser, and it uses that language for all localization. It\u2019s possible to override the language used for localization by providing a \`locale\` prop to the \`Provider\` component.
34775
+ Baseline UI automatically detects a user\u2019s preferred language from the browser, and it uses that language for all localization. It\u2019s possible to override the language used for localization by providing a \`locale\` prop to the \`I18nProvider\` component.
33267
34776
 
33268
- See [\`useMessageFormatter\`](https://react-spectrum.adobe.com/react-aria/useMessageFormatter.html), [\`DateFormat\`](/?path=/docs/utilities-dateformat--docs), [\`NumberFormat\`](/?path=/docs/utilities-numberformat--docs), and [\`useCollator\`](https://react-spectrum.adobe.com/react-aria/useCollator.html) for more information about using our internationalization hooks.
34777
+ See [\`useMessageFormatter\`](https://react-spectrum.adobe.com/react-aria/useMessageFormatter.html), [\`DateFormat\`](/?path=/docs/core-utilities-dateformat--docs), [\`NumberFormat\`](/?path=/docs/core-utilities-numberformat--docs), and [\`useCollator\`](https://react-spectrum.adobe.com/react-aria/useCollator.html) for more information about using our internationalization hooks.
33269
34778
 
33270
34779
  ### Example
33271
34780
 
33272
- The rootmost element of your application should define the [\`lang\`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) and [\`dir\`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir) attributes so that the browser knows which language and direction the user interface should be rendered in. This can be done with the \`useLocale\` hook from \`../../utils\`:
34781
+ The rootmost element of your application should define the [\`lang\`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) and [\`dir\`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir) attributes so that the browser knows which language and direction the user interface should be rendered in. This can be done with the \`useLocale\` hook:
33273
34782
 
33274
34783
  \`\`\`jsx
33275
- import { useLocale } from "../../utils";
34784
+ import { useLocale } from "@baseline-ui/core";
33276
34785
 
33277
34786
  function YourApp() {
33278
34787
  let { locale, direction } = useLocale();
@@ -33285,38 +34794,242 @@ function YourApp() {
33285
34794
  }
33286
34795
  \`\`\`
33287
34796
 
33288
- You can also override the language used for localization with the [\`I18nProvider\`](/?path=/docs/utilities-i18nprovider--docs) component:
34797
+ You can also override the language used for localization with the [\`I18nProvider\`](/?path=/docs/core-utilities-i18nprovider--docs) component:
33289
34798
 
33290
34799
  \`\`\`jsx
33291
- import { I18nProvider } from "../../utils";
34800
+ import { I18nProvider } from "@baseline-ui/core";
33292
34801
 
33293
34802
  <I18nProvider locale="fr-FR">
33294
34803
  <YourApp />
33295
34804
  </I18nProvider>;
33296
34805
  \`\`\`
33297
34806
 
34807
+ Setting \`locale\` alone switches date, number and collation formatting, and flips text direction for RTL languages. It does not translate Baseline UI\u2019s own strings \u2014 for that, load a catalog.
34808
+
34809
+ ### Loading a translation catalog
34810
+
34811
+ Import the catalogs for the languages you ship and hand them to \`I18nProvider\`:
34812
+
34813
+ \`\`\`jsx
34814
+ import { I18nProvider } from "@baseline-ui/core";
34815
+ import de from "@baseline-ui/core/intl/de.json";
34816
+ import fr from "@baseline-ui/core/intl/fr.json";
34817
+
34818
+ // Hoisted out of render: a fresh object each render rebuilds the message
34819
+ // formatter, so every string reformats on every pass.
34820
+ const messages = { de, fr };
34821
+
34822
+ <I18nProvider locale="de-DE" messages={messages}>
34823
+ <YourApp />
34824
+ </I18nProvider>;
34825
+ \`\`\`
34826
+
34827
+ Each file is a flat map of \`bui.*\` ID to translated string, so your bundler includes only
34828
+ the locales you actually import. The key you file a catalog under is matched against the
34829
+ active locale exactly first, then by language, then by any other region of that language \u2014
34830
+ so \`{ de }\` answers for \`de\`, \`de-DE\` and \`de-AT\` alike, and \`{ de, "de-AT" }\` under
34831
+ \`de-AT\` uses the Austrian entry.
34832
+
34833
+ TypeScript needs \`resolveJsonModule\` enabled to import the files, and Node ESM needs a
34834
+ \`with { type: "json" }\` import attribute.
34835
+
34836
+ Key each catalog with a BCP 47 locale ID. An underscored key (\`de_DE\`) is read as its
34837
+ hyphenated form, so both spellings resolve \u2014 but supplying the same locale under both is
34838
+ reported once, and the hyphenated entry wins.
34839
+
34840
+ To avoid shipping every language up front, put each catalog behind a dynamic \`import()\`
34841
+ and load one when the user picks a language. Write the specifiers out in full \u2014 a
34842
+ template literal over a bare package subpath is not statically analyzable, so bundlers
34843
+ cannot split on it:
34844
+
34845
+ \`\`\`jsx
34846
+ const catalogs = {
34847
+ de: () => import("@baseline-ui/core/intl/de.json"),
34848
+ fr: () => import("@baseline-ui/core/intl/fr.json"),
34849
+ };
34850
+
34851
+ const [messages, setMessages] = useState({});
34852
+
34853
+ async function selectLocale(locale) {
34854
+ const catalog = await catalogs[locale]();
34855
+
34856
+ setMessages({ [locale]: catalog.default });
34857
+ }
34858
+ \`\`\`
34859
+
34860
+ An ID your catalog omits falls back to that component's English \`defaultMessage\`, so a
34861
+ partial catalog is a valid one \u2014 you are never forced to translate a string before you
34862
+ ship it. Outside production, each such ID is reported once in the console.
34863
+
34864
+ If you supply catalogs but none of them matches the active locale \u2014 \`{ de }\` while the
34865
+ locale is \`ja-JP\`, say \u2014 every string falls back to English and that is reported once for
34866
+ the locale rather than once per ID. It means the catalog keys and the locale disagree,
34867
+ which no per-ID warning can tell you. It stays quiet when the active locale is itself
34868
+ English, since falling back to English is then the intended result. Supplying no catalogs
34869
+ at all is the supported English-only mode and stays silent.
34870
+
34871
+ Only \`bui.*\` IDs are reported. An app that routes its own strings through
34872
+ [\`useI18n\`](#your-own-strings) is missing no translation Baseline UI can tell it how to add.
34873
+
34874
+ ### Controlling the warnings
34875
+
34876
+ The warnings above \u2014 the missing-ID, no-matching-catalog, duplicate-locale-key and
34877
+ [bare-ID deprecation](#migrating-from-bare-ids) ones \u2014 are development guidance, so they
34878
+ are off when \`process.env.NODE_ENV === "production"\`. That keeps a consumer who never
34879
+ touched \`messages\` from logging Baseline UI's internal IDs to their end users. Set
34880
+ \`shouldLogMissingMessages\` to force them either way:
34881
+
34882
+ \`\`\`jsx
34883
+ // Surface them in a production build \u2014 e.g. a staging deploy checking catalog coverage.
34884
+ <I18nProvider locale="de-DE" messages={messages} shouldLogMissingMessages>
34885
+ <YourApp />
34886
+ </I18nProvider>;
34887
+
34888
+ // Or silence them in development, while a catalog is still being filled in.
34889
+ <I18nProvider
34890
+ locale="de-DE"
34891
+ messages={messages}
34892
+ shouldLogMissingMessages={false}
34893
+ >
34894
+ <YourApp />
34895
+ </I18nProvider>;
34896
+ \`\`\`
34897
+
34898
+ Each distinct warning is logged once and reset when the locale changes, so switching
34899
+ languages re-reports what the new locale is missing.
34900
+
34901
+ ### Your own strings
34902
+
34903
+ \`useI18n\` is the same hook the components use, so you can put your application's strings
34904
+ through it and get one formatter for both:
34905
+
34906
+ \`\`\`tsx
34907
+ import { defineMessages, useI18n } from "@baseline-ui/core";
34908
+
34909
+ const messages = defineMessages({
34910
+ greeting: {
34911
+ id: "myApp.greeting",
34912
+ defaultMessage: "Hello, {name}",
34913
+ description:
34914
+ "Greeting on the dashboard header. {name} is the user's first name.",
34915
+ },
34916
+ });
34917
+
34918
+ // Hoisted for the same reason as the catalogs above.
34919
+ const catalogs = { de: { "myApp.greeting": "Hallo, {name}" } };
34920
+
34921
+ function Header({ name }) {
34922
+ const { formatMessage } = useI18n(catalogs);
34923
+
34924
+ return <h1>{formatMessage(messages.greeting, { name })}</h1>;
34925
+ }
34926
+ \`\`\`
34927
+
34928
+ The catalogs you pass to \`useI18n\` are merged per locale with the ones on \`I18nProvider\`,
34929
+ and the provider wins on a shared key \u2014 so an application can hold its own strings locally
34930
+ while a host app still overrides any \`bui.*\` string from above.
34931
+
33298
34932
  ### Supported locales
33299
34933
 
33300
- The following locales are supported:
34934
+ A catalog ships for each of these locales, with an entry for every message ID:
34935
+
34936
+ ### Message IDs
34937
+
34938
+ Every string Baseline UI can render is keyed by a \`bui.<component>.<key>\` ID. The same
34939
+ list is exported as a value, so you can derive translation coverage in a test instead of
34940
+ committing a copy that goes stale on the next upgrade:
34941
+
34942
+ \`\`\`ts
34943
+ import { messageIds } from "@baseline-ui/core";
34944
+
34945
+ const uncovered = messageIds.filter((id) => !(id in ourCatalog.en));
34946
+ \`\`\`
33301
34947
 
33302
- ### Translations list
34948
+ Below is every ID with the English string it renders. For the translator context that goes
34949
+ with each one, read \`@baseline-ui/core/messages.json\` \u2014 the extracted source messages keyed
34950
+ by ID.
33303
34951
 
33304
- This list includes the IDs for all of the translations in the \`messages\` object for the \`en\` locale. This can be used to find the ID of a translation to use in the \`formatMessage\` function:
34952
+ ### Translating them yourself
34953
+
34954
+ If you localize the \`bui.*\` strings in your own pipeline rather than shipping our catalogs,
34955
+ hand your translators \`@baseline-ui/core/messages.json\` \u2014 the extracted source messages,
34956
+ keyed by ID and shaped the way a TMS expects:
34957
+
34958
+ \`\`\`json
34959
+ {
34960
+ "bui.select.more": {
34961
+ "defaultMessage": "+{count} more",
34962
+ "description": "Tag shown on the select trigger standing in for the selected options that did not fit \u2014 renders as '+3 more'. ..."
34963
+ }
34964
+ }
34965
+ \`\`\`
34966
+
34967
+ The \`description\` is the context a translator needs and cannot infer from the string
34968
+ alone \u2014 part of speech, sense, and where it renders. Translating \`bui.editor.mention\`
34969
+ without it yields the verb "to mention" where the noun label belongs, and
34970
+ \`bui.editor.shortcut\` becomes a desktop shortcut file rather than a key combination.
33305
34971
 
33306
34972
  ## Overriding Component Translations
33307
34973
 
33308
34974
  You can override any built-in component string by passing its \`bui.*\`-namespaced key to
33309
34975
  \`I18nProvider.messages\`. Only the keys you provide are overridden \u2014 all other strings
33310
- fall back to each component's built-in defaults. You can also supply strings for locales
33311
- that a component does not ship with natively.
34976
+ fall back to each component's English \`defaultMessage\`. Spread a shipped catalog and
34977
+ override on top of it to change a single string:
34978
+
34979
+ \`\`\`jsx
34980
+ import de from "@baseline-ui/core/intl/de.json";
34981
+
34982
+ <I18nProvider
34983
+ locale="de-DE"
34984
+ messages={{ de: { ...de, "bui.imageDropZone.selectImage": "Bild w\xE4hlen" } }}
34985
+ >
34986
+ <YourApp />
34987
+ </I18nProvider>;
34988
+ \`\`\`
33312
34989
 
33313
34990
  Each component's documentation lists its overridable keys and shows an example. See:
33314
34991
 
33315
- * [Pagination](?path=/docs/components-pagination--docs#translations)
33316
- * [ColorInput](?path=/docs/components-colorinput--docs#translations)
33317
- * [ImageDropZone](?path=/docs/components-imagedropzone--docs#translations)
33318
- * [FreehandCanvas](?path=/docs/components-freehandcanvas--docs#translations)
33319
- * [InlineAlert](?path=/docs/components-inlinealert--docs#translations)`,styling:`# Styling with Sprinkles
34992
+ * [Pagination](?path=/docs/core-forms-pagination--docs#translations)
34993
+ * [ColorInput](?path=/docs/core-forms-colorinput--docs#translations)
34994
+ * [ImageDropZone](?path=/docs/core-forms-imagedropzone--docs#translations)
34995
+ * [FreehandCanvas](?path=/docs/core-miscellaneous-freehandcanvas--docs#translations)
34996
+ * [InlineAlert](?path=/docs/core-status-inlinealert--docs#translations)
34997
+
34998
+ ### Migrating from bare IDs
34999
+
35000
+ Older catalogs key off the bare tail of an ID (\`close\` rather than \`bui.drawer.close\`).
35001
+ That still resolves, with a deprecation warning, but it will be removed in a future
35002
+ major \u2014 and it is ambiguous: several bare IDs are claimed by more than one component,
35003
+ so a single entry silently translates all of them. That happens to work in English, but
35004
+ it forecloses any language where the word inflects differently per context.
35005
+
35006
+ \`legacyMessageIdMap\` maps each bare ID to every namespaced ID that claims it, so a catalog
35007
+ can be re-keyed mechanically. Keep each original key alongside the IDs it maps to \u2014 a bare
35008
+ key may also be one your own app looks up, and Baseline UI resolves the namespaced entry
35009
+ either way:
35010
+
35011
+ \`\`\`ts
35012
+ import { legacyMessageIdMap } from "@baseline-ui/core";
35013
+
35014
+ const migrated = Object.fromEntries(
35015
+ Object.entries(oldCatalog).flatMap(([key, value]) => [
35016
+ [key, value],
35017
+ ...(legacyMessageIdMap[key] ?? []).map((id) => [id, value]),
35018
+ ]),
35019
+ );
35020
+ \`\`\`
35021
+
35022
+ Review the ambiguous ones by hand \u2014 each deserves its own translation:
35023
+
35024
+ | Bare ID | Also translates |
35025
+ | ----------- | ---------------------------------------------------------------------------------------- |
35026
+ | \`close\` | \`bui.alertDialog.close\`, \`bui.drawer.close\`, \`bui.editor.close\`, \`bui.inlineAlert.close\` |
35027
+ | \`more\` | \`bui.buttonSelect.more\`, \`bui.select.more\`, \`bui.toolbar.more\` |
35028
+ | \`cancel\` | \`bui.colorInput.cancel\`, \`bui.editor.cancel\`, \`bui.imageGallery.cancel\` |
35029
+ | \`redo\` | \`bui.editor.redo\`, \`bui.freehandCanvas.redo\` |
35030
+ | \`selectAll\` | \`bui.editor.selectAll\`, \`bui.select.selectAll\` |
35031
+ | \`undo\` | \`bui.editor.undo\`, \`bui.freehandCanvas.undo\` |
35032
+ | \`loading\` | \`bui.fileList.loading\`, \`bui.imageGallery.loading\`, \`bui.table.loading\` |`,styling:`# Styling with Sprinkles
33320
35033
 
33321
35034
  Baseline UI provides a powerful, type-safe styling system built on top of [vanilla-extract sprinkles](https://vanilla-extract.style/documentation/packages/sprinkles/).
33322
35035
 
@@ -34226,7 +35939,7 @@ padding={[null, "lg", "xl"]}
34226
35939
 
34227
35940
  * [vanilla-extract sprinkles documentation](https://vanilla-extract.style/documentation/packages/sprinkles/) - Learn about the underlying sprinkles framework
34228
35941
  * [Box component documentation](/docs/core-utilities-box--docs) - Detailed information about the Box component
34229
- * [Theme documentation](/docs/theming--docs) - Learn about Baseline UI's theming system`};var c={"8":["CaretDownIcon","CaretLeftIcon","CaretRightIcon","CaretUpIcon","ChevronRightFilledIcon","ChevronRightIcon","EllipseIcon","MinusIcon","PlusIcon","XIcon"],"12":["CaretDownIcon","CaretLeftIcon","CaretRightIcon","CaretUpIcon","CheckmarkIcon","DragIndicatorIcon","DragIndicatorVerticalIcon","EditIcon","EllipseIcon","EnterKeyIcon","LockFilledIcon","LockIcon","MinusIcon","MoreVIcon","MoreIcon","PlaceholderIcon","PlusIcon","SearchIcon","SizeIcon","TrashIcon","XIcon","ZoomIcon"],"16":["AlignBottomIcon","AlignMiddleIcon","AlignTopIcon","AnonymousIcon","ArrowDiagonalTopLeftBottomRightIcon","ArrowDownCircleFilledIcon","ArrowDownIcon","ArrowIcon","ArrowLeftRightIcon","ArrowRightIcon","ArrowUpArrowDownIcon","ArrowUpIcon","AtIcon","AttachmentsIcon","AvatarIcon","BoldIcon","BookmarkFilledIcon","BookmarkIcon","BulletListIcon","CalendarIcon","CaretLeftIcon","CaretRightIcon","CheckmarkCircleFilledIcon","CheckmarkCircleIcon","CheckmarkIcon","CircleFilledIcon","ClockIcon","CopyIcon","CustomizeIcon","DocumentEditIcon","DownloadIcon","DuplicateIcon","EditIcon","ElipseAreaIcon","EllipseCloudyIcon","EllipseDashedIcon","EllipseIcon","EmbedIcon","EmojiSmileIcon","ErrorAltCircleFilledIcon","ErrorCircleFilledIcon","ErrorCircleIcon","ExpandIcon","FilterAltIcon","FolderIcon","FormButtonIcon","FormChoiceIcon","FormComboboxIcon","FormDateIcon","FormListboxIcon","FormRadioButtonIcon","FormSignatureIcon","FormTextFieldIcon","FullScreenIcon","HelpCircleIcon","HelpIcon","HereIcon","HideIcon","HighlightTextAltIcon","HighlightTextIcon","HorizontalScrollIcon","ImageIcon","InfoCircleFilledIcon","InsertIcon","ItalicIcon","LightBulbIcon","LineIcon","LinkIcon","ListIcon","LockIcon","MagicIcon","MeasureIcon","MinusIcon","MoreIcon","MoreVerticalIcon","NoteArrowRightIcon","NoteCheckIcon","NoteCircleIcon","NoteCloudIcon","NoteCrossIcon","NoteHelpIcon","NoteInsetIcon","NoteKeyIcon","NoteNewParagraphAltIcon","NoteNewParagraphIcon","NoteNoteIcon","NotePointerRightIcon","NoteSpeechBubbleIcon","NoteStarIcon","NumberedListIcon","OpenIcon","PageFittingFillIcon","PageFittingFitIcon","PageHorizontalScrollIcon","PageLayoutDoubleIcon","PageLayoutSingleIcon","PageVerticalScrollIcon","PauseIcon","PenHighlighterIcon","PenIcon","PerimeterIcon","PlaceholderIcon","PlayIcon","PlusIcon","PolygonAreaIcon","PolygonCloudyIcon","PolygonDashedIcon","PolygonIcon","PolylineIcon","ReadOnlyIcon","RectangleAreaIcon","RectangleCloudyIcon","RectangleDashedIcon","RectangleIcon","RedoIcon","RemoveFormattingIcon","ReorderIcon","RotateClockwiseIcon","RotateCounterClockwiseIcon","RulerIcon","SearchIcon","SettingsIcon","ShowIcon","SlashCommandsIcon","SoundRecordIcon","StampIcon","StarFilledIcon","StarIcon","StrikeoutTextAltIcon","TableCellIcon","TableColumnIcon","TableHeaderIcon","TableIcon","TableRowIcon","TextAlignCenterIcon","TextAlignJustifyIcon","TextAlignLeftIcon","TextAlignRightIcon","TextCalloutIcon","TextDecreaseIndentIcon","TextIcon","TextIncreaseIndentIcon","TextMarkIcon","ThumbnailsIcon","ThumbsDownIcon","ThumbsUpIcon","TrashIcon","TypeTextIcon","UnderlineIcon","UndoIcon","UnlockIcon","VerticalScrollIcon","VideoIcon","WarningFilledIcon","WarningIcon","WindowedIcon","WorkflowIcon","XCircleFilledIcon","XIcon"],"20":["AddPageIcon","AnonymousIcon","ArrowLeftIcon","ArrowRightIcon","ArrowUpCircleFilledIcon","AtIcon","AvatarFilledIcon","BoldIcon","CalloutIcon","CaretDownIcon","CaretLeftIcon","CaretRightIcon","CaretUpIcon","CheckCircleFilledIcon","CheckmarkCircleIcon","CheckmarkIcon","ClockIcon","CollapseIcon","CommentIcon","CopyIcon","CutIcon","DistanceIcon","DownloadIcon","DuplicateIcon","EditIcon","EllipseIcon","EmojiSmileIcon","ErrorAltCircleFilledIcon","ErrorAlternativeCircleIcon","ErrorCircleFilledIcon","ErrorCircleIcon","ExpandIcon","FormDateIcon","FormSignatureIcon","FormTextFieldIcon","HelpCircleIcon","HighlightTextIcon","HomeIcon","ImageIcon","InfoCircleFilledIcon","InfoCircleIcon","ItalicIcon","LinkIcon","ListIcon","LockIcon","MagicIcon","MinusIcon","MoreIcon","MoreVerticalIcon","MoveIcon","NoteArrowRightIcon","NoteCheckIcon","NoteCircleIcon","NoteCrossIcon","NoteHelpIcon","NoteInsetIcon","NoteKeyIcon","NoteNewParagraphAltIcon","NoteNewParagraphIcon","NoteNoteIcon","NotePointerRightIcon","NoteSpeechBubbleIcon","NoteStarIcon","OpenIcon","PageMoveLeftIcon","PageMoveRightIcon","PagesInsertIcon","PasteIcon","PipetteIcon","PlusIcon","PrintIcon","RotateClockwiseIcon","SearchIcon","SettingsIcon","ShapeIcon","ShareIcon","SoundIcon","SoundRecordIcon","StarFilledIcon","StarIcon","StyleIcon","TextAlignCenterIcon","TextAlignJustifyIcon","TextAlignLeftIcon","TextAlignRightIcon","TextIcon","ThumbnailsIcon","ThumbsDownIcon","ThumbsUpIcon","TrashIcon","TypeTextIcon","UnderlineIcon","UploadIcon","WarningFilledIcon","WarningIcon","XCircleFilledIcon","XCircleIcon","XIcon"],"24":["AddNoteCloudIcon","AddNoteIcon","AddTextSerifIcon","AiIcon","AirplaneIcon","AlignBottomIcon","AlignHorizontalCenterIcon","AlignMiddleIcon","AlignTopIcon","AnonymousIcon","ArrowDownIcon","ArrowIcon","ArrowLeftIcon","ArrowRightIcon","ArrowUpIcon","AtIcon","AttachmentIcon","AvatarFilledIcon","AvatarIcon","BlendModeIcon","BoldIcon","BookmarkFilledIcon","BookmarkIcon","BorderColorIcon","BottomBorderIcon","BulletListIcon","CalibrateIcon","CaptureAddIcon","CaretDownIcon","CaretIcon","CaretLeftIcon","CaretRightIcon","CaretUpIcon","CheckmarkCircleFilledIcon","CheckmarkCircleIcon","CheckmarkIcon","ChevronListIcon","ClockIcon","CloudyBorderIcon","CollapseIcon","ColorPaletteIcon","ColorSwatchIcon","CommentIcon","CommentInSidebarIcon","CommentOnPageIcon","CompareDocumentsIcon","CopyIcon","CopyPageIcon","CropIcon","CustomizeIcon","CutIcon","DateModifiedIcon","DatePlusIcon","DebugIcon","DocumentArrowDownCircleIcon","DocumentArrowDownIcon","DocumentArrowRightIcon","DocumentFilledIcon","DocumentLockIcon","DocumentPdfIcon","DownloadIcon","DragIndicatorIcon","DragIndicatorVerticalIcon","DuplicateIcon","EditAnnotationsIcon","EditContentIcon","EditDocumentIcon","EditIcon","EditThumbnailsIcon","EllipseAreaIcon","EllipseCloudyIcon","EllipseDashedIcon","EllipseIcon","EmbedIcon","EmojiSmileIcon","EndCapArrowFilledIcon","EndCapArrowIcon","EndCapChevronFilledIcon","EndCapChevronIcon","EndCapCircleIcon","EndCapDiamondIcon","EndCapNoneIcon","EndCapSlantedIcon","EndCapSquareIcon","EndCapStraightIcon","EraserIcon","ErrorAltCircleFilledIcon","ErrorAltIcon","ErrorCircleFilledIcon","ErrorCircleIcon","ExpandIcon","ExpandVerticalIcon","FillColorIcon","FilterIcon","FitToHeightIcon","FivePagesHorizontalFilledIcon","FivePagesVerticalFilledIcon","FolderAddIcon","FolderIcon","FontListIcon","FontSizeIcon","FormButtonIcon","FormChoiceIcon","FormComboboxIcon","FormDateIcon","FormListboxIcon","FormPageIcon","FormRadioButtonIcon","FormSignatureIcon","FormTextFieldIcon","FormTwoRadioButtonsIcon","FourPagesGridFilledIcon","FourPagesHorizontalFilledIcon","FourPagesStackedFilledIcon","FourPagesVerticalFilledIcon","GroupIcon","HamburgerMenuIcon","HandIcon","HeartIcon","HideIcon","HideRevealIcon","HighlightTextIcon","HomeIcon","HorizontalScollIcon","ImageIcon","InfoCircleFilledIcon","InfoCircleIcon","InitialsIcon","InnerHorizontalBorderIcon","InnerVerticalBorderIcon","InsertIcon","ItalicIcon","LayerBottomIcon","LayerDownIcon","LayerTopIcon","LayerUpIcon","LayersIcon","LeftBindingIcon","LeftBorderIcon","LineCapsIcon","LineIcon","LineSpacingIcon","LineStyleCloudyIcon","LineStyleDashedDoubleDashIcon","LineStyleDashedDoubleGapIcon","LineStyleDashedQuadrupleDashIcon","LineStyleDashedSingleGapIcon","LineStyleIcon","LineStyleSolidIcon","LineWidthIcon","LinkIcon","LockFilledIcon","LockIcon","MagicIcon","MagicPenIcon","MailIcon","MarkupIcon","MarqueeZoomIcon","MeasureIcon","MergeIcon","MessageCloudIcon","MinusIcon","MoonIcon","MoreCircleIcon","MoreIcon","MoreVerticalIcon","MoveAllDirectionsIcon","MoveLeftIcon","MoveLeftRightIcon","MoveRightIcon","MultiplePagesIcon","NonEditableIcon","NoteArrowRightIcon","NoteCheckIcon","NoteCircleIcon","NoteCloudIcon","NoteCrossIcon","NoteHelpIcon","NoteIcon","NoteInsetIcon","NoteKeyIcon","NoteNewParagraphAltIcon","NoteNewParagraphIcon","NoteNoteIcon","NotePointerRightIcon","NoteSpeechBubbleIcon","NoteStarIcon","OcrIcon","OpacityIcon","PageAddIcon","PageCurlIcon","PageDuplicateIcon","PageFittingFillIcon","PageFittingFitIcon","PageHorizontalScrollIcon","PageLandscapeIcon","PageLayoutDoubleIcon","PageLayoutSingleIcon","PageMoveLeftIcon","PageMoveRightIcon","PageNumberCircleIcon","PageNumberIcon","PagePortraitIcon","PageRemoveIcon","PageVerticalScrollIcon","PagesInsertAltIcon","PagesInsertIcon","PagesNewFromSelectionAltIcon","PagesNewFromSelectionIcon","PagesSelectAllIcon","PagesSelectNoneIcon","PasteBoardIcon","PastePageIcon","PauseIcon","PenHighlighterIcon","PenIcon","PerimeterIcon","PinDropFilledIcon","PinDropIcon","PipetteIcon","PlayIcon","PlusCircleFilledIcon","PlusCircleIcon","PlusIcon","PointerIcon","PolygonAreaIcon","PolygonCloudyIcon","PolygonDashedIcon","PolygonIcon","PolylineIcon","PrecisionIcon","PrintIcon","PrivateModeIcon","PushPinIcon","QuestionmarkCircleIcon","ReaderViewIcon","RectangleAreaIcon","RectangleCloudyIcon","RectangleDashedIcon","RectangleIcon","RedactIcon","RedactRectangleIcon","RedactTextHighlighterIcon","RedactionTextRepeatingIcon","RedactionTextSingleIcon","RedoAllIcon","RedoIcon","RegexIcon","ReplaceIcon","RightBindingIcon","RightBorderIcon","RotateClockwiseIcon","RotateCounterClockwiseIcon","RotateObjectClockwiseIcon","RotateObjectCounterClockwiseIcon","RulerIcon","ScaleIcon","SearchCircleIcon","SearchIcon","SearchSelectionIcon","SelectAllIcon","SelectionToolIcon","SettingsIcon","ShapesIcon","ShareAltIcon","ShareIcon","ShieldAddIcon","ShieldCheckmarkIcon","ShieldWarningIcon","ShieldXIcon","ShowIcon","SidebarIcon","SignOutIcon","SignatureDigitalIcon","SignatureIcon","SinglePageFilledIcon","SoundIcon","SquigglyTextIcon","StampAddIcon","StampIcon","StarFilledIcon","StarIcon","StartCapArrowFilledIcon","StartCapArrowIcon","StartCapChevronFilledIcon","StartCapChevronIcon","StartCapCircleIcon","StartCapDiamondIcon","StartCapNoneIcon","StartCapSlantedIcon","StartCapSquareIcon","StartCapStraightIcon","StrikeoutTextIcon","StyleFilledIcon","StyleIcon","StylusFilledIcon","StylusIcon","SunIcon","TableCellIcon","TextAlignCenterIcon","TextAlignJustifyIcon","TextAlignLeftIcon","TextAlignRightIcon","TextCalloutIcon","TextColorIcon","TextIcon","TextPropertiesHideIcon","TextPropertiesShowIcon","TextSerifIcon","TextSmallerIcon","ThreePagesHorizontalFilledIcon","ThreePagesStackedFilledIcon","ThreePagesVerticalFilledIcon","ThumbnailsIcon","ThumbsDownIcon","ThumbsUpIcon","TopBorderIcon","TrashIcon","TwoPagesHorizontalFilledIcon","TwoPagesVerticalFilledIcon","TypeTextIcon","UnderlineIcon","UnderlineTextIcon","UndoAllIcon","UndoIcon","UndoRedoIcon","UngroupIcon","UnlockIcon","UploadIcon","UserIcon","VerticalScrollIcon","VideoIcon","WarningFilledIcon","WarningIcon","WidgetIcon","WorkflowIcon","XCircleFilledIcon","XCircleIcon","XIcon","ZoomInIcon","ZoomOutIcon"],"36":["ArrowRight","Check","Circle","Cross","Help","Inset","Key","NewParagraphAlt","NewParagraph","Note","PointerRight","SpeechBubble","Star"]};var p={version:"1.2.0"};var u=`
35942
+ * [Theme documentation](/docs/theming--docs) - Learn about Baseline UI's theming system`};var c={"8":["CaretDownIcon","CaretLeftIcon","CaretRightIcon","CaretUpIcon","ChevronRightFilledIcon","ChevronRightIcon","EllipseIcon","MinusIcon","PlusIcon","XIcon"],"12":["CaretDownIcon","CaretLeftIcon","CaretRightIcon","CaretUpIcon","CheckmarkIcon","DragIndicatorIcon","DragIndicatorVerticalIcon","EditIcon","EllipseIcon","EnterKeyIcon","LockFilledIcon","LockIcon","MinusIcon","MoreVIcon","MoreIcon","PlaceholderIcon","PlusIcon","SearchIcon","SizeIcon","TrashIcon","XIcon","ZoomIcon"],"16":["AlignBottomIcon","AlignMiddleIcon","AlignTopIcon","AnonymousIcon","ArrowDiagonalTopLeftBottomRightIcon","ArrowDownCircleFilledIcon","ArrowDownIcon","ArrowIcon","ArrowLeftRightIcon","ArrowRightIcon","ArrowUpArrowDownIcon","ArrowUpIcon","AtIcon","AttachmentsIcon","AvatarIcon","BoldIcon","BookmarkFilledIcon","BookmarkIcon","BulletListIcon","CalendarIcon","CaretLeftIcon","CaretRightIcon","CheckmarkCircleFilledIcon","CheckmarkCircleIcon","CheckmarkIcon","CircleFilledIcon","ClockIcon","CopyIcon","CustomizeIcon","DocumentEditIcon","DownloadIcon","DuplicateIcon","EditIcon","ElipseAreaIcon","EllipseCloudyIcon","EllipseDashedIcon","EllipseIcon","EmbedIcon","EmojiSmileIcon","ErrorAltCircleFilledIcon","ErrorCircleFilledIcon","ErrorCircleIcon","ExpandIcon","FilterAltIcon","FolderIcon","FormButtonIcon","FormChoiceIcon","FormComboboxIcon","FormDateIcon","FormListboxIcon","FormRadioButtonIcon","FormSignatureIcon","FormTextFieldIcon","FullScreenIcon","HelpCircleIcon","HelpIcon","HereIcon","HideIcon","HighlightTextAltIcon","HighlightTextIcon","HorizontalScrollIcon","ImageIcon","InfoCircleFilledIcon","InsertIcon","ItalicIcon","LightBulbIcon","LineIcon","LinkIcon","ListIcon","LockIcon","MagicIcon","MeasureIcon","MinusIcon","MoreIcon","MoreVerticalIcon","NoteArrowRightIcon","NoteCheckIcon","NoteCircleIcon","NoteCloudIcon","NoteCrossIcon","NoteHelpIcon","NoteInsetIcon","NoteKeyIcon","NoteNewParagraphAltIcon","NoteNewParagraphIcon","NoteNoteIcon","NotePointerRightIcon","NoteSpeechBubbleIcon","NoteStarIcon","NumberedListIcon","OpenIcon","PageFittingFillIcon","PageFittingFitIcon","PageHorizontalScrollIcon","PageLayoutDoubleIcon","PageLayoutSingleIcon","PageVerticalScrollIcon","PauseIcon","PenHighlighterIcon","PenIcon","PerimeterIcon","PlaceholderIcon","PlayIcon","PlusIcon","PolygonAreaIcon","PolygonCloudyIcon","PolygonDashedIcon","PolygonIcon","PolylineIcon","ReadOnlyIcon","RectangleAreaIcon","RectangleCloudyIcon","RectangleDashedIcon","RectangleIcon","RedoIcon","RemoveFormattingIcon","ReorderIcon","RotateClockwiseIcon","RotateCounterClockwiseIcon","RulerIcon","SearchIcon","SettingsIcon","ShowIcon","SlashCommandsIcon","SoundRecordIcon","StampIcon","StarFilledIcon","StarIcon","StrikeoutTextAltIcon","TableCellIcon","TableColumnIcon","TableHeaderIcon","TableIcon","TableRowIcon","TextAlignCenterIcon","TextAlignJustifyIcon","TextAlignLeftIcon","TextAlignRightIcon","TextCalloutIcon","TextDecreaseIndentIcon","TextIcon","TextIncreaseIndentIcon","TextMarkIcon","ThumbnailsIcon","ThumbsDownIcon","ThumbsUpIcon","TrashIcon","TypeTextIcon","UnderlineIcon","UndoIcon","UnlockIcon","VerticalScrollIcon","VideoIcon","WarningFilledIcon","WarningIcon","WindowedIcon","WorkflowIcon","XCircleFilledIcon","XIcon"],"20":["AddPageIcon","AnonymousIcon","ArrowLeftIcon","ArrowRightIcon","ArrowUpCircleFilledIcon","AtIcon","AvatarFilledIcon","BoldIcon","CalloutIcon","CaretDownIcon","CaretLeftIcon","CaretRightIcon","CaretUpIcon","CheckCircleFilledIcon","CheckmarkCircleIcon","CheckmarkIcon","ClockIcon","CollapseIcon","CommentIcon","CopyIcon","CutIcon","DistanceIcon","DownloadIcon","DuplicateIcon","EditIcon","EllipseIcon","EmojiSmileIcon","ErrorAltCircleFilledIcon","ErrorAlternativeCircleIcon","ErrorCircleFilledIcon","ErrorCircleIcon","ExpandIcon","FormDateIcon","FormSignatureIcon","FormTextFieldIcon","HelpCircleIcon","HighlightTextIcon","HomeIcon","ImageIcon","InfoCircleFilledIcon","InfoCircleIcon","ItalicIcon","LinkIcon","ListIcon","LockIcon","MagicIcon","MinusIcon","MoreIcon","MoreVerticalIcon","MoveIcon","NoteArrowRightIcon","NoteCheckIcon","NoteCircleIcon","NoteCrossIcon","NoteHelpIcon","NoteInsetIcon","NoteKeyIcon","NoteNewParagraphAltIcon","NoteNewParagraphIcon","NoteNoteIcon","NotePointerRightIcon","NoteSpeechBubbleIcon","NoteStarIcon","OpenIcon","PageMoveLeftIcon","PageMoveRightIcon","PagesInsertIcon","PasteIcon","PipetteIcon","PlusIcon","PrintIcon","RotateClockwiseIcon","SearchIcon","SettingsIcon","ShapeIcon","ShareIcon","SoundIcon","SoundRecordIcon","StarFilledIcon","StarIcon","StyleIcon","TextAlignCenterIcon","TextAlignJustifyIcon","TextAlignLeftIcon","TextAlignRightIcon","TextIcon","ThumbnailsIcon","ThumbsDownIcon","ThumbsUpIcon","TrashIcon","TypeTextIcon","UnderlineIcon","UploadIcon","WarningFilledIcon","WarningIcon","XCircleFilledIcon","XCircleIcon","XIcon"],"24":["AddNoteCloudIcon","AddNoteIcon","AddTextSerifIcon","AiIcon","AirplaneIcon","AlignBottomIcon","AlignHorizontalCenterIcon","AlignMiddleIcon","AlignTopIcon","AnonymousIcon","ArrowDownIcon","ArrowIcon","ArrowLeftIcon","ArrowRightIcon","ArrowUpIcon","AtIcon","AttachmentIcon","AvatarFilledIcon","AvatarIcon","BlendModeIcon","BoldIcon","BookmarkFilledIcon","BookmarkIcon","BorderColorIcon","BottomBorderIcon","BulletListIcon","CalibrateIcon","CaptureAddIcon","CaretDownIcon","CaretIcon","CaretLeftIcon","CaretRightIcon","CaretUpIcon","CheckmarkCircleFilledIcon","CheckmarkCircleIcon","CheckmarkIcon","ChevronListIcon","ClockIcon","CloudyBorderIcon","CollapseIcon","ColorPaletteIcon","ColorSwatchIcon","CommentIcon","CommentInSidebarIcon","CommentOnPageIcon","CompareDocumentsIcon","CopyIcon","CopyPageIcon","CropIcon","CustomizeIcon","CutIcon","DateModifiedIcon","DatePlusIcon","DebugIcon","DocumentArrowDownCircleIcon","DocumentArrowDownIcon","DocumentArrowRightIcon","DocumentFilledIcon","DocumentLockIcon","DocumentPdfIcon","DownloadIcon","DragIndicatorIcon","DragIndicatorVerticalIcon","DuplicateIcon","EditAnnotationsIcon","EditContentIcon","EditDocumentIcon","EditIcon","EditThumbnailsIcon","EllipseAreaIcon","EllipseCloudyIcon","EllipseDashedIcon","EllipseIcon","EmbedIcon","EmojiSmileIcon","EndCapArrowFilledIcon","EndCapArrowIcon","EndCapChevronFilledIcon","EndCapChevronIcon","EndCapCircleIcon","EndCapDiamondIcon","EndCapNoneIcon","EndCapSlantedIcon","EndCapSquareIcon","EndCapStraightIcon","EraserIcon","ErrorAltCircleFilledIcon","ErrorAltIcon","ErrorCircleFilledIcon","ErrorCircleIcon","ExpandIcon","ExpandVerticalIcon","FillColorIcon","FilterIcon","FitToHeightIcon","FivePagesHorizontalFilledIcon","FivePagesVerticalFilledIcon","FolderAddIcon","FolderIcon","FontListIcon","FontSizeIcon","FormButtonIcon","FormChoiceIcon","FormComboboxIcon","FormDateIcon","FormListboxIcon","FormPageIcon","FormRadioButtonIcon","FormSignatureIcon","FormTextFieldIcon","FormTwoRadioButtonsIcon","FourPagesGridFilledIcon","FourPagesHorizontalFilledIcon","FourPagesStackedFilledIcon","FourPagesVerticalFilledIcon","GroupIcon","HamburgerMenuIcon","HandIcon","HeartIcon","HideIcon","HideRevealIcon","HighlightTextIcon","HomeIcon","HorizontalScollIcon","ImageIcon","InfoCircleFilledIcon","InfoCircleIcon","InitialsIcon","InnerHorizontalBorderIcon","InnerVerticalBorderIcon","InsertIcon","ItalicIcon","LayerBottomIcon","LayerDownIcon","LayerTopIcon","LayerUpIcon","LayersIcon","LeftBindingIcon","LeftBorderIcon","LineCapsIcon","LineIcon","LineSpacingIcon","LineStyleCloudyIcon","LineStyleDashedDoubleDashIcon","LineStyleDashedDoubleGapIcon","LineStyleDashedQuadrupleDashIcon","LineStyleDashedSingleGapIcon","LineStyleIcon","LineStyleSolidIcon","LineWidthIcon","LinkIcon","LockFilledIcon","LockIcon","MagicIcon","MagicPenIcon","MailIcon","MarkupIcon","MarqueeZoomIcon","MeasureIcon","MergeIcon","MessageCloudIcon","MinusIcon","MoonIcon","MoreCircleIcon","MoreIcon","MoreVerticalIcon","MoveAllDirectionsIcon","MoveLeftIcon","MoveLeftRightIcon","MoveRightIcon","MultiplePagesIcon","NonEditableIcon","NoteArrowRightIcon","NoteCheckIcon","NoteCircleIcon","NoteCloudIcon","NoteCrossIcon","NoteHelpIcon","NoteIcon","NoteInsetIcon","NoteKeyIcon","NoteNewParagraphAltIcon","NoteNewParagraphIcon","NoteNoteIcon","NotePointerRightIcon","NoteSpeechBubbleIcon","NoteStarIcon","OcrIcon","OpacityIcon","PageAddIcon","PageCurlIcon","PageDuplicateIcon","PageFittingFillIcon","PageFittingFitIcon","PageHorizontalScrollIcon","PageLandscapeIcon","PageLayoutDoubleIcon","PageLayoutSingleIcon","PageMoveLeftIcon","PageMoveRightIcon","PageNumberCircleIcon","PageNumberIcon","PagePortraitIcon","PageRemoveIcon","PageVerticalScrollIcon","PagesInsertAltIcon","PagesInsertIcon","PagesNewFromSelectionAltIcon","PagesNewFromSelectionIcon","PagesSelectAllIcon","PagesSelectNoneIcon","PasteBoardIcon","PastePageIcon","PauseIcon","PenHighlighterIcon","PenIcon","PerimeterIcon","PinDropFilledIcon","PinDropIcon","PipetteIcon","PlayIcon","PlusCircleFilledIcon","PlusCircleIcon","PlusIcon","PointerIcon","PolygonAreaIcon","PolygonCloudyIcon","PolygonDashedIcon","PolygonIcon","PolylineIcon","PrecisionIcon","PrintIcon","PrivateModeIcon","PushPinIcon","QuestionmarkCircleIcon","ReaderViewIcon","RectangleAreaIcon","RectangleCloudyIcon","RectangleDashedIcon","RectangleIcon","RedactIcon","RedactRectangleIcon","RedactTextHighlighterIcon","RedactionTextRepeatingIcon","RedactionTextSingleIcon","RedoAllIcon","RedoIcon","RegexIcon","ReplaceIcon","RightBindingIcon","RightBorderIcon","RotateClockwiseIcon","RotateCounterClockwiseIcon","RotateObjectClockwiseIcon","RotateObjectCounterClockwiseIcon","RulerIcon","ScaleIcon","SearchCircleIcon","SearchIcon","SearchSelectionIcon","SelectAllIcon","SelectionToolIcon","SettingsIcon","ShapesIcon","ShareAltIcon","ShareIcon","ShieldAddIcon","ShieldCheckmarkIcon","ShieldWarningIcon","ShieldXIcon","ShowIcon","SidebarIcon","SignOutIcon","SignatureDigitalIcon","SignatureIcon","SinglePageFilledIcon","SoundIcon","SquigglyTextIcon","StampAddIcon","StampIcon","StarFilledIcon","StarIcon","StartCapArrowFilledIcon","StartCapArrowIcon","StartCapChevronFilledIcon","StartCapChevronIcon","StartCapCircleIcon","StartCapDiamondIcon","StartCapNoneIcon","StartCapSlantedIcon","StartCapSquareIcon","StartCapStraightIcon","StrikeoutTextIcon","StyleFilledIcon","StyleIcon","StylusFilledIcon","StylusIcon","SunIcon","TableCellIcon","TextAlignCenterIcon","TextAlignJustifyIcon","TextAlignLeftIcon","TextAlignRightIcon","TextCalloutIcon","TextColorIcon","TextIcon","TextPropertiesHideIcon","TextPropertiesShowIcon","TextSerifIcon","TextSmallerIcon","ThreePagesHorizontalFilledIcon","ThreePagesStackedFilledIcon","ThreePagesVerticalFilledIcon","ThumbnailsIcon","ThumbsDownIcon","ThumbsUpIcon","TopBorderIcon","TrashIcon","TwoPagesHorizontalFilledIcon","TwoPagesVerticalFilledIcon","TypeTextIcon","UnderlineIcon","UnderlineTextIcon","UndoAllIcon","UndoIcon","UndoRedoIcon","UngroupIcon","UnlockIcon","UploadIcon","UserIcon","VerticalScrollIcon","VideoIcon","WarningFilledIcon","WarningIcon","WidgetIcon","WorkflowIcon","XCircleFilledIcon","XCircleIcon","XIcon","ZoomInIcon","ZoomOutIcon"],"36":["ArrowRight","Check","Circle","Cross","Help","Inset","Key","NewParagraphAlt","NewParagraph","Note","PointerRight","SpeechBubble","Star"]};var p={version:"2.1.0"};var u=`
34230
35943
  # Baseline UI MCP Server Guidelines
34231
35944
 
34232
35945
  This MCP server provides AI assistants with structured access to Baseline UI's comprehensive component documentation, icon library, theming resources, and design guidelines.
@@ -34381,13 +36094,14 @@ Use this tool to:
34381
36094
  - Consult **list_components** to find the right component for your needs
34382
36095
  - Reference **theming** for custom design systems
34383
36096
  - Check **styling** for CSS and design token guidance
34384
- `;var S=`# List of available components in Baseline UI design system
36097
+ `;var C=`# List of available components in Baseline UI design system
34385
36098
  | Name | Description | Similar To |
34386
36099
  | --- | --- | --- |
34387
- ${Object.entries(o).toSorted(([n],[t])=>n.localeCompare(t)).map(([n,{description:t,similarTo:e}])=>`|${n}|${t}|${e?.join(", ")||""}|`).join(`
36100
+ ${Object.entries(t).toSorted(([n],[e])=>n.localeCompare(e)).map(([n,{description:e,similarTo:i}])=>`|${n}|${e}|${i?.join(", ")||""}|`).join(`
34388
36101
  `)}
34389
- `,P=Object.entries(c).map(([n,t])=>`${n}
36102
+ `,T=Object.entries(c).map(([n,e])=>`${n}
34390
36103
 
34391
- ${t.map(e=>"- "+e).join(`
36104
+ ${e.map(i=>"- "+i).join(`
34392
36105
  `)}`).join(`
34393
- `);async function k(){let n=new mcp_js.McpServer({name:"baseline-ui",version:p.version});n.registerResource("list_components","resource://baseline-ui/list_components.md",{description:a,mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:S}]})),n.registerResource("list_icons","resource://baseline-ui/list_icons.md",{description:r,mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:P}]})),n.registerResource("getting_started","resource://baseline-ui/getting_started.md",{description:"Quick start guide for integrating Baseline UI into new projects",mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:i.gettingStarted}]})),n.registerResource("nutrient_web_viewer_theming","resource://baseline-ui/nutrient_web_viewer_theming.md",{description:"Specialized theming guide for customizing Baseline UI in Nutrient Web Viewer. This is not applicable if you are not theming the Nutrient Web Viewer SDK.",mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:i.nutrientWebViewerTheming}]})),n.registerResource("theming","resource://baseline-ui/theming.md",{description:"Comprehensive guide for implementing custom themes and color schemes in Baseline UI",mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:i.theming}]})),n.registerResource("internationalization","resource://baseline-ui/internationalization.md",{description:"Guide for implementing multi-language support and localization in Baseline UI",mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:i.internationalization}]})),n.registerResource("styling","resource://baseline-ui/styling.md",{description:l,mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:i.styling}]})),n.registerResource("guidelines","resource://baseline-ui/guidelines.md",{description:s,mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:u}]})),n.registerTool("get_component_info",{title:"Get component info",description:m,inputSchema:{componentName:zod.z.enum(Object.keys(o))}},({componentName:e})=>({content:[{type:"text",text:JSON.stringify(Object.fromEntries(Object.entries(o[e]).filter(([f])=>!["description","similarTo","figmaUrl"].includes(f))),null,2)}]})),n.registerTool("get_story_url",{title:"Get story demo URL",description:h,inputSchema:{storyId:zod.z.string()}},({storyId:e})=>({content:[{type:"text",text:`https://nutrient.io/baseline-ui/iframe.html?id=${e}`}]})),n.registerTool("get_figma_url",{title:"Get Figma URL",description:b,inputSchema:{componentName:zod.z.enum(Object.keys(o))}},({componentName:e})=>({content:[{type:"text",text:o[e].figmaUrl}]})),n.registerTool("list_available_resources",{title:"List Available Resources",description:g,inputSchema:{}},()=>({content:[{type:"text",text:JSON.stringify([{name:"list_components",uri:"resource://baseline-ui/list_components.md",description:a,mimeType:"text/markdown"},{name:"list_icons",uri:"resource://baseline-ui/list_icons.md",description:r,mimeType:"text/markdown"},{name:"getting_started",uri:"resource://baseline-ui/getting_started.md",description:"Quick start guide for integrating Baseline UI into new projects",mimeType:"text/markdown"},{name:"nutrient_web_viewer_theming",uri:"resource://baseline-ui/nutrient_web_viewer_theming.md",description:"Specialized theming guide for customizing Baseline UI in Nutrient Web Viewer. This is not applicable if you are not theming the Nutrient Web Viewer SDK.",mimeType:"text/markdown"},{name:"theming",uri:"resource://baseline-ui/theming.md",description:"Comprehensive guide for implementing custom themes and color schemes in Baseline UI",mimeType:"text/markdown"},{name:"internationalization",uri:"resource://baseline-ui/internationalization.md",description:"Guide for implementing multi-language support and localization in Baseline UI",mimeType:"text/markdown"},{name:"styling",uri:"resource://baseline-ui/styling.md",description:l,mimeType:"text/markdown"},{name:"guidelines",uri:"resource://baseline-ui/guidelines.md",description:s,mimeType:"text/markdown"}],null,2)}]}));let t=new stdio_js.StdioServerTransport;await n.connect(t);}(async()=>await k())();
36106
+ `);function z(){let n=new mcp_js.McpServer({name:"baseline-ui",version:p.version});return n.registerResource("list_components","resource://baseline-ui/list_components.md",{description:a,mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:C}]})),n.registerResource("list_icons","resource://baseline-ui/list_icons.md",{description:r,mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:T}]})),n.registerResource("getting_started","resource://baseline-ui/getting_started.md",{description:"Quick start guide for integrating Baseline UI into new projects",mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:o.gettingStarted}]})),n.registerResource("nutrient_web_viewer_theming","resource://baseline-ui/nutrient_web_viewer_theming.md",{description:"Specialized theming guide for customizing Baseline UI in Nutrient Web Viewer. This is not applicable if you are not theming the Nutrient Web Viewer SDK.",mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:o.nutrientWebViewerTheming}]})),n.registerResource("theming","resource://baseline-ui/theming.md",{description:"Comprehensive guide for implementing custom themes and color schemes in Baseline UI",mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:o.theming}]})),n.registerResource("internationalization","resource://baseline-ui/internationalization.md",{description:"Guide for implementing multi-language support and localization in Baseline UI",mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:o.internationalization}]})),n.registerResource("styling","resource://baseline-ui/styling.md",{description:l,mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:o.styling}]})),n.registerResource("guidelines","resource://baseline-ui/guidelines.md",{description:s,mimeType:"text/markdown"},e=>({contents:[{uri:e.href,mimeType:"text/markdown",text:u}]})),n.registerTool("get_component_info",{title:"Get component info",description:m,inputSchema:{componentName:zod.z.enum(Object.keys(t))}},({componentName:e})=>({content:[{type:"text",text:JSON.stringify(Object.fromEntries(Object.entries(t[e]).filter(([i])=>!["description","similarTo","figmaUrl"].includes(i))),null,2)}]})),n.registerTool("get_story_url",{title:"Get story demo URL",description:h,inputSchema:{storyId:zod.z.string()}},({storyId:e})=>({content:[{type:"text",text:`https://nutrient.io/baseline-ui/iframe.html?id=${e}`}]})),n.registerTool("get_figma_url",{title:"Get Figma URL",description:b,inputSchema:{componentName:zod.z.enum(Object.keys(t))}},({componentName:e})=>({content:[{type:"text",text:t[e].figmaUrl}]})),n.registerTool("list_available_resources",{title:"List Available Resources",description:g,inputSchema:{}},()=>({content:[{type:"text",text:JSON.stringify([{name:"list_components",uri:"resource://baseline-ui/list_components.md",description:a,mimeType:"text/markdown"},{name:"list_icons",uri:"resource://baseline-ui/list_icons.md",description:r,mimeType:"text/markdown"},{name:"getting_started",uri:"resource://baseline-ui/getting_started.md",description:"Quick start guide for integrating Baseline UI into new projects",mimeType:"text/markdown"},{name:"nutrient_web_viewer_theming",uri:"resource://baseline-ui/nutrient_web_viewer_theming.md",description:"Specialized theming guide for customizing Baseline UI in Nutrient Web Viewer. This is not applicable if you are not theming the Nutrient Web Viewer SDK.",mimeType:"text/markdown"},{name:"theming",uri:"resource://baseline-ui/theming.md",description:"Comprehensive guide for implementing custom themes and color schemes in Baseline UI",mimeType:"text/markdown"},{name:"internationalization",uri:"resource://baseline-ui/internationalization.md",description:"Guide for implementing multi-language support and localization in Baseline UI",mimeType:"text/markdown"},{name:"styling",uri:"resource://baseline-ui/styling.md",description:l,mimeType:"text/markdown"},{name:"guidelines",uri:"resource://baseline-ui/guidelines.md",description:s,mimeType:"text/markdown"}],null,2)}]})),n}
36107
+ exports.createServer=z;