@vjdesigns/vjdesigns-ui 1.5.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.
- package/README.md +781 -723
- package/dist/ai/llms-full.txt +1003 -923
- package/dist/ai/llms.txt +2 -1
- package/dist/ai/registry.json +340 -121
- package/dist/components/data-display/Chat/Chat.cjs.js +1 -0
- package/dist/components/data-display/Chat/Chat.d.ts +114 -0
- package/dist/components/data-display/Chat/Chat.mjs +301 -0
- package/dist/components/data-display/Chat/Chat.module.css.cjs.js +1 -0
- package/dist/components/data-display/Chat/Chat.module.css.mjs +32 -0
- package/dist/components/data-display/Chat/index.d.ts +2 -0
- package/dist/components/data-display/Chat/streams.cjs.js +4 -0
- package/dist/components/data-display/Chat/streams.d.ts +32 -0
- package/dist/components/data-display/Chat/streams.mjs +35 -0
- package/dist/components/data-display/DataGrid/DataGrid.module.css.cjs.js +1 -1
- package/dist/components/data-display/DataGrid/DataGrid.module.css.mjs +1 -1
- package/dist/components/data-display/VirtualList/VirtualList.module.css.cjs.js +1 -1
- package/dist/components/data-display/VirtualList/VirtualList.module.css.mjs +1 -1
- package/dist/components/feedback/ErrorSummary/ErrorSummary.module.css.cjs.js +1 -1
- package/dist/components/feedback/ErrorSummary/ErrorSummary.module.css.mjs +1 -1
- package/dist/components/forms/Combobox/Combobox.cjs.js +1 -1
- package/dist/components/forms/Combobox/Combobox.mjs +5 -5
- package/dist/components/forms/DateTimePicker/DateTimePicker.module.css.cjs.js +1 -1
- package/dist/components/forms/DateTimePicker/DateTimePicker.module.css.mjs +1 -1
- package/dist/components/forms/Rating/Rating.module.css.cjs.js +1 -1
- package/dist/components/forms/Rating/Rating.module.css.mjs +1 -1
- package/dist/components/forms/TagInput/TagInput.cjs.js +1 -1
- package/dist/components/forms/TagInput/TagInput.mjs +5 -5
- package/dist/components/forms/TimePicker/TimePicker.module.css.cjs.js +1 -1
- package/dist/components/forms/TimePicker/TimePicker.module.css.mjs +1 -1
- package/dist/components/layout/ResizablePanels/ResizablePanels.cjs.js +1 -1
- package/dist/components/layout/ResizablePanels/ResizablePanels.mjs +1 -0
- package/dist/components/layout/ResizablePanels/ResizablePanels.module.css.cjs.js +1 -1
- package/dist/components/layout/ResizablePanels/ResizablePanels.module.css.mjs +1 -1
- package/dist/components/layout/ScrollArea/ScrollArea.module.css.cjs.js +1 -1
- package/dist/components/layout/ScrollArea/ScrollArea.module.css.mjs +1 -1
- package/dist/components/navigation/ContextMenu/ContextMenu.module.css.cjs.js +1 -1
- package/dist/components/navigation/ContextMenu/ContextMenu.module.css.mjs +1 -1
- package/dist/components/overlays/HoverCard/HoverCard.module.css.cjs.js +1 -1
- package/dist/components/overlays/HoverCard/HoverCard.module.css.mjs +2 -2
- package/dist/components/primitives/Button/Button.cjs.js +1 -1
- package/dist/components/primitives/Button/Button.mjs +37 -37
- package/dist/components/utilities/LocaleProvider/LocaleProvider.module.css.cjs.js +1 -1
- package/dist/components/utilities/LocaleProvider/LocaleProvider.module.css.mjs +1 -1
- package/dist/components/utilities/Theme/Theme.cjs.js +1 -1
- package/dist/components/utilities/Theme/Theme.d.ts +6 -6
- package/dist/components/utilities/Theme/Theme.mjs +49 -45
- package/dist/hooks/useFormValidation.cjs.js +1 -1
- package/dist/hooks/useFormValidation.mjs +1 -1
- package/dist/i18n/en.cjs.js +1 -1
- package/dist/i18n/en.mjs +71 -0
- package/dist/i18n/types.d.ts +91 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +108 -106
- package/dist/locales/ar.cjs.js +1 -1
- package/dist/locales/ar.mjs +71 -0
- package/dist/locales/bn.cjs.js +1 -1
- package/dist/locales/bn.mjs +71 -0
- package/dist/locales/cs.cjs.js +1 -1
- package/dist/locales/cs.mjs +71 -0
- package/dist/locales/da.cjs.js +1 -1
- package/dist/locales/da.mjs +71 -0
- package/dist/locales/de.cjs.js +1 -1
- package/dist/locales/de.mjs +71 -0
- package/dist/locales/el.cjs.js +1 -1
- package/dist/locales/el.mjs +71 -0
- package/dist/locales/es.cjs.js +1 -1
- package/dist/locales/es.mjs +71 -0
- package/dist/locales/fa.cjs.js +1 -1
- package/dist/locales/fa.mjs +71 -0
- package/dist/locales/fi.cjs.js +1 -1
- package/dist/locales/fi.mjs +71 -0
- package/dist/locales/fr.cjs.js +1 -1
- package/dist/locales/fr.mjs +71 -0
- package/dist/locales/he.cjs.js +1 -1
- package/dist/locales/he.mjs +71 -0
- package/dist/locales/hi.cjs.js +1 -1
- package/dist/locales/hi.mjs +71 -0
- package/dist/locales/hu.cjs.js +1 -1
- package/dist/locales/hu.mjs +71 -0
- package/dist/locales/id.cjs.js +1 -1
- package/dist/locales/id.mjs +71 -0
- package/dist/locales/it.cjs.js +1 -1
- package/dist/locales/it.mjs +71 -0
- package/dist/locales/ja.cjs.js +1 -1
- package/dist/locales/ja.mjs +71 -0
- package/dist/locales/ko.cjs.js +1 -1
- package/dist/locales/ko.mjs +71 -0
- package/dist/locales/nb.cjs.js +1 -1
- package/dist/locales/nb.mjs +71 -0
- package/dist/locales/nl.cjs.js +1 -1
- package/dist/locales/nl.mjs +71 -0
- package/dist/locales/pl.cjs.js +1 -1
- package/dist/locales/pl.mjs +71 -0
- package/dist/locales/pt-BR.cjs.js +1 -1
- package/dist/locales/pt-BR.mjs +71 -0
- package/dist/locales/ro.cjs.js +1 -1
- package/dist/locales/ro.mjs +71 -0
- package/dist/locales/ru.cjs.js +1 -1
- package/dist/locales/ru.mjs +71 -0
- package/dist/locales/sv.cjs.js +1 -1
- package/dist/locales/sv.mjs +71 -0
- package/dist/locales/th.cjs.js +1 -1
- package/dist/locales/th.mjs +71 -0
- package/dist/locales/tr.cjs.js +1 -1
- package/dist/locales/tr.mjs +71 -0
- package/dist/locales/uk.cjs.js +1 -1
- package/dist/locales/uk.mjs +71 -0
- package/dist/locales/ur.cjs.js +1 -1
- package/dist/locales/ur.mjs +71 -0
- package/dist/locales/vi.cjs.js +1 -1
- package/dist/locales/vi.mjs +71 -0
- package/dist/locales/zh-Hans.cjs.js +1 -1
- package/dist/locales/zh-Hans.mjs +71 -0
- package/dist/locales/zh-Hant.cjs.js +1 -1
- package/dist/locales/zh-Hant.mjs +71 -0
- package/dist/utils/brandPalette.cjs.js +3 -3
- package/dist/utils/brandPalette.d.ts +4 -2
- package/dist/utils/brandPalette.mjs +20 -20
- package/dist/vjdesigns-ui.css +1 -1
- package/package.json +90 -88
package/dist/ai/registry.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vjdesigns/vjdesigns-ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"summary": "Accessible React 19 component library: WCAG 2.2 AA verified (axe on every story, a contrast check on every color pair), 31 languages with right-to-left support, themeable with any brand color (auto-adjusted to stay accessible), tree-shakable, no runtime dependencies besides React.",
|
|
5
5
|
"install": "npm install @vjdesigns/vjdesigns-ui",
|
|
6
6
|
"stylesheet": "@vjdesigns/vjdesigns-ui/dist/vjdesigns-ui.css",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"Install: `npm install @vjdesigns/vjdesigns-ui` (peer: react, react-dom 18 or 19).",
|
|
9
9
|
"Import the stylesheet once: `import \"@vjdesigns/vjdesigns-ui/dist/vjdesigns-ui.css\";`",
|
|
10
10
|
"Import components from the package root: `import { Button, Field, Input } from \"@vjdesigns/vjdesigns-ui\";`",
|
|
11
|
-
"Theme: wrap the app in `<Theme brand=\"blue\" | \"purple\" | ... | \"#hex\" mode=\"light\" | \"dark\" accent=\"brand\" | \"
|
|
11
|
+
"Theme: wrap the app in `<Theme brand=\"blue\" | \"purple\" | ... | \"#hex\" mode=\"light\" | \"dark\" accent=\"brand\" | \"lime\" | \"#hex\">`.",
|
|
12
12
|
"Language: `<LocaleProvider locale=\"es\" messages={es}>` with `import es from \"@vjdesigns/vjdesigns-ui/locales/es\"`.",
|
|
13
13
|
"Forms: wrap each control in `Field` (label, hint, error, required); validate with `useFormValidation` + `ErrorSummary`.",
|
|
14
14
|
"Style with the props (variant, tone, size) and the --color-*, --space-*, --radius-* CSS variables; don't override internal class names."
|
|
@@ -17,27 +17,27 @@
|
|
|
17
17
|
{
|
|
18
18
|
"id": "setup",
|
|
19
19
|
"title": "Installation",
|
|
20
|
-
"markdown": "Install the package along with React and React DOM (version 18 or 19), which the components need in order to render:\
|
|
20
|
+
"markdown": "Install the package along with React and React DOM (version 18 or 19), which the components need in order to render:\n\n```bash\nnpm install @vjdesigns/vjdesigns-ui react react-dom\n```\n\nIf your project already uses React, you only need the first package:\n\n```bash\nnpm install @vjdesigns/vjdesigns-ui\n```\n\n### Import the styles\n\nThe component styles and shared design tokens ship as a separate stylesheet. Import it **once** at your app's entry point, or the components will render unstyled.\n\n**React:** in `main.tsx` (or `App.tsx`)\n\n```ts\nimport \"@vjdesigns/vjdesigns-ui/dist/vjdesigns-ui.css\";\n```\n\n**Angular:** add it to the `styles` array in `angular.json`\n\n```json\n\"styles\": [\n \"node_modules/@vjdesigns/vjdesigns-ui/dist/vjdesigns-ui.css\",\n \"src/styles.css\"\n]\n```\n\n**Vue:** in `main.ts`\n\n```ts\nimport \"@vjdesigns/vjdesigns-ui/dist/vjdesigns-ui.css\";\n```"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"id": "usage",
|
|
24
24
|
"title": "Usage",
|
|
25
|
-
"markdown": "The components are built with React. You can use them directly in a React project. In Angular or Vue, you mount them inside a small wrapper component, as shown below.\
|
|
25
|
+
"markdown": "The components are built with React. You can use them directly in a React project. In Angular or Vue, you mount them inside a small wrapper component, as shown below.\n\n### React\n\n```tsx\nimport { useState } from \"react\";\nimport { Button, CheckBox } from \"@vjdesigns/vjdesigns-ui\";\n\nexport function SignupForm() {\n const [accepted, setAccepted] = useState(false);\n\n return (\n <form>\n <CheckBox\n label=\"Accept terms\"\n checked={accepted}\n onCheckedChange={setAccepted}\n />\n <Button\n variant=\"solid\"\n tone=\"primary\"\n size=\"md\"\n disabled={!accepted}\n onClick={() => console.log(\"Submitted\")}\n >\n Submit\n </Button>\n </form>\n );\n}\n```\n\n### Angular\n\nMake a standalone wrapper that renders the React component into its host element. The wrapper uses `createElement`, so you don't need to change your JSX or TSX settings.\n\n```ts\n// vj-button.component.ts\nimport {\n AfterViewInit,\n Component,\n ElementRef,\n EventEmitter,\n Input,\n OnChanges,\n OnDestroy,\n Output,\n ViewChild,\n} from \"@angular/core\";\nimport { createElement, type MouseEvent as ReactMouseEvent, type ReactElement } from \"react\";\nimport { createRoot, Root } from \"react-dom/client\";\nimport {\n Button,\n type ButtonProps,\n type ButtonVariant,\n type Size,\n type Tone,\n} from \"@vjdesigns/vjdesigns-ui\";\n\n// Button has overloaded types (for `href` and `as`). Narrowing it here lets createElement type-check.\nconst ReactButton = Button as (props: ButtonProps) => ReactElement;\n\n@Component({\n selector: \"vj-button\",\n standalone: true,\n template: `<span #host></span>`,\n})\nexport class VjButtonComponent implements AfterViewInit, OnChanges, OnDestroy {\n @ViewChild(\"host\", { static: true }) host!: ElementRef<HTMLElement>;\n\n @Input() label = \"\";\n @Input() variant: ButtonVariant = \"solid\";\n @Input() tone: Tone = \"primary\";\n @Input() size: Size = \"md\";\n @Input() disabled = false;\n @Output() clicked = new EventEmitter<MouseEvent>();\n\n private root?: Root;\n\n ngAfterViewInit() {\n this.root = createRoot(this.host.nativeElement);\n this.render();\n }\n\n ngOnChanges() {\n this.render();\n }\n\n ngOnDestroy() {\n this.root?.unmount();\n }\n\n private render() {\n this.root?.render(\n createElement(\n ReactButton,\n {\n variant: this.variant,\n tone: this.tone,\n size: this.size,\n disabled: this.disabled,\n onClick: (event: ReactMouseEvent) => this.clicked.emit(event.nativeEvent),\n },\n this.label // the button's text is passed as children\n )\n );\n }\n}\n```\n\nImport the wrapper wherever you need it:\n\n```ts\n// app.component.ts\nimport { Component } from \"@angular/core\";\nimport { VjButtonComponent } from \"./vj-button.component\";\n\n@Component({\n selector: \"app-root\",\n standalone: true,\n imports: [VjButtonComponent],\n template: `<vj-button label=\"Submit\" tone=\"primary\" (clicked)=\"onSubmit()\" />`,\n})\nexport class AppComponent {\n onSubmit() {\n console.log(\"Submitted\");\n }\n}\n```\n\n### Vue 3\n\nMake a wrapper component that mounts the React component when it loads and re-renders it whenever its props change.\n\n```vue\n<!-- VjButton.vue -->\n<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref, watch } from \"vue\";\nimport { createElement, type MouseEvent as ReactMouseEvent, type ReactElement } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport {\n Button,\n type ButtonProps,\n type ButtonVariant,\n type Size,\n type Tone,\n} from \"@vjdesigns/vjdesigns-ui\";\n\n// Button has overloaded types (for `href` and `as`). Narrowing it here lets createElement type-check.\nconst ReactButton = Button as (props: ButtonProps) => ReactElement;\n\nconst props = withDefaults(\n defineProps<{\n label: string;\n variant?: ButtonVariant;\n tone?: Tone;\n size?: Size;\n disabled?: boolean;\n }>(),\n { variant: \"solid\", tone: \"primary\", size: \"md\", disabled: false }\n);\n\nconst emit = defineEmits<{ click: [event: MouseEvent] }>();\n\nconst host = ref<HTMLElement | null>(null);\nlet root: Root | undefined;\n\nfunction render() {\n root?.render(\n createElement(\n ReactButton,\n {\n variant: props.variant,\n tone: props.tone,\n size: props.size,\n disabled: props.disabled,\n onClick: (event: ReactMouseEvent) => emit(\"click\", event.nativeEvent),\n },\n props.label // the button's text is passed as children\n )\n );\n}\n\nonMounted(() => {\n root = createRoot(host.value!);\n render();\n});\n\nwatch(() => ({ ...props }), render);\n\nonBeforeUnmount(() => root?.unmount());\n</script>\n\n<template>\n <span ref=\"host\" />\n</template>\n```\n\nUse it like any other Vue component:\n\n```vue\n<script setup lang=\"ts\">\nimport VjButton from \"./VjButton.vue\";\n\nconst onSubmit = () => console.log(\"Submitted\");\n</script>\n\n<template>\n <VjButton label=\"Submit\" tone=\"primary\" @click=\"onSubmit\" />\n</template>\n```\n\n> **Tip:** Wrap `CheckBox` and any other component the same way. Pass its props into `createElement`, and send its callbacks back out as Angular outputs or Vue emits. For `CheckBox`, that means sending `onCheckedChange` back out."
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"id": "theming",
|
|
29
29
|
"title": "Theming",
|
|
30
|
-
"markdown": "Wrap any part of your app in `Theme` to set the color mode, brand palette, and accent for everything inside it:\
|
|
30
|
+
"markdown": "Wrap any part of your app in `Theme` to set the color mode, brand palette, and accent for everything inside it:\n\n```tsx\nimport { Theme, Button } from \"@vjdesigns/vjdesigns-ui\";\n\n<Theme mode=\"dark\" brand=\"purple\" fill>\n <Button>Dark, purple button</Button>\n</Theme>\n```\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `mode` | `\"light\" \\| \"dark\"` | inherited | Color mode for everything inside. |\n| `brand` | `\"black\" \\| \"blue\" \\| \"gold\" \\| \"green\" \\| \"purple\"`, a hex color, or `{ light, dark }` | inherited | Brand palette for buttons, links, focus rings and indicators. |\n| `accent` | `\"brand\" | \"lime\"`, a hex color, or `{ light, dark }` | inherited (the brand's own) | The bright highlight on badges, switches, progress bars, sliders, steppers, and active navigation. |\n| `fill` | `boolean` | `false` | Paints the theme's background and text color on the wrapper. |\n\nOutside React (or to theme the whole page), set the attributes on `<html>` instead:\n\n```html\n<html data-theme=\"dark\" data-brand=\"purple\" data-accent=\"lime\">\n```\n\n### Accent color\n\nThe accent is the energetic highlight: badges, switches, progress bars, sliders, steppers, and the active link in the Navbar. By default it follows the brand, as a bright shade of the brand's own color, so it always fits:\n\n| Brand | Default accent |\n| --- | --- |\n| `blue` (default) | sky blue `#76c7ff` |\n| `purple` | lavender `#c0b0ff` |\n| `gold` | amber `#f5ad5f` |\n| `green` | mint `#72d4ad` |\n| `black` | lime `#c8f031` (black has no hue to build on) |\n| a custom hex | generated from that color |\n\nTo choose a different one:\n\n```tsx\n<Theme accent=\"lime\">…</Theme> // the original bright yellow-green (\"volt\", its former name, still works)\n<Theme accent=\"#ff6b00\">…</Theme> // any color\n<Theme accent=\"brand\">…</Theme> // back to the brand's own, inside an area with another accent\n```\n\nAccents always carry dark text. A color too dark for that is lightened (same hue) just enough to meet WCAG AA, with a warning in development. An accent you set also holds inside nested brands. In CSS, `accentCss(\"name\", \"#ff6b00\")` generates a `[data-accent=\"name\"]` rule, like `brandCss` below.\n\n### Custom brand colors\n\nPass any hex color as `brand` and a full palette is generated for both modes:\n\n```tsx\n<Theme brand=\"#e11d48\">…</Theme>\n<Theme brand={{ light: \"#4338ca\", dark: \"#f472b6\" }}>…</Theme>\n```\n\nEvery generated palette meets WCAG 2.2 AA. A single color is used exactly as given in the mode where it has enough contrast (dark colors in light mode, light colors in dark mode), and the other mode gets a matching shade of the same hue. If a color doesn't have enough contrast (for example, a mid-tone that fails in both modes), its lightness is shifted just enough to pass, and a warning is logged in development.\n\nTo define a brand in CSS instead (for the whole page, applied before any JavaScript runs), generate a stylesheet rule with `brandCss`:\n\n```ts\nimport { brandCss } from \"@vjdesigns/vjdesigns-ui\";\n\nbrandCss(\"acme\", \"#e11d48\"); // '[data-brand=\"acme\"] { --brand-light-primary: … }'\n```\n\nAdd the rule to your stylesheet and use `<html data-brand=\"acme\">`. `createBrandPalette(color)` returns the generated colors as an object, if you need them directly."
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"id": "languages",
|
|
34
34
|
"title": "Languages",
|
|
35
|
-
"markdown": "Components have built-in text: button names like \"Close\" and \"Next page\", screen-reader announcements like \"3 results available\", validation messages, and date formats. Wrap your app in `LocaleProvider` to show all of it in another language:\
|
|
35
|
+
"markdown": "Components have built-in text: button names like \"Close\" and \"Next page\", screen-reader announcements like \"3 results available\", validation messages, and date formats. Wrap your app in `LocaleProvider` to show all of it in another language:\n\n```tsx\nimport { LocaleProvider } from \"@vjdesigns/vjdesigns-ui\";\nimport es from \"@vjdesigns/vjdesigns-ui/locales/es\";\n\n<LocaleProvider locale=\"es\" messages={es}>\n <App />\n</LocaleProvider>\n```\n\n`LocaleProvider` sets:\n\n- **Built-in text** for every component, from `messages`.\n- **Dates and numbers** in the locale's format (`23/10/2026`, `1.234,5`), and plurals that follow the language's rules.\n- **Writing direction**: Arabic, Hebrew, Persian, and Urdu get `dir=\"rtl\"`, and layouts and arrow keys mirror.\n- **`lang`** on its wrapper, so screen readers pronounce the text correctly. Popups (menus, dialogs, tooltips) keep the language of the area that opened them.\n\nEach locale is a separate import, so your app only downloads the languages it uses. Without a `LocaleProvider`, components use English text and the browser's date and number format.\n\nThis library only translates its own built-in text. Text your app passes in, like `<Button>Save</Button>`, is your app's content: translate it with your app's i18n library (react-i18next, FormatJS, and so on).\n\n### What gets translated\n\nThe language is never picked automatically from where the user lives or their browser settings. Your app chooses it by rendering a `LocaleProvider`. For example, with `<LocaleProvider locale=\"de\" messages={de}>`:\n\n| | Translated? |\n| --- | --- |\n| Text your app passes in, like `<Button>Save</Button>` | No: your app translates it |\n| Built-in names, like \"Close\", \"Next page\", \"Clear search\" | Yes: \"Schließen\", \"Nächste Seite\", \"Suche löschen\" |\n| Screen-reader announcements, like \"3 results available\" | Yes: \"3 Ergebnisse verfügbar\" |\n| Validation messages, empty states, file-upload prompts | Yes |\n| Dates and numbers | Yes: \"23.10.2026\", \"4,5\" |\n\nTo follow the user's browser language, pass it yourself: `locale={navigator.language}` together with the matching `messages` (see [Switching language at runtime](#switching-language-at-runtime)).\n\n### Bundled languages\n\n`ar` Arabic · `bn` Bengali · `cs` Czech · `da` Danish · `de` German · `el` Greek · `es` Spanish · `fa` Persian · `fi` Finnish · `fr` French · `he` Hebrew · `hi` Hindi · `hu` Hungarian · `id` Indonesian · `it` Italian · `ja` Japanese · `ko` Korean · `nb` Norwegian Bokmål · `nl` Dutch · `pl` Polish · `pt-BR` Portuguese (Brazil) · `ro` Romanian · `ru` Russian · `sv` Swedish · `th` Thai · `tr` Turkish · `uk` Ukrainian · `ur` Urdu · `vi` Vietnamese · `zh-Hans` Chinese (Simplified) · `zh-Hant` Chinese (Traditional), plus English (the default).\n\nThe translations are machine-generated and haven't been reviewed by native speakers yet. If you find a wrong or awkward phrase, override it (below) and report it so the bundled locale can be fixed. (Working on the library itself? See [Translating built-in text](#translating-built-in-text).)\n\n### Changing a few strings\n\nPass only the strings you want to change. Everything else falls back to the outer provider's language (English at the top):\n\n```tsx\n<LocaleProvider locale=\"en-GB\" messages={{ pagination: { next: \"Onwards\" } }}>\n```\n\nProviders nest, so an inner provider can switch the language for one part of the page. Per-component props such as `clearLabel` or `labels` still win over the provider.\n\n### Other languages\n\nFor a language that isn't bundled, write a complete messages object. TypeScript flags any missing string. Use English as the template:\n\n```ts\nimport { en, plural, type Messages } from \"@vjdesigns/vjdesigns-ui\";\n\nconst p = plural(\"sw\");\nexport const sw: Messages = {\n ...en,\n pagination: { ...en.pagination, next: \"Ukurasa unaofuata\", previous: \"Ukurasa uliopita\" },\n searchInput: {\n ...en.searchInput,\n results: (count) => p(count, { \"=0\": \"Hakuna matokeo\", one: \"Tokeo #\", other: \"Matokeo #\" }),\n },\n // ...\n};\n```\n\n`plural(locale)` picks the right plural form for any language (`#` is the formatted number).\n\n### Switching language at runtime\n\nList the languages your app offers as loaders, so each one is downloaded only when picked:\n\n```tsx\nimport { LocaleProvider, type Messages } from \"@vjdesigns/vjdesigns-ui\";\n\nconst loaders: Record<string, () => Promise<{ default: Messages }>> = {\n es: () => import(\"@vjdesigns/vjdesigns-ui/locales/es\"),\n fr: () => import(\"@vjdesigns/vjdesigns-ui/locales/fr\"),\n ar: () => import(\"@vjdesigns/vjdesigns-ui/locales/ar\"),\n};\n\nfunction App({ lang }: { lang: string }) {\n const [messages, setMessages] = useState<Messages>();\n useEffect(() => {\n const load = loaders[lang];\n if (load) load().then((mod) => setMessages(mod.default));\n else setMessages(undefined); // English\n }, [lang]);\n // Until a language loads, the previous one (or English) shows.\n return <LocaleProvider locale={lang} messages={messages}>…</LocaleProvider>;\n}\n```\n\nFor server rendering, also set `<html lang=\"…\" dir=\"…\">` on the page. `getLocaleDirection(locale)` returns `\"ltr\"` or `\"rtl\"`."
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"id": "validation",
|
|
39
39
|
"title": "Form validation",
|
|
40
|
-
"markdown": "> **Front-end validation is not security.** It helps people fill in a form correctly, but anyone can turn off JavaScript or send requests straight to your server. Always validate again on the server. The format checks below (`isEmail`, `isIP`, …) are plain functions you can reuse there.\
|
|
40
|
+
"markdown": "> **Front-end validation is not security.** It helps people fill in a form correctly, but anyone can turn off JavaScript or send requests straight to your server. Always validate again on the server. The format checks below (`isEmail`, `isIP`, …) are plain functions you can reuse there.\n\n`useFormValidation` checks a form when it's submitted, using the browser's own rules on each input (`required`, `type=\"email\"`, `minLength`, `pattern`, `min` / `max`) plus any rules you add. Messages follow the current language.\n\n```tsx\nimport {\n useFormValidation, rules, ErrorSummary, Field, Input, PasswordInput, Button,\n} from \"@vjdesigns/vjdesigns-ui\";\n\nfunction SignUp() {\n const form = useFormValidation({\n rules: {\n confirm: rules.matches(\"password\", \"The passwords don't match.\"),\n serverIp: rules.ip(), // IPv4 or IPv6\n },\n // Runs only when everything is valid. Return the server's errors to show them on their fields.\n onValid: async (values) => {\n const response = await api.signUp(values);\n return response.errors; // e.g. { username: \"That username is taken.\" }\n },\n });\n\n return (\n <form {...form.formProps}>\n <ErrorSummary ref={form.summaryRef} errors={form.errorList} />\n <Field label=\"Email\" required error={form.errors.email}>\n <Input name=\"email\" type=\"email\" />\n </Field>\n <Field label=\"Password\" required error={form.errors.password}>\n <PasswordInput name=\"password\" minLength={12} />\n </Field>\n <Field label=\"Confirm password\" required error={form.errors.confirm}>\n <PasswordInput name=\"confirm\" />\n </Field>\n <Field label=\"Server IP\" error={form.errors.serverIp}>\n <Input name=\"serverIp\" />\n </Field>\n <Button type=\"submit\" loading={form.isSubmitting}>Create account</Button>\n </form>\n );\n}\n```\n\nHow it behaves:\n\n- **On submit**, every field is checked. If anything is wrong, the `ErrorSummary` at the top lists the problems and takes focus, so screen readers announce it. Each item links to its field. Without a summary, focus goes to the first invalid field.\n- **Errors are keyed by the field's `name`.** Pass `form.errors.<name>` to each `Field`'s `error`.\n- **After a failed submit**, an error disappears as soon as the field is fixed (while typing), and a field is fully re-checked when you leave it. It doesn't nag while you're still typing.\n- **Server errors** (returned from `onValid`, or set with `form.setErrors({...})`) show on their fields and stay until that field is edited.\n- **Emails** must have a real domain (`name@example.com`). The browser's own check also accepts `name@localhost`.\n\n### Rules and format checks\n\n| Rule | Checks |\n| --- | --- |\n| `rules.required()` | Not empty (for controls without a native `required`) |\n| `rules.email()` | An email address in the usual format, with a top-level domain |\n| `rules.ip()` / `rules.ip({ version: 4 })` / `rules.ip({ version: 6 })` | An IPv4 or IPv6 address |\n| `rules.minLength(n)` / `rules.maxLength(n)` | Length in characters |\n| `rules.pattern(/regex/, \"message\")` | The whole value matches |\n| `rules.matches(\"otherField\")` | Equals another field (confirm password) |\n| your own `(value, { values }) => message` | Anything else |\n\nEach rule takes an optional custom message. The checks behind them are exported for use anywhere, including your server:\n\n- `isEmail(value)`\n- `isIPv4(value)`: four numbers 0–255, no leading zeros\n- `isIPv6(value)`: including `::` shorthand and IPv4 tails like `::ffff:192.168.0.1`\n- `isIP(value, version?)`"
|
|
41
41
|
}
|
|
42
42
|
],
|
|
43
43
|
"components": [
|
|
@@ -1264,6 +1264,215 @@
|
|
|
1264
1264
|
}
|
|
1265
1265
|
]
|
|
1266
1266
|
},
|
|
1267
|
+
{
|
|
1268
|
+
"name": "Chat",
|
|
1269
|
+
"category": "data-display",
|
|
1270
|
+
"folder": "Chat",
|
|
1271
|
+
"import": "import { Chat } from \"@vjdesigns/vjdesigns-ui\";",
|
|
1272
|
+
"description": "A chat interface for an AI assistant (or a person), with no backend of its\nown: `onSend` connects it to any API and can stream the reply. Includes a\nscrolling conversation that follows new messages, an auto-growing message\nbox (Enter sends, Shift+Enter adds a line), Stop and Retry, copy buttons,\nsuggested prompts, and screen reader announcements of each reply.\n\n```tsx\n<Chat\n style={{ blockSize: 560 }}\n onSend={async function* (text, { messages, signal }) {\n const res = await fetch(\"/api/chat\", {\n method: \"POST\",\n body: JSON.stringify({ messages }),\n signal,\n });\n yield* readTextStream(res);\n }}\n/>\n```",
|
|
1273
|
+
"nativeElement": "section",
|
|
1274
|
+
"props": [
|
|
1275
|
+
{
|
|
1276
|
+
"name": "messages",
|
|
1277
|
+
"type": "ChatMessage[]",
|
|
1278
|
+
"required": false,
|
|
1279
|
+
"description": "Controlled conversation."
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
"name": "defaultMessages",
|
|
1283
|
+
"type": "ChatMessage[]",
|
|
1284
|
+
"required": false,
|
|
1285
|
+
"description": "Initial conversation when uncontrolled."
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
"name": "onMessagesChange",
|
|
1289
|
+
"type": "(messages: ChatMessage[]) => void",
|
|
1290
|
+
"required": false,
|
|
1291
|
+
"description": "Called with the whole conversation whenever it changes (new messages, every streamed chunk, status changes)."
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
"name": "onSend",
|
|
1295
|
+
"type": "( text: string, context: ChatSendContext, ) => ChatSendResult | Promise<ChatSendResult>",
|
|
1296
|
+
"required": false,
|
|
1297
|
+
"description": "Connects the Chat to your backend. Called with the user's text when they\nsend a message (or retry). Return the reply as a string, stream it as an\n`AsyncIterable<string>`, or return nothing and add it yourself through\ncontrolled `messages`. Throwing (or rejecting) turns the reply into an\nerror with a Retry button."
|
|
1298
|
+
},
|
|
1299
|
+
{
|
|
1300
|
+
"name": "onStop",
|
|
1301
|
+
"type": "() => void",
|
|
1302
|
+
"required": false,
|
|
1303
|
+
"description": "Called when the user stops a reply, after its `signal` is aborted."
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
"name": "onError",
|
|
1307
|
+
"type": "(error: unknown) => void",
|
|
1308
|
+
"required": false,
|
|
1309
|
+
"description": "Called with whatever `onSend` threw, for logging. The user sees `labels.error` (or the message's `error`)."
|
|
1310
|
+
},
|
|
1311
|
+
{
|
|
1312
|
+
"name": "draft",
|
|
1313
|
+
"type": "string",
|
|
1314
|
+
"required": false,
|
|
1315
|
+
"description": "Controlled text of the message box."
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
"name": "defaultDraft",
|
|
1319
|
+
"type": "string",
|
|
1320
|
+
"required": false,
|
|
1321
|
+
"description": "Initial text of the message box when uncontrolled."
|
|
1322
|
+
},
|
|
1323
|
+
{
|
|
1324
|
+
"name": "onDraftChange",
|
|
1325
|
+
"type": "(draft: string) => void",
|
|
1326
|
+
"required": false,
|
|
1327
|
+
"description": "Called with the message box text on every edit."
|
|
1328
|
+
},
|
|
1329
|
+
{
|
|
1330
|
+
"name": "suggestions",
|
|
1331
|
+
"type": "string[]",
|
|
1332
|
+
"required": false,
|
|
1333
|
+
"description": "Prompts offered as buttons while the conversation is empty. Pressing one sends it."
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
"name": "emptyState",
|
|
1337
|
+
"type": "ReactNode",
|
|
1338
|
+
"required": false,
|
|
1339
|
+
"description": "Shown while the conversation is empty, above the suggestions. Defaults to a short greeting (`labels.emptyTitle`)."
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
"name": "renderContent",
|
|
1343
|
+
"type": "(message: ChatMessage) => ReactNode",
|
|
1344
|
+
"required": false,
|
|
1345
|
+
"description": "Renders a message's content, e.g. through your Markdown renderer.\nDefaults to plain text with line breaks kept. Never render\nunsanitized HTML from a model or a user."
|
|
1346
|
+
},
|
|
1347
|
+
{
|
|
1348
|
+
"name": "assistantName",
|
|
1349
|
+
"type": "string",
|
|
1350
|
+
"required": false,
|
|
1351
|
+
"description": "Name of the assistant, read to screen readers (\"Coach said:\"). Defaults to `labels.assistant`."
|
|
1352
|
+
},
|
|
1353
|
+
{
|
|
1354
|
+
"name": "assistantAvatar",
|
|
1355
|
+
"type": "ReactNode",
|
|
1356
|
+
"required": false,
|
|
1357
|
+
"description": "Shown beside assistant messages (e.g. an `Avatar` or `Icon`). Decorative: hidden from assistive technology."
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
"name": "userAvatar",
|
|
1361
|
+
"type": "ReactNode",
|
|
1362
|
+
"required": false,
|
|
1363
|
+
"description": "Shown beside user messages. Decorative: hidden from assistive technology."
|
|
1364
|
+
},
|
|
1365
|
+
{
|
|
1366
|
+
"name": "copyable",
|
|
1367
|
+
"type": "boolean",
|
|
1368
|
+
"required": false,
|
|
1369
|
+
"default": "true",
|
|
1370
|
+
"description": "Show a Copy button under finished assistant messages."
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
"name": "disclaimer",
|
|
1374
|
+
"type": "ReactNode",
|
|
1375
|
+
"required": false,
|
|
1376
|
+
"description": "Small print under the message box, e.g. \"AI can make mistakes.\""
|
|
1377
|
+
},
|
|
1378
|
+
{
|
|
1379
|
+
"name": "placeholder",
|
|
1380
|
+
"type": "string",
|
|
1381
|
+
"required": false,
|
|
1382
|
+
"description": "Placeholder of the message box. Defaults to `labels.placeholder`."
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
"name": "maxLength",
|
|
1386
|
+
"type": "number",
|
|
1387
|
+
"required": false,
|
|
1388
|
+
"description": "Maximum length of a message, in characters. Shows a counter near the limit."
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
"name": "disabled",
|
|
1392
|
+
"type": "boolean",
|
|
1393
|
+
"required": false,
|
|
1394
|
+
"description": "Disables the message box and the suggestions."
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
"name": "labels",
|
|
1398
|
+
"type": "Partial<ChatLabels>",
|
|
1399
|
+
"required": false,
|
|
1400
|
+
"description": "Overrides for the built-in text."
|
|
1401
|
+
}
|
|
1402
|
+
],
|
|
1403
|
+
"subcomponents": [],
|
|
1404
|
+
"storybook": "Data Display/Chat",
|
|
1405
|
+
"stories": [
|
|
1406
|
+
{
|
|
1407
|
+
"name": "Playground",
|
|
1408
|
+
"description": ""
|
|
1409
|
+
},
|
|
1410
|
+
{
|
|
1411
|
+
"name": "Send And Stream",
|
|
1412
|
+
"description": "Type a message and press Enter: it is sent, and the streamed reply is announced when done."
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
"name": "Shift Enter Adds Line",
|
|
1416
|
+
"description": "Shift+Enter adds a line instead of sending."
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
"name": "String Reply",
|
|
1420
|
+
"description": "`onSend` can return the whole reply as a string (or a promise of one)."
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
"name": "With Suggestions",
|
|
1424
|
+
"description": "While the conversation is empty, `emptyState` and `suggestions` show. Pressing a suggestion sends it."
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
"name": "With Avatars",
|
|
1428
|
+
"description": "Avatars beside each speaker. They're decorative; the speaker is read from hidden text."
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
"name": "Long Conversation",
|
|
1432
|
+
"description": "A long conversation. Scroll up, and a button jumps back to the latest message."
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
"name": "Long Content",
|
|
1436
|
+
"description": "A long unbroken string (a URL, say) wraps instead of overflowing the bubble."
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
"name": "Error And Retry",
|
|
1440
|
+
"description": "When `onSend` throws, the reply shows an error with a Retry button. This backend fails the first time."
|
|
1441
|
+
},
|
|
1442
|
+
{
|
|
1443
|
+
"name": "Stop Streaming",
|
|
1444
|
+
"description": "While a reply streams, the send button becomes Stop (Escape in the message box works too)."
|
|
1445
|
+
},
|
|
1446
|
+
{
|
|
1447
|
+
"name": "Disabled",
|
|
1448
|
+
"description": ""
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
"name": "With Max Length",
|
|
1452
|
+
"description": "`maxLength` limits a message and shows a counter near the limit."
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
"name": "System Messages",
|
|
1456
|
+
"description": "System messages (joins, handoffs, context notes) are centered notes."
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
"name": "Custom Rendering",
|
|
1460
|
+
"description": "`renderContent` renders messages your way, e.g. through a Markdown\nrenderer. This one turns `**bold**` into `<strong>` and keeps paragraphs.\nNever render unsanitized HTML from a model."
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
"name": "Controlled",
|
|
1464
|
+
"description": "Controlled: the parent owns the messages, so it can save, clear, or seed\nthem. Here, `onSend` returns nothing and the parent adds the reply itself\n(the way a WebSocket or a human agent would)."
|
|
1465
|
+
},
|
|
1466
|
+
{
|
|
1467
|
+
"name": "Connecting ABackend",
|
|
1468
|
+
"description": "How to connect a real backend: stream server-sent events from your API\nroute with `readEventStream`, passing `signal` so Stop cancels the request.\n\n```tsx\nimport { Chat, readEventStream } from \"@vjdesigns/vjdesigns-ui\";\n\n<Chat\n onSend={async function* (_text, { messages, signal }) {\n const res = await fetch(\"/api/chat\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n messages: messages.map(({ role, content }) => ({ role, content })),\n }),\n signal,\n });\n if (!res.ok) throw new Error(`Chat request failed: ${res.status}`);\n for await (const data of readEventStream(res)) {\n if (data === \"[DONE]\") break;\n yield JSON.parse(data).text ?? \"\";\n }\n }}\n/>\n```\n\nKeep API keys on your server: the browser should call your own route,\nwhich calls the model provider. This story fakes the server with an\nin-memory event stream."
|
|
1469
|
+
},
|
|
1470
|
+
{
|
|
1471
|
+
"name": "Right To Left",
|
|
1472
|
+
"description": "Right-to-left: user messages sit on the inline end, which is the left in RTL."
|
|
1473
|
+
}
|
|
1474
|
+
]
|
|
1475
|
+
},
|
|
1267
1476
|
{
|
|
1268
1477
|
"name": "Chip",
|
|
1269
1478
|
"category": "data-display",
|
|
@@ -1841,7 +2050,7 @@
|
|
|
1841
2050
|
"category": "data-display",
|
|
1842
2051
|
"folder": "DataGrid",
|
|
1843
2052
|
"import": "import { DataGrid } from \"@vjdesigns/vjdesigns-ui\";",
|
|
1844
|
-
"description": "An interactive data table (WAI-ARIA APG \"Data Grid\"): sortable columns\
|
|
2053
|
+
"description": "An interactive data table (WAI-ARIA APG \"Data Grid\"): sortable columns\n(with Shift for multi-column sorts), search filtering, single or\nmultiple row selection, pagination, and a full keyboard model. Works with\nclient-side data, or server-side data with `manual`.\n\nKeyboard: the grid is one Tab stop. Arrow keys move between cells\n(mirrored in RTL), Home / End go to the row's first or last cell, Ctrl+Home\n/ Ctrl+End to the first or last cell of the grid, Page Up / Page Down move\nten rows. On a header, Enter sorts (Shift+Enter adds a secondary sort).\nSpace selects the row, Shift+click or Shift+Space selects a range, and\nCtrl/⌘+A selects every row. A cell with a single button, link, or\ncheckbox focuses it directly; for cells with several controls or a text\nfield, Enter or F2 moves into the cell, Tab cycles inside it, and Escape\nreturns to the grid.\n\nScreen readers hear sort changes and filter result counts; the grid is a\nreal `<table role=\"grid\">` with `aria-sort`, `aria-selected`,\n`aria-rowcount`, and `aria-rowindex`.\n\n```tsx\n<DataGrid\n aria-label=\"Roster\"\n rows={players}\n columns={[\n { key: \"name\", header: \"Player\", sortable: true, rowHeader: true },\n { key: \"ppg\", header: \"PPG\", sortable: true, numeric: true },\n ]}\n selectionMode=\"multiple\"\n pageSize={10}\n/>\n```",
|
|
1845
2054
|
"props": [
|
|
1846
2055
|
{
|
|
1847
2056
|
"name": "rows",
|
|
@@ -1916,7 +2125,7 @@
|
|
|
1916
2125
|
"name": "filter",
|
|
1917
2126
|
"type": "string",
|
|
1918
2127
|
"required": false,
|
|
1919
|
-
"description": "Search text: rows are kept when their filterable columns contain every\
|
|
2128
|
+
"description": "Search text: rows are kept when their filterable columns contain every\nword (case- and diacritic-insensitive). Render your own search field\n(e.g. `SearchInput`) and pass its text here."
|
|
1920
2129
|
},
|
|
1921
2130
|
{
|
|
1922
2131
|
"name": "selectionMode",
|
|
@@ -1980,7 +2189,7 @@
|
|
|
1980
2189
|
"type": "boolean",
|
|
1981
2190
|
"required": false,
|
|
1982
2191
|
"default": "false",
|
|
1983
|
-
"description": "Server-side data: the grid doesn't sort, filter, or page `rows` itself.\
|
|
2192
|
+
"description": "Server-side data: the grid doesn't sort, filter, or page `rows` itself.\nIt shows them as given and reports `sort`, `filter`, and `page` changes\nfor you to fetch. Pass `rowCount` for the total."
|
|
1984
2193
|
},
|
|
1985
2194
|
{
|
|
1986
2195
|
"name": "rowCount",
|
|
@@ -2047,7 +2256,7 @@
|
|
|
2047
2256
|
"type": "\"auto\" | \"fixed\"",
|
|
2048
2257
|
"required": false,
|
|
2049
2258
|
"default": "\"auto\"",
|
|
2050
|
-
"description": "Table layout. `fixed` makes column widths exact (content wraps or clips\
|
|
2259
|
+
"description": "Table layout. `fixed` makes column widths exact (content wraps or clips\ninstead of widening a column)."
|
|
2051
2260
|
},
|
|
2052
2261
|
{
|
|
2053
2262
|
"name": "tableStyle",
|
|
@@ -2083,7 +2292,7 @@
|
|
|
2083
2292
|
},
|
|
2084
2293
|
{
|
|
2085
2294
|
"name": "Keyboard",
|
|
2086
|
-
"description": "Keyboard: the grid is one Tab stop. Arrows move between cells, Home/End\
|
|
2295
|
+
"description": "Keyboard: the grid is one Tab stop. Arrows move between cells, Home/End\nto the row's ends, Ctrl+Home/End to the grid's corners. Enter on a\nheader sorts; Space selects the row."
|
|
2087
2296
|
},
|
|
2088
2297
|
{
|
|
2089
2298
|
"name": "Range Selection",
|
|
@@ -2091,7 +2300,7 @@
|
|
|
2091
2300
|
},
|
|
2092
2301
|
{
|
|
2093
2302
|
"name": "Interactive Cells",
|
|
2094
|
-
"description": "Cells with several controls: Enter (or F2) steps into the cell, Tab\
|
|
2303
|
+
"description": "Cells with several controls: Enter (or F2) steps into the cell, Tab\ncycles its controls, Escape steps back out. A cell with one button\nfocuses it directly."
|
|
2095
2304
|
},
|
|
2096
2305
|
{
|
|
2097
2306
|
"name": "Single Selection",
|
|
@@ -2099,7 +2308,7 @@
|
|
|
2099
2308
|
},
|
|
2100
2309
|
{
|
|
2101
2310
|
"name": "Server Side",
|
|
2102
|
-
"description": "Server-side data: with `manual`, the grid reports sort and page changes\
|
|
2311
|
+
"description": "Server-side data: with `manual`, the grid reports sort and page changes\nand shows the rows you fetch. `rowCount` gives the total."
|
|
2103
2312
|
},
|
|
2104
2313
|
{
|
|
2105
2314
|
"name": "Empty And Loading",
|
|
@@ -2119,7 +2328,7 @@
|
|
|
2119
2328
|
},
|
|
2120
2329
|
{
|
|
2121
2330
|
"name": "Extending Columns",
|
|
2122
|
-
"description": "Extending columns: `headerEnd` adds content after a header's label,\
|
|
2331
|
+
"description": "Extending columns: `headerEnd` adds content after a header's label,\noutside its sort button (here a column menu button), and `className` /\n`style` reach every cell of a column. Controls marked\n`data-grid-secondary` don't steal the header's focus: the sort button\nstays its target, and F2 then Tab reaches them."
|
|
2123
2332
|
}
|
|
2124
2333
|
]
|
|
2125
2334
|
},
|
|
@@ -4002,7 +4211,7 @@
|
|
|
4002
4211
|
"category": "data-display",
|
|
4003
4212
|
"folder": "VirtualList",
|
|
4004
4213
|
"import": "import { VirtualList } from \"@vjdesigns/vjdesigns-ui\";",
|
|
4005
|
-
"description": "A list that renders only the items in view, so tens of thousands of rows\
|
|
4214
|
+
"description": "A list that renders only the items in view, so tens of thousands of rows\nscroll smoothly. Items may have any height (they're measured as they\nrender). Built on `useVirtualizer` and `ScrollArea`.\n\nAccessibility: a real `role=\"list\"` whose items carry `aria-setsize` and\n`aria-posinset`, so screen readers announce \"item 4,812 of 10,000\" even\nthough only a few dozen exist in the page. The scroll area is keyboard\nscrollable, and Tab through items' links and buttons keeps working (new\nitems render as focus scrolls them into view). Browser find-in-page only\nsees rendered items, so offer search for long lists.\n\n```tsx\n<VirtualList\n aria-label=\"All players\"\n items={players}\n height=\"24rem\"\n getKey={(p) => p.id}\n renderItem={(p) => <PlayerRow player={p} />}\n/>\n```",
|
|
4006
4215
|
"props": [
|
|
4007
4216
|
{
|
|
4008
4217
|
"name": "items",
|
|
@@ -4450,7 +4659,7 @@
|
|
|
4450
4659
|
"description": "Every tone, solid. `accent` is the promo strip, always with dark text."
|
|
4451
4660
|
},
|
|
4452
4661
|
{
|
|
4453
|
-
"name": "
|
|
4662
|
+
"name": "Accent Promo",
|
|
4454
4663
|
"description": "The accent promo strip: bold, dark text on accent, with a strong call to action."
|
|
4455
4664
|
},
|
|
4456
4665
|
{
|
|
@@ -4585,7 +4794,7 @@
|
|
|
4585
4794
|
"category": "feedback",
|
|
4586
4795
|
"folder": "ErrorSummary",
|
|
4587
4796
|
"import": "import { ErrorSummary } from \"@vjdesigns/vjdesigns-ui\";",
|
|
4588
|
-
"description": "The list of a form's errors, shown above it after a failed submit: a danger\
|
|
4797
|
+
"description": "The list of a form's errors, shown above it after a failed submit: a danger\n`Alert` whose items link to their fields (activating one moves focus to the\nfield). It's focusable (`tabIndex={-1}`): give `useFormValidation`'s\n`summaryRef` as `ref` and a failed submit moves focus here, so screen\nreaders announce the heading and the list. The title is a real heading\n(`h2` by default; set `headingLevel` to fit the page outline).\n\n```tsx\n<ErrorSummary ref={form.summaryRef} errors={form.errorList} />\n```",
|
|
4589
4798
|
"nativeElement": "div",
|
|
4590
4799
|
"props": [
|
|
4591
4800
|
{
|
|
@@ -6048,7 +6257,7 @@
|
|
|
6048
6257
|
"category": "forms",
|
|
6049
6258
|
"folder": "ColorPicker",
|
|
6050
6259
|
"import": "import { ColorInput } from \"@vjdesigns/vjdesigns-ui\";",
|
|
6051
|
-
"description": "A pill field showing a swatch and the color value; pressing it opens a\
|
|
6260
|
+
"description": "A pill field showing a swatch and the color value; pressing it opens a\n`ColorPicker` in a `Popover` (a non-modal dialog on the shared overlay\nlayer stack: Escape and outside presses close it, focus returns to the\nfield). Takes the same value, format, alpha, swatch, and state props as\n`ColorPicker`.\n\nThe trigger is a native `<button>` with `aria-haspopup=\"dialog\"` and\n`aria-expanded`. Its name is the label plus the current value and a plain\ndescription (\"Jersey color, #1d4ed8, dark blue\"). Inside a `Field`, the\nlabel, hint, error, invalid, disabled, and read-only state are wired to\nit. A read-only field still opens, so the values can be inspected.",
|
|
6052
6261
|
"nativeElement": "button",
|
|
6053
6262
|
"props": [
|
|
6054
6263
|
{
|
|
@@ -6777,7 +6986,7 @@
|
|
|
6777
6986
|
"category": "forms",
|
|
6778
6987
|
"folder": "DatePicker",
|
|
6779
6988
|
"import": "import { DatePicker } from \"@vjdesigns/vjdesigns-ui\";",
|
|
6780
|
-
"description": "A date field: type a date in the locale's short format (or ISO), or press\
|
|
6989
|
+
"description": "A date field: type a date in the locale's short format (or ISO), or press\nthe calendar button to pick one from a `Calendar` in a popup dialog.\n\nThe value is an ISO date string (\"YYYY-MM-DD\"), so it never shifts with\ntime zones. The text field shows it in the locale's numeric format; on\nblur (or Enter), typed text is parsed and normalized, or rejected with an\ninline error. Inside a `Field`, label, hint, error, required, disabled,\nand invalid state are wired to the text input.\n\nThe popup is a modal `Popover` on the shared overlay layer stack, so\nEscape closes only the calendar, also inside a `Modal` or `Drawer`.\n\n```tsx\n<Field label=\"Game day\">\n <DatePicker name=\"gameDay\" min=\"2026-10-01\" onValueChange={setDay} />\n</Field>\n```",
|
|
6781
6990
|
"nativeElement": "input",
|
|
6782
6991
|
"props": [
|
|
6783
6992
|
{
|
|
@@ -6979,13 +7188,13 @@
|
|
|
6979
7188
|
"name": "onInvalidInput",
|
|
6980
7189
|
"type": "(error: DateInputError) => void",
|
|
6981
7190
|
"required": false,
|
|
6982
|
-
"description": "Called when the user leaves the field with text that isn't a valid,\
|
|
7191
|
+
"description": "Called when the user leaves the field with text that isn't a valid,\nallowed date. Use it to set your own `Field` error."
|
|
6983
7192
|
},
|
|
6984
7193
|
{
|
|
6985
7194
|
"name": "invalidMessage",
|
|
6986
7195
|
"type": "ReactNode | ((error: DateInputError) => ReactNode)",
|
|
6987
7196
|
"required": false,
|
|
6988
|
-
"description": "Message shown under the field when typed text is rejected. Pass a\
|
|
7197
|
+
"description": "Message shown under the field when typed text is rejected. Pass a\nfunction to word it per reason, or `null` to show none (for example\nwhen you show a `Field` error from `onInvalidInput` instead).\nDefaults to \"Enter a date as MM/DD/YYYY.\" and range/availability messages."
|
|
6989
7198
|
},
|
|
6990
7199
|
{
|
|
6991
7200
|
"name": "chooseDateLabel",
|
|
@@ -7109,7 +7318,7 @@
|
|
|
7109
7318
|
"category": "forms",
|
|
7110
7319
|
"folder": "DatePicker",
|
|
7111
7320
|
"import": "import { DateRangePicker } from \"@vjdesigns/vjdesigns-ui\";",
|
|
7112
|
-
"description": "A date range field: two text fields (start and end) typed in the locale's\
|
|
7321
|
+
"description": "A date range field: two text fields (start and end) typed in the locale's\nshort format, plus a calendar button that opens a range `Calendar` (two\nmonths by default) in a modal popup.\n\nThe value is `{ start, end }` with ISO dates (\"YYYY-MM-DD\"). In the\ncalendar, the first pick sets the start and the second the end; the range\nis committed (and the popup closes) only when both are picked, so Escape\nafter one pick changes nothing. Typed dates are validated like\n`DatePicker`'s, plus an end before the start is rejected.\n\nAccessibility: the pair is a `role=\"group\"` named by the `Field` label\n(or `aria-label`); each field is named by that label plus its own\nvisible label (\"Season dates, Start date\"). Field hint, error, required, disabled, and\ninvalid state reach both fields. Errors are announced politely.\n\n```tsx\n<Field label=\"Season dates\">\n <DateRangePicker startName=\"from\" endName=\"to\" onValueChange={setRange} />\n</Field>\n```",
|
|
7113
7322
|
"props": [
|
|
7114
7323
|
{
|
|
7115
7324
|
"name": "value",
|
|
@@ -7127,7 +7336,7 @@
|
|
|
7127
7336
|
"name": "onValueChange",
|
|
7128
7337
|
"type": "(value: DateRange) => void",
|
|
7129
7338
|
"required": false,
|
|
7130
|
-
"description": "Called with the new range when the user completes a pick in the\
|
|
7339
|
+
"description": "Called with the new range when the user completes a pick in the\ncalendar, or types a date in either field and leaves it. Typed text that\nisn't a valid, allowed date clears that end."
|
|
7131
7340
|
},
|
|
7132
7341
|
{
|
|
7133
7342
|
"name": "min",
|
|
@@ -7214,7 +7423,7 @@
|
|
|
7214
7423
|
"name": "invalidMessage",
|
|
7215
7424
|
"type": "ReactNode | ((error: DateRangeInputError) => ReactNode)",
|
|
7216
7425
|
"required": false,
|
|
7217
|
-
"description": "Message shown under the fields when typed text is rejected. Pass a\
|
|
7426
|
+
"description": "Message shown under the fields when typed text is rejected. Pass a\nfunction to word it per reason, or `null` to show none."
|
|
7218
7427
|
},
|
|
7219
7428
|
{
|
|
7220
7429
|
"name": "startLabel",
|
|
@@ -7342,7 +7551,7 @@
|
|
|
7342
7551
|
"category": "forms",
|
|
7343
7552
|
"folder": "DateTimePicker",
|
|
7344
7553
|
"import": "import { DateTimePicker } from \"@vjdesigns/vjdesigns-ui\";",
|
|
7345
|
-
"description": "A date and a time together: a `DatePicker` and a `TimePicker` side by\
|
|
7554
|
+
"description": "A date and a time together: a `DatePicker` and a `TimePicker` side by\nside, with one value, \"YYYY-MM-DDTHH:mm\" (the format of a native\n`datetime-local` input, with no time zone). Each half keeps its full\nbehavior: typed dates in the locale's format or the calendar popup, typed\ntimes in any common form or the list of times.\n\n`min` and `max` limit the calendar by their dates, and the time list by\ntheir times on those boundary dates. `defaultTime` fills the time when a\ndate is picked first.\n\nAccessibility: the pair is a `role=\"group\"` named by the `Field` label\n(or `aria-label`); each field is named by that label plus its own\nvisible label (\"Tip-off, Date\", \"Tip-off, Time\"). Field hint, error,\nrequired, disabled, and invalid state reach both.\n\n```tsx\n<Field label=\"Tip-off\">\n <DateTimePicker name=\"tipoff\" defaultTime=\"19:00\" step={15} />\n</Field>\n```",
|
|
7346
7555
|
"props": [
|
|
7347
7556
|
{
|
|
7348
7557
|
"name": "value",
|
|
@@ -7360,7 +7569,7 @@
|
|
|
7360
7569
|
"name": "onValueChange",
|
|
7361
7570
|
"type": "(value: ISODateTime | null) => void",
|
|
7362
7571
|
"required": false,
|
|
7363
|
-
"description": "Called with \"YYYY-MM-DDTHH:mm\" once both a date and a time are set, and\
|
|
7572
|
+
"description": "Called with \"YYYY-MM-DDTHH:mm\" once both a date and a time are set, and\nwith null when either is cleared or invalid."
|
|
7364
7573
|
},
|
|
7365
7574
|
{
|
|
7366
7575
|
"name": "min",
|
|
@@ -7397,7 +7606,7 @@
|
|
|
7397
7606
|
"name": "defaultTime",
|
|
7398
7607
|
"type": "ISOTime",
|
|
7399
7608
|
"required": false,
|
|
7400
|
-
"description": "Time filled in when a date is picked while the time is empty (e.g.\
|
|
7609
|
+
"description": "Time filled in when a date is picked while the time is empty (e.g.\n\"09:00\"), so one pick gives a complete value. Without it, the value\nstays null until a time is set."
|
|
7401
7610
|
},
|
|
7402
7611
|
{
|
|
7403
7612
|
"name": "locale",
|
|
@@ -7545,7 +7754,7 @@
|
|
|
7545
7754
|
},
|
|
7546
7755
|
{
|
|
7547
7756
|
"name": "Min And Max",
|
|
7548
|
-
"description": "`min` and `max` limit the calendar by date, and the time list on the\
|
|
7757
|
+
"description": "`min` and `max` limit the calendar by date, and the time list on the\nboundary dates only: on Oct 22 the list starts at 6:00 PM; on other days\nit's the whole day."
|
|
7549
7758
|
},
|
|
7550
7759
|
{
|
|
7551
7760
|
"name": "Controlled",
|
|
@@ -9580,7 +9789,7 @@
|
|
|
9580
9789
|
"category": "forms",
|
|
9581
9790
|
"folder": "Rating",
|
|
9582
9791
|
"import": "import { Rating } from \"@vjdesigns/vjdesigns-ui\";",
|
|
9583
|
-
"description": "An editable star (or custom icon) rating, built on real native radios\
|
|
9792
|
+
"description": "An editable star (or custom icon) rating, built on real native radios\n(visually hidden but focusable, sharing one `name`) inside\n`role=\"radiogroup\"`. Tab lands on the selected value (or the first option),\narrow keys move and select, and the value submits with a form. Supports\nhalf values, clearing, hover preview, and Field/Fieldset wiring.",
|
|
9584
9793
|
"nativeElement": "div",
|
|
9585
9794
|
"props": [
|
|
9586
9795
|
{
|
|
@@ -9595,21 +9804,21 @@
|
|
|
9595
9804
|
"type": "Size",
|
|
9596
9805
|
"required": false,
|
|
9597
9806
|
"default": "\"md\"",
|
|
9598
|
-
"description": "Icon size. `Rating`: sm 20px, md 24px, lg 32px icons (every hit target\
|
|
9807
|
+
"description": "Icon size. `Rating`: sm 20px, md 24px, lg 32px icons (every hit target\nis still at least 24x24px). `RatingDisplay`: sm 16px, md 20px, lg 24px.\nFalls back to a surrounding Field's size."
|
|
9599
9808
|
},
|
|
9600
9809
|
{
|
|
9601
9810
|
"name": "tone",
|
|
9602
9811
|
"type": "Tone",
|
|
9603
9812
|
"required": false,
|
|
9604
9813
|
"default": "\"warning\"",
|
|
9605
|
-
"description": "Color of the filled icons. Uses each tone's verified `--color-{tone}-border`\
|
|
9814
|
+
"description": "Color of the filled icons. Uses each tone's verified `--color-{tone}-border`\n(3:1 on bg, surface, and raised surfaces in both themes). Empty icons are\noutlines in `--color-border-strong`, so filled vs empty differs by shape too."
|
|
9606
9815
|
},
|
|
9607
9816
|
{
|
|
9608
9817
|
"name": "icon",
|
|
9609
9818
|
"type": "ReactNode",
|
|
9610
9819
|
"required": false,
|
|
9611
9820
|
"default": "<StarFilledIcon />",
|
|
9612
|
-
"description": "Filled icon (a filled shape, e.g. `<StarFilledIcon />`). It is sized to the\
|
|
9821
|
+
"description": "Filled icon (a filled shape, e.g. `<StarFilledIcon />`). It is sized to the\nicon box. Keep it visibly different in *shape* from `emptyIcon`\n(filled vs outline), not only in color."
|
|
9613
9822
|
},
|
|
9614
9823
|
{
|
|
9615
9824
|
"name": "emptyIcon",
|
|
@@ -9648,20 +9857,20 @@
|
|
|
9648
9857
|
"type": "RatingPrecision",
|
|
9649
9858
|
"required": false,
|
|
9650
9859
|
"default": "1",
|
|
9651
|
-
"description": "Step between values. `0.5` renders two radios per icon (left and right\
|
|
9860
|
+
"description": "Step between values. `0.5` renders two radios per icon (left and right\nhalves). Each half hit area is kept at least 24px wide, so half-precision\nicons sit in 48px-wide cells at every size."
|
|
9652
9861
|
},
|
|
9653
9862
|
{
|
|
9654
9863
|
"name": "allowZero",
|
|
9655
9864
|
"type": "boolean",
|
|
9656
9865
|
"required": false,
|
|
9657
9866
|
"default": "false",
|
|
9658
|
-
"description": "Adds a \"0\" option before the first icon, so zero is a real rating\
|
|
9867
|
+
"description": "Adds a \"0\" option before the first icon, so zero is a real rating\n(\"0 out of 5\"), distinct from no rating (`null`). It is the first radio,\nso ArrowLeft from the lowest icon reaches it."
|
|
9659
9868
|
},
|
|
9660
9869
|
{
|
|
9661
9870
|
"name": "allowClear",
|
|
9662
9871
|
"type": "boolean",
|
|
9663
9872
|
"required": false,
|
|
9664
|
-
"description": "Let the user remove their rating: clicking (or pressing Space on) the\
|
|
9873
|
+
"description": "Let the user remove their rating: clicking (or pressing Space on) the\nselected value again, or pressing Delete / Backspace, resets it to `null`."
|
|
9665
9874
|
},
|
|
9666
9875
|
{
|
|
9667
9876
|
"name": "clearHint",
|
|
@@ -9687,7 +9896,7 @@
|
|
|
9687
9896
|
"name": "readOnly",
|
|
9688
9897
|
"type": "boolean",
|
|
9689
9898
|
"required": false,
|
|
9690
|
-
"description": "Show the value without allowing changes. Renders the `RatingDisplay`\
|
|
9899
|
+
"description": "Show the value without allowing changes. Renders the `RatingDisplay`\nlook (a single `role=\"img\"`, not focusable) plus a hidden input so the\nvalue still submits with a form when `name` is set."
|
|
9691
9900
|
},
|
|
9692
9901
|
{
|
|
9693
9902
|
"name": "invalid",
|
|
@@ -9732,7 +9941,7 @@
|
|
|
9732
9941
|
"name": "label",
|
|
9733
9942
|
"type": "ReactNode",
|
|
9734
9943
|
"required": false,
|
|
9735
|
-
"description": "Visible label, used when the rating stands alone. Ignored inside a\
|
|
9944
|
+
"description": "Visible label, used when the rating stands alone. Ignored inside a\n`Field`/`Fieldset`, whose label or legend names it. Without a label,\npass `aria-label` or `aria-labelledby`."
|
|
9736
9945
|
},
|
|
9737
9946
|
{
|
|
9738
9947
|
"name": "description",
|
|
@@ -9754,7 +9963,7 @@
|
|
|
9754
9963
|
},
|
|
9755
9964
|
{
|
|
9756
9965
|
"name": "Zero to five, in halves",
|
|
9757
|
-
"description": "`allowZero` adds a \"0\" option before the stars, so 0/5 is a real rating\
|
|
9966
|
+
"description": "`allowZero` adds a \"0\" option before the stars, so 0/5 is a real rating\n(not the same as no rating, `null`). With `precision={0.5}`, every value\nfrom 0 to 5 in halves can be picked by pointer or keyboard."
|
|
9758
9967
|
},
|
|
9759
9968
|
{
|
|
9760
9969
|
"name": "Zero (read-only)",
|
|
@@ -9815,7 +10024,7 @@
|
|
|
9815
10024
|
"category": "forms",
|
|
9816
10025
|
"folder": "Rating",
|
|
9817
10026
|
"import": "import { RatingDisplay } from \"@vjdesigns/vjdesigns-ui\";",
|
|
9818
|
-
"description": "A read-only rating: a row of icons with fractional fill, exposed to\
|
|
10027
|
+
"description": "A read-only rating: a row of icons with fractional fill, exposed to\nassistive tech as a single `role=\"img\"` named \"Rated 4.5 out of 5\",\noptionally followed by the value (\"4.5 / 5\") and a review count.\nNot focusable. For an editable rating use `Rating`.",
|
|
9819
10028
|
"nativeElement": "span",
|
|
9820
10029
|
"props": [
|
|
9821
10030
|
{
|
|
@@ -9830,21 +10039,21 @@
|
|
|
9830
10039
|
"type": "Size",
|
|
9831
10040
|
"required": false,
|
|
9832
10041
|
"default": "\"md\"",
|
|
9833
|
-
"description": "Icon size. `Rating`: sm 20px, md 24px, lg 32px icons (every hit target\
|
|
10042
|
+
"description": "Icon size. `Rating`: sm 20px, md 24px, lg 32px icons (every hit target\nis still at least 24x24px). `RatingDisplay`: sm 16px, md 20px, lg 24px.\nFalls back to a surrounding Field's size."
|
|
9834
10043
|
},
|
|
9835
10044
|
{
|
|
9836
10045
|
"name": "tone",
|
|
9837
10046
|
"type": "Tone",
|
|
9838
10047
|
"required": false,
|
|
9839
10048
|
"default": "\"warning\"",
|
|
9840
|
-
"description": "Color of the filled icons. Uses each tone's verified `--color-{tone}-border`\
|
|
10049
|
+
"description": "Color of the filled icons. Uses each tone's verified `--color-{tone}-border`\n(3:1 on bg, surface, and raised surfaces in both themes). Empty icons are\noutlines in `--color-border-strong`, so filled vs empty differs by shape too."
|
|
9841
10050
|
},
|
|
9842
10051
|
{
|
|
9843
10052
|
"name": "icon",
|
|
9844
10053
|
"type": "ReactNode",
|
|
9845
10054
|
"required": false,
|
|
9846
10055
|
"default": "<StarFilledIcon />",
|
|
9847
|
-
"description": "Filled icon (a filled shape, e.g. `<StarFilledIcon />`). It is sized to the\
|
|
10056
|
+
"description": "Filled icon (a filled shape, e.g. `<StarFilledIcon />`). It is sized to the\nicon box. Keep it visibly different in *shape* from `emptyIcon`\n(filled vs outline), not only in color."
|
|
9848
10057
|
},
|
|
9849
10058
|
{
|
|
9850
10059
|
"name": "emptyIcon",
|
|
@@ -9883,7 +10092,7 @@
|
|
|
9883
10092
|
"type": "(value: number, max: number) => string",
|
|
9884
10093
|
"required": false,
|
|
9885
10094
|
"default": "\"Rated 4.5 out of 5\"",
|
|
9886
|
-
"description": "Accessible name of the icon row (`role=\"img\"`). Localize it here.\
|
|
10095
|
+
"description": "Accessible name of the icon row (`role=\"img\"`). Localize it here.\nAn explicit `aria-label` prop wins."
|
|
9887
10096
|
}
|
|
9888
10097
|
],
|
|
9889
10098
|
"subcomponents": [],
|
|
@@ -9899,7 +10108,7 @@
|
|
|
9899
10108
|
},
|
|
9900
10109
|
{
|
|
9901
10110
|
"name": "Zero to five, in halves",
|
|
9902
|
-
"description": "`allowZero` adds a \"0\" option before the stars, so 0/5 is a real rating\
|
|
10111
|
+
"description": "`allowZero` adds a \"0\" option before the stars, so 0/5 is a real rating\n(not the same as no rating, `null`). With `precision={0.5}`, every value\nfrom 0 to 5 in halves can be picked by pointer or keyboard."
|
|
9903
10112
|
},
|
|
9904
10113
|
{
|
|
9905
10114
|
"name": "Zero (read-only)",
|
|
@@ -10989,7 +11198,7 @@
|
|
|
10989
11198
|
"category": "forms",
|
|
10990
11199
|
"folder": "TagInput",
|
|
10991
11200
|
"import": "import { TagInput } from \"@vjdesigns/vjdesigns-ui\";",
|
|
10992
|
-
"description": "A text field that turns entries into removable tags (chips). Type and\
|
|
11201
|
+
"description": "A text field that turns entries into removable tags (chips). Type and\npress Enter (or a delimiter such as a comma) to add; paste \"a, b, c\" to\nadd several at once.\n\nKeyboard:\n- Enter / delimiters (and Tab with `addOnTab`): add the typed text.\n- Backspace in an empty input, or Left Arrow with the caret at the\n start: move focus to the last tag's remove button. A second Backspace\n (or Delete) removes that tag and focus returns to the next tag, or the\n input. This two-step pattern avoids deleting a tag by accident and\n lets screen readers announce which tag is about to be removed.\n- On a tag: Left / Right Arrow move between tags and back to the input;\n Home / End jump to the first tag / the input; Backspace or Delete removes.\n\nInside a `Field`, the label, hint, error, required, disabled, read-only,\nand size are wired automatically. `ref` points at the `<input>`;\nnative input props are spread onto it, while `className` and `style` go\nto the outer wrapper.\n\n```tsx\n<Field label=\"Skills\">\n <TagInput defaultValue={[\"Defense\"]} placeholder=\"Add a skill\" />\n</Field>\n```",
|
|
10993
11202
|
"nativeElement": "input",
|
|
10994
11203
|
"props": [
|
|
10995
11204
|
{
|
|
@@ -11035,14 +11244,14 @@
|
|
|
11035
11244
|
"type": "string[]",
|
|
11036
11245
|
"required": false,
|
|
11037
11246
|
"default": "[\",\"]",
|
|
11038
|
-
"description": "Strings that end a tag as they are typed, in addition to Enter. Pasted\
|
|
11247
|
+
"description": "Strings that end a tag as they are typed, in addition to Enter. Pasted\ntext is also split on them (and on line breaks)."
|
|
11039
11248
|
},
|
|
11040
11249
|
{
|
|
11041
11250
|
"name": "addOnTab",
|
|
11042
11251
|
"type": "boolean",
|
|
11043
11252
|
"required": false,
|
|
11044
11253
|
"default": "false",
|
|
11045
|
-
"description": "Tab adds the typed text as a tag (focus stays in the input). Tab still\
|
|
11254
|
+
"description": "Tab adds the typed text as a tag (focus stays in the input). Tab still\nmoves focus normally when the input is empty or the text is rejected,\nso it never traps the keyboard."
|
|
11046
11255
|
},
|
|
11047
11256
|
{
|
|
11048
11257
|
"name": "addOnBlur",
|
|
@@ -11056,19 +11265,19 @@
|
|
|
11056
11265
|
"type": "boolean",
|
|
11057
11266
|
"required": false,
|
|
11058
11267
|
"default": "false",
|
|
11059
|
-
"description": "Allow the same tag more than once. When `false`, entering an existing\
|
|
11268
|
+
"description": "Allow the same tag more than once. When `false`, entering an existing\ntag highlights it briefly and announces \"{tag} is already added\".\nComparison is exact after `transform` (use `transform` to lowercase for\ncase-insensitive tags)."
|
|
11060
11269
|
},
|
|
11061
11270
|
{
|
|
11062
11271
|
"name": "maxTags",
|
|
11063
11272
|
"type": "number",
|
|
11064
11273
|
"required": false,
|
|
11065
|
-
"description": "Maximum number of tags. When reached, the input becomes read-only, the\
|
|
11274
|
+
"description": "Maximum number of tags. When reached, the input becomes read-only, the\nplaceholder hides, and \"Maximum of N tags reached\" is announced and\nadded to the input's description."
|
|
11066
11275
|
},
|
|
11067
11276
|
{
|
|
11068
11277
|
"name": "validate",
|
|
11069
11278
|
"type": "(tag: string) => boolean | string",
|
|
11070
11279
|
"required": false,
|
|
11071
|
-
"description": "Checks a tag before it is added (after trimming and `transform`).\
|
|
11280
|
+
"description": "Checks a tag before it is added (after trimming and `transform`).\nReturn `true` to accept, `false` to reject with a generic message, or a\nstring to reject with that message. The message is shown under the\nfield, linked with `aria-describedby`, and announced; the rejected text\nstays in the input so it can be fixed."
|
|
11072
11281
|
},
|
|
11073
11282
|
{
|
|
11074
11283
|
"name": "transform",
|
|
@@ -11109,7 +11318,7 @@
|
|
|
11109
11318
|
"type": "boolean",
|
|
11110
11319
|
"required": false,
|
|
11111
11320
|
"default": "false",
|
|
11112
|
-
"description": "Requires at least one tag. Sets `aria-required` on the input and a\
|
|
11321
|
+
"description": "Requires at least one tag. Sets `aria-required` on the input and a\nnative validation message (`messages.required`) while there are no\ntags, so form submission is blocked. Inherits from a surrounding `Field`."
|
|
11113
11322
|
},
|
|
11114
11323
|
{
|
|
11115
11324
|
"name": "placeholder",
|
|
@@ -11121,7 +11330,7 @@
|
|
|
11121
11330
|
"name": "name",
|
|
11122
11331
|
"type": "string",
|
|
11123
11332
|
"required": false,
|
|
11124
|
-
"description": "Form field name. Renders one `<input type=\"hidden\" name={name}>` per\
|
|
11333
|
+
"description": "Form field name. Renders one `<input type=\"hidden\" name={name}>` per\ntag, so `formData.getAll(name)` returns the tags as an array."
|
|
11125
11334
|
},
|
|
11126
11335
|
{
|
|
11127
11336
|
"name": "chipVariant",
|
|
@@ -11148,13 +11357,13 @@
|
|
|
11148
11357
|
"name": "tagListLabel",
|
|
11149
11358
|
"type": "string",
|
|
11150
11359
|
"required": false,
|
|
11151
|
-
"description": "Accessible name of the tag list. Defaults to the surrounding `Field`'s\
|
|
11360
|
+
"description": "Accessible name of the tag list. Defaults to the surrounding `Field`'s\nlabel (or the input's `aria-labelledby`), else \"Tags\"."
|
|
11152
11361
|
},
|
|
11153
11362
|
{
|
|
11154
11363
|
"name": "instructions",
|
|
11155
11364
|
"type": "string",
|
|
11156
11365
|
"required": false,
|
|
11157
|
-
"description": "Visually hidden keyboard instructions, linked to the input with\
|
|
11366
|
+
"description": "Visually hidden keyboard instructions, linked to the input with\n`aria-describedby`. Generated from `delimiters` and `addOnTab` by default."
|
|
11158
11367
|
},
|
|
11159
11368
|
{
|
|
11160
11369
|
"name": "messages",
|
|
@@ -11409,7 +11618,7 @@
|
|
|
11409
11618
|
"category": "forms",
|
|
11410
11619
|
"folder": "TimePicker",
|
|
11411
11620
|
"import": "import { TimePicker } from \"@vjdesigns/vjdesigns-ui\";",
|
|
11412
|
-
"description": "A time field: type a time in almost any form (\"2:30 pm\", \"1430\", \"230p\",\
|
|
11621
|
+
"description": "A time field: type a time in almost any form (\"2:30 pm\", \"1430\", \"230p\",\n\"14h30\", the locale's own format), or pick one from a list of times\n`step` minutes apart. Built on `Combobox`, so the list follows the\nWAI-ARIA combobox pattern: ArrowDown opens it on the selected time,\narrows move, Enter picks, Escape closes, and typing filters it.\n\nThe value is a 24-hour \"HH:mm\" string, the same as a native\n`<input type=\"time\">`, so it never shifts with time zones. The field\nshows it in the locale's clock (12- or 24-hour, override with\n`hourCycle`). On blur (or Enter), typed text is parsed and normalized, or\nrejected with an inline, announced error. Inside a `Field`, label, hint,\nerror, required, disabled, and invalid state are wired to the input.\n\n```tsx\n<Field label=\"Tip-off\">\n <TimePicker name=\"tipoff\" min=\"08:00\" max=\"22:00\" step={15} />\n</Field>\n```",
|
|
11413
11622
|
"props": [
|
|
11414
11623
|
{
|
|
11415
11624
|
"name": "value",
|
|
@@ -11427,7 +11636,7 @@
|
|
|
11427
11636
|
"name": "onValueChange",
|
|
11428
11637
|
"type": "(value: ISOTime | null) => void",
|
|
11429
11638
|
"required": false,
|
|
11430
|
-
"description": "Called with the new time (\"HH:mm\") or null when the user picks one from\
|
|
11639
|
+
"description": "Called with the new time (\"HH:mm\") or null when the user picks one from\nthe list, types one and leaves the field, or clears it. Typed text that\nisn't a valid, allowed time sets the value to null."
|
|
11431
11640
|
},
|
|
11432
11641
|
{
|
|
11433
11642
|
"name": "min",
|
|
@@ -11535,13 +11744,13 @@
|
|
|
11535
11744
|
"name": "onInvalidInput",
|
|
11536
11745
|
"type": "(error: TimeInputError) => void",
|
|
11537
11746
|
"required": false,
|
|
11538
|
-
"description": "Called when the user leaves the field with text that isn't a valid,\
|
|
11747
|
+
"description": "Called when the user leaves the field with text that isn't a valid,\nallowed time. Use it to set your own `Field` error."
|
|
11539
11748
|
},
|
|
11540
11749
|
{
|
|
11541
11750
|
"name": "invalidMessage",
|
|
11542
11751
|
"type": "ReactNode | ((error: TimeInputError) => ReactNode)",
|
|
11543
11752
|
"required": false,
|
|
11544
|
-
"description": "Message shown under the field when typed text is rejected. Pass a\
|
|
11753
|
+
"description": "Message shown under the field when typed text is rejected. Pass a\nfunction to word it per reason, or `null` to show none (when you show a\n`Field` error from `onInvalidInput` instead)."
|
|
11545
11754
|
},
|
|
11546
11755
|
{
|
|
11547
11756
|
"name": "formatDescription",
|
|
@@ -11622,7 +11831,7 @@
|
|
|
11622
11831
|
},
|
|
11623
11832
|
{
|
|
11624
11833
|
"name": "Validation",
|
|
11625
|
-
"description": "Text it can't read, or a time outside `min` / `max`, is flagged inline\
|
|
11834
|
+
"description": "Text it can't read, or a time outside `min` / `max`, is flagged inline\nand announced politely; the value becomes null. `onInvalidInput` lets you\nshow your own `Field` error instead (with `invalidMessage={null}`)."
|
|
11626
11835
|
},
|
|
11627
11836
|
{
|
|
11628
11837
|
"name": "Typed Formats",
|
|
@@ -13146,7 +13355,7 @@
|
|
|
13146
13355
|
"stories": [
|
|
13147
13356
|
{
|
|
13148
13357
|
"name": "Playground",
|
|
13149
|
-
"description": "A collapsible playbook beside the court. Keyboard: Tab to the handle;\
|
|
13358
|
+
"description": "A collapsible playbook beside the court. Keyboard: Tab to the handle;\n←/→ resize, Home/End jump to the limits, Enter collapses and restores."
|
|
13150
13359
|
},
|
|
13151
13360
|
{
|
|
13152
13361
|
"name": "Pointer Resize",
|
|
@@ -13175,7 +13384,7 @@
|
|
|
13175
13384
|
"category": "layout",
|
|
13176
13385
|
"folder": "ScrollArea",
|
|
13177
13386
|
"import": "import { ScrollArea } from \"@vjdesigns/vjdesigns-ui\";",
|
|
13178
|
-
"description": "A scrolling region with slim, on-brand scrollbars that work the same in\
|
|
13387
|
+
"description": "A scrolling region with slim, on-brand scrollbars that work the same in\nevery browser. Scrolling itself stays native (wheel, touch, trackpad\nmomentum, keyboard, find-in-page, scroll anchoring); only the look of the\nbars is custom. The thumbs can be dragged, and pressing the track pages\ntoward the press.\n\nAccessibility: while the content overflows, the viewport is focusable\n(`tabIndex=0`) so keyboard users can scroll it with the arrow keys, Page\nUp / Page Down, and Home / End. Give it a name with `aria-label` or\n`aria-labelledby`, which also makes it a `region` landmark. The custom\nbars are hidden from assistive technology (they duplicate native\nscrolling). Edge shadows show there is more to see. In Windows High\nContrast mode the native scrollbars come back.\n\n```tsx\n<ScrollArea maxHeight=\"20rem\" aria-label=\"Roster\">\n <List>…</List>\n</ScrollArea>\n```",
|
|
13179
13388
|
"nativeElement": "div",
|
|
13180
13389
|
"props": [
|
|
13181
13390
|
{
|
|
@@ -13216,7 +13425,7 @@
|
|
|
13216
13425
|
"type": "boolean",
|
|
13217
13426
|
"required": false,
|
|
13218
13427
|
"default": "true",
|
|
13219
|
-
"description": "Fade the edges where more content is hidden, so it's clear the area\
|
|
13428
|
+
"description": "Fade the edges where more content is hidden, so it's clear the area\nscrolls even before the scrollbar is noticed."
|
|
13220
13429
|
},
|
|
13221
13430
|
{
|
|
13222
13431
|
"name": "hideDelay",
|
|
@@ -13229,7 +13438,7 @@
|
|
|
13229
13438
|
"name": "viewportRef",
|
|
13230
13439
|
"type": "Ref<HTMLDivElement>",
|
|
13231
13440
|
"required": false,
|
|
13232
|
-
"description": "Ref to the scrolling viewport element (to read or set `scrollTop`, or\
|
|
13441
|
+
"description": "Ref to the scrolling viewport element (to read or set `scrollTop`, or\ncall `scrollTo`). `ref` goes to the outer root."
|
|
13233
13442
|
},
|
|
13234
13443
|
{
|
|
13235
13444
|
"name": "viewportClassName",
|
|
@@ -13273,7 +13482,7 @@
|
|
|
13273
13482
|
},
|
|
13274
13483
|
{
|
|
13275
13484
|
"name": "All Types",
|
|
13276
|
-
"description": "When the bars show, side by side. `auto` (default) shows them whenever\
|
|
13485
|
+
"description": "When the bars show, side by side. `auto` (default) shows them whenever\ncontent overflows; `hover` while the pointer is over the area, it's\nscrolling, or focus is inside; `scroll` only while scrolling; `always`\nkeeps the track even when nothing overflows."
|
|
13277
13486
|
},
|
|
13278
13487
|
{
|
|
13279
13488
|
"name": "Sizes",
|
|
@@ -13678,7 +13887,7 @@
|
|
|
13678
13887
|
"category": "navigation",
|
|
13679
13888
|
"folder": "ContextMenu",
|
|
13680
13889
|
"import": "import { ContextMenu } from \"@vjdesigns/vjdesigns-ui\";",
|
|
13681
|
-
"description": "A menu that opens at the pointer on right-click (or a long press on touch\
|
|
13890
|
+
"description": "A menu that opens at the pointer on right-click (or a long press on touch\nscreens), for actions on the thing under the pointer. It is a `Menu` in\nevery other way: compose it from the same items (`ContextMenu.Item`,\n`.CheckboxItem`, `.RadioGroup` / `.RadioItem`, `.Group` / `.Label`,\n`.Separator`, and submenus), with the same keyboard model (arrows, Home /\nEnd, typeahead, Escape, Tab) and RTL support.\n\nKeyboard: with focus inside the trigger area, Shift+F10 or the Menu key\nopens the menu next to the focused element and focuses its first item.\nOn close, focus goes back to where it was.\n\nRight-click menus are hard to discover, and many touch screens and\nassistive technologies never open them, so offer the same actions\nsomewhere visible too (a `Menu` button, a toolbar). Treat the context\nmenu as a shortcut, never the only way.\n\n```tsx\n<ContextMenu>\n <ContextMenu.Trigger>\n <Card tabIndex={0}>Game film</Card>\n </ContextMenu.Trigger>\n <ContextMenu.Content>\n <ContextMenu.Item onSelect={rename}>Rename</ContextMenu.Item>\n <ContextMenu.Item tone=\"danger\" onSelect={remove}>Delete</ContextMenu.Item>\n </ContextMenu.Content>\n</ContextMenu>\n```",
|
|
13682
13891
|
"props": [
|
|
13683
13892
|
{
|
|
13684
13893
|
"name": "children",
|
|
@@ -13716,7 +13925,7 @@
|
|
|
13716
13925
|
"name": "closeOnSelect",
|
|
13717
13926
|
"type": "boolean",
|
|
13718
13927
|
"required": false,
|
|
13719
|
-
"description": "Default for every item's `closeOnSelect`. When unset, plain items close\
|
|
13928
|
+
"description": "Default for every item's `closeOnSelect`. When unset, plain items close\nthe menu on selection; checkbox and radio items keep it open."
|
|
13720
13929
|
},
|
|
13721
13930
|
{
|
|
13722
13931
|
"name": "disabled",
|
|
@@ -13737,7 +13946,7 @@
|
|
|
13737
13946
|
{
|
|
13738
13947
|
"name": "ContextMenu.Trigger",
|
|
13739
13948
|
"exportedAs": "ContextMenuTrigger",
|
|
13740
|
-
"description": "The area that opens the menu: right-click, a touch long press, or\
|
|
13949
|
+
"description": "The area that opens the menu: right-click, a touch long press, or\nShift+F10 / the Menu key while focus is inside it. Make the area (or the\nitems in it) focusable so keyboard users can reach the menu.",
|
|
13741
13950
|
"nativeElement": "div",
|
|
13742
13951
|
"props": [
|
|
13743
13952
|
{
|
|
@@ -13751,14 +13960,14 @@
|
|
|
13751
13960
|
"type": "boolean",
|
|
13752
13961
|
"required": false,
|
|
13753
13962
|
"default": "false",
|
|
13754
|
-
"description": "Clone the single child instead of wrapping it in a `div`. The child must\
|
|
13963
|
+
"description": "Clone the single child instead of wrapping it in a `div`. The child must\naccept `ref`, `className`, and the pointer, context menu, and key handlers."
|
|
13755
13964
|
}
|
|
13756
13965
|
]
|
|
13757
13966
|
},
|
|
13758
13967
|
{
|
|
13759
13968
|
"name": "ContextMenu.Content",
|
|
13760
13969
|
"exportedAs": "ContextMenuContent",
|
|
13761
|
-
"description": "The floating menu (`role=\"menu\"`), opened at the pointer. Takes the same\
|
|
13970
|
+
"description": "The floating menu (`role=\"menu\"`), opened at the pointer. Takes the same\nprops as `Menu.Content`; `placement` is relative to the pointer, and\nflips and clamps to stay on screen. @see MenuContent",
|
|
13762
13971
|
"nativeElement": "div",
|
|
13763
13972
|
"props": [
|
|
13764
13973
|
{
|
|
@@ -13799,7 +14008,7 @@
|
|
|
13799
14008
|
"name": "aria-label",
|
|
13800
14009
|
"type": "string",
|
|
13801
14010
|
"required": false,
|
|
13802
|
-
"description": "Accessible name of the menu. Defaults to \"Context menu\" (translated);\
|
|
14011
|
+
"description": "Accessible name of the menu. Defaults to \"Context menu\" (translated);\nname it after what it acts on when you can (\"Game film actions\")."
|
|
13803
14012
|
}
|
|
13804
14013
|
]
|
|
13805
14014
|
},
|
|
@@ -14219,11 +14428,11 @@
|
|
|
14219
14428
|
"stories": [
|
|
14220
14429
|
{
|
|
14221
14430
|
"name": "Playground",
|
|
14222
|
-
"description": "Right-click the area (or long-press it on a touch screen). With the area\
|
|
14431
|
+
"description": "Right-click the area (or long-press it on a touch screen). With the area\nfocused, Shift+F10 or the Menu key opens the menu on the first item."
|
|
14223
14432
|
},
|
|
14224
14433
|
{
|
|
14225
14434
|
"name": "Keyboard",
|
|
14226
|
-
"description": "Keyboard: Shift+F10 (or the Menu key) opens the menu next to the focused\
|
|
14435
|
+
"description": "Keyboard: Shift+F10 (or the Menu key) opens the menu next to the focused\nelement and focuses the first item. Escape closes it and focus goes back."
|
|
14227
14436
|
},
|
|
14228
14437
|
{
|
|
14229
14438
|
"name": "Long Press",
|
|
@@ -14407,7 +14616,7 @@
|
|
|
14407
14616
|
"category": "navigation",
|
|
14408
14617
|
"folder": "Menu",
|
|
14409
14618
|
"import": "import { Menu } from \"@vjdesigns/vjdesigns-ui\";",
|
|
14410
|
-
"description": "A dropdown menu of actions or options, opened from a button (WAI-ARIA APG\
|
|
14619
|
+
"description": "A dropdown menu of actions or options, opened from a button (WAI-ARIA APG\n\"Menu Button\" and \"Menu\" patterns). Compose it from `Menu.Trigger`,\n`Menu.Content`, `Menu.Item`, `Menu.CheckboxItem`, `Menu.RadioGroup` /\n`Menu.RadioItem`, `Menu.Group` / `Menu.Label`, `Menu.Separator`, and\nsubmenus (`Menu.Sub`, `Menu.SubTrigger`, `Menu.SubContent`).\n\nKeyboard: Enter, Space, or ArrowDown on the trigger opens the menu and\nfocuses the first item (ArrowUp: the last). Inside, ArrowUp/ArrowDown move\n(wrapping), Home/End jump, typing a character jumps to the next matching\nitem, Enter/Space activates, ArrowRight opens a submenu and ArrowLeft\ncloses it (mirrored in RTL), Escape closes the current level and returns\nfocus to its trigger, and Tab closes the whole menu.\n\nDisabled items stay focusable (`aria-disabled=\"true\"`) so keyboard and\nscreen reader users can still discover them, but can't be activated.\n\n```tsx\n<Menu>\n <Menu.Trigger>Actions</Menu.Trigger>\n <Menu.Content>\n <Menu.Item onSelect={edit}>Edit</Menu.Item>\n <Menu.Item tone=\"danger\" onSelect={remove}>Delete</Menu.Item>\n </Menu.Content>\n</Menu>\n```",
|
|
14411
14620
|
"props": [
|
|
14412
14621
|
{
|
|
14413
14622
|
"name": "children",
|
|
@@ -14445,14 +14654,14 @@
|
|
|
14445
14654
|
"name": "closeOnSelect",
|
|
14446
14655
|
"type": "boolean",
|
|
14447
14656
|
"required": false,
|
|
14448
|
-
"description": "Default for every item's `closeOnSelect`. When unset, plain items\
|
|
14657
|
+
"description": "Default for every item's `closeOnSelect`. When unset, plain items\n(`Menu.Item`) close the menu on selection, while checkbox and radio\nitems keep it open so several can be toggled. An item's own\n`closeOnSelect` always wins."
|
|
14449
14658
|
}
|
|
14450
14659
|
],
|
|
14451
14660
|
"subcomponents": [
|
|
14452
14661
|
{
|
|
14453
14662
|
"name": "Menu.Trigger",
|
|
14454
14663
|
"exportedAs": "MenuTrigger",
|
|
14455
|
-
"description": "The button that opens the menu. Adds `aria-haspopup=\"menu\"`,\
|
|
14664
|
+
"description": "The button that opens the menu. Adds `aria-haspopup=\"menu\"`,\n`aria-expanded`, and `aria-controls`. Pass a single `Button`/`IconButton`\nto use it as-is, or plain content to get a default `Button`.",
|
|
14456
14665
|
"nativeElement": "button",
|
|
14457
14666
|
"props": [
|
|
14458
14667
|
{
|
|
@@ -14546,7 +14755,7 @@
|
|
|
14546
14755
|
"name": "asChild",
|
|
14547
14756
|
"type": "boolean",
|
|
14548
14757
|
"required": false,
|
|
14549
|
-
"description": "Clone the single child instead of wrapping it in a `Button`. Defaults to\
|
|
14758
|
+
"description": "Clone the single child instead of wrapping it in a `Button`. Defaults to\n`true` when `children` is a single element. The child must be a\nfocusable button that accepts `ref`, `id`, `onClick`, `onKeyDown`, and `aria-*`."
|
|
14550
14759
|
}
|
|
14551
14760
|
]
|
|
14552
14761
|
},
|
|
@@ -17514,7 +17723,7 @@
|
|
|
17514
17723
|
"category": "overlays",
|
|
17515
17724
|
"folder": "HoverCard",
|
|
17516
17725
|
"import": "import { HoverCard } from \"@vjdesigns/vjdesigns-ui\";",
|
|
17517
|
-
"description": "A preview card that opens while the pointer rests on a link or button, or\
|
|
17726
|
+
"description": "A preview card that opens while the pointer rests on a link or button, or\nwhile it has keyboard focus: a player's profile behind their name, a page\npreview behind a link. Unlike `Tooltip`, the card can hold rich and\ninteractive content (links, buttons).\n\nMeets WCAG 1.4.13: the pointer can move onto the card, it stays open\nwhile hovered or focused, and Escape closes it. Keyboard users get it\ntoo: focusing the trigger opens it, Tab moves into the card's links, and\nTab past the last one continues to whatever follows the trigger.\n\nTouch screens have no hover, so the card never opens from a tap (the tap\nfollows the link). Treat it as a preview: everything in it must also be\nreachable from the page the trigger leads to.\n\n```tsx\n<HoverCard>\n <HoverCard.Trigger>\n <Link href=\"/players/23\">Jordan Reyes</Link>\n </HoverCard.Trigger>\n <HoverCard.Content>…profile…</HoverCard.Content>\n</HoverCard>\n```",
|
|
17518
17727
|
"props": [
|
|
17519
17728
|
{
|
|
17520
17729
|
"name": "children",
|
|
@@ -17553,7 +17762,7 @@
|
|
|
17553
17762
|
"type": "number",
|
|
17554
17763
|
"required": false,
|
|
17555
17764
|
"default": "300",
|
|
17556
|
-
"description": "Delay before closing after the pointer leaves the trigger or the card,\
|
|
17765
|
+
"description": "Delay before closing after the pointer leaves the trigger or the card,\nin ms. Gives the pointer time to cross the gap onto the card."
|
|
17557
17766
|
}
|
|
17558
17767
|
],
|
|
17559
17768
|
"subcomponents": [
|
|
@@ -17566,14 +17775,14 @@
|
|
|
17566
17775
|
"name": "children",
|
|
17567
17776
|
"type": "ReactElement",
|
|
17568
17777
|
"required": true,
|
|
17569
|
-
"description": "Exactly one focusable element, usually a `Link` (or a button) that\
|
|
17778
|
+
"description": "Exactly one focusable element, usually a `Link` (or a button) that\naccepts `ref`, event handlers, and `data-*` props. Its own handlers and\nref are kept."
|
|
17570
17779
|
}
|
|
17571
17780
|
]
|
|
17572
17781
|
},
|
|
17573
17782
|
{
|
|
17574
17783
|
"name": "HoverCard.Content",
|
|
17575
17784
|
"exportedAs": "HoverCardContent",
|
|
17576
|
-
"description": "The floating card. Rendered in a portal, positioned against the trigger\
|
|
17785
|
+
"description": "The floating card. Rendered in a portal, positioned against the trigger\nwith flip and viewport clamping, and mounted only while open.",
|
|
17577
17786
|
"nativeElement": "div",
|
|
17578
17787
|
"props": [
|
|
17579
17788
|
{
|
|
@@ -17618,11 +17827,11 @@
|
|
|
17618
17827
|
"stories": [
|
|
17619
17828
|
{
|
|
17620
17829
|
"name": "Playground",
|
|
17621
|
-
"description": "Rest the pointer on the name. The card opens after `openDelay`, stays\
|
|
17830
|
+
"description": "Rest the pointer on the name. The card opens after `openDelay`, stays\nopen while the pointer is on the link or the card, and closes\n`closeDelay` after it leaves both."
|
|
17622
17831
|
},
|
|
17623
17832
|
{
|
|
17624
17833
|
"name": "Keyboard",
|
|
17625
|
-
"description": "Keyboard: Tab to the link opens the card. Tab again moves into its links;\
|
|
17834
|
+
"description": "Keyboard: Tab to the link opens the card. Tab again moves into its links;\nTab past the last one continues to what follows the trigger and closes\nthe card. Escape closes it and returns focus to the link."
|
|
17626
17835
|
},
|
|
17627
17836
|
{
|
|
17628
17837
|
"name": "Click Closes",
|
|
@@ -18786,6 +18995,10 @@
|
|
|
18786
18995
|
"name": "Disabled",
|
|
18787
18996
|
"description": ""
|
|
18788
18997
|
},
|
|
18998
|
+
{
|
|
18999
|
+
"name": "Disabled but focusable",
|
|
19000
|
+
"description": "`aria-disabled=\"true\"` instead of `disabled`: the button looks and acts\r\nunavailable (clicks are ignored) but stays focusable, so a tooltip or a\r\ncountdown (\"Resend code in 0:24\") can still be reached and read."
|
|
19001
|
+
},
|
|
18789
19002
|
{
|
|
18790
19003
|
"name": "As Router Link",
|
|
18791
19004
|
"description": "`as` renders your router's link with the button's styling, and its props\r\n(here `to`) are typed. While disabled or loading it becomes a plain\r\n`<a aria-disabled=\"true\">`, since a router link can't render without a\r\ndestination."
|
|
@@ -19718,7 +19931,7 @@
|
|
|
19718
19931
|
"category": "utilities",
|
|
19719
19932
|
"folder": "Theme",
|
|
19720
19933
|
"import": "import { Theme } from \"@vjdesigns/vjdesigns-ui\";",
|
|
19721
|
-
"description": "Applies a brand palette, accent, and/or color mode to everything inside\r\nit, through `data-brand`, `data-accent`, and `data-theme` (and, for hex\r\ncolors, the generated custom properties inline). Wrap the whole app, or\r\nany part of it; themes nest (a blue area inside a purple app, a dark panel\r\non a light page). Overlays opened inside (Popover, Menu, Modal, Tooltip,\r\nToast, ...) render elsewhere in the page but keep this theme. For the whole\r\npage you can instead set the attributes on `<html>`:\r\n`<html data-theme=\"dark\" data-brand=\"purple\" data-accent=\"
|
|
19934
|
+
"description": "Applies a brand palette, accent, and/or color mode to everything inside\r\nit, through `data-brand`, `data-accent`, and `data-theme` (and, for hex\r\ncolors, the generated custom properties inline). Wrap the whole app, or\r\nany part of it; themes nest (a blue area inside a purple app, a dark panel\r\non a light page). Overlays opened inside (Popover, Menu, Modal, Tooltip,\r\nToast, ...) render elsewhere in the page but keep this theme. For the whole\r\npage you can instead set the attributes on `<html>`:\r\n`<html data-theme=\"dark\" data-brand=\"purple\" data-accent=\"lime\">`.",
|
|
19722
19935
|
"nativeElement": "div",
|
|
19723
19936
|
"props": [
|
|
19724
19937
|
{
|
|
@@ -19731,7 +19944,7 @@
|
|
|
19731
19944
|
"name": "accent",
|
|
19732
19945
|
"type": "ThemeAccent | BrandColor",
|
|
19733
19946
|
"required": false,
|
|
19734
|
-
"description": "The accent: the energetic highlight on badges, switches, progress bars,\r\nsliders, steppers, and active navigation. Leave unset to inherit; with no\r\naccent set anywhere, each brand uses its own (a bright shade of its hue).\r\n\r\n`\"
|
|
19947
|
+
"description": "The accent: the energetic highlight on badges, switches, progress bars,\r\nsliders, steppers, and active navigation. Leave unset to inherit; with no\r\naccent set anywhere, each brand uses its own (a bright shade of its hue).\r\n\r\n`\"lime\"` for the original bright yellow-green, `\"brand\"` to go back to the\r\nbrand's own inside an area with another accent, or any hex color for a\r\ncustom accent. Accents always carry dark text, so a color too dark for\r\nthat is lightened (same hue), with a console warning in development. An\r\naccent set here also holds inside nested brands."
|
|
19735
19948
|
},
|
|
19736
19949
|
{
|
|
19737
19950
|
"name": "mode",
|
|
@@ -19784,7 +19997,7 @@
|
|
|
19784
19997
|
},
|
|
19785
19998
|
{
|
|
19786
19999
|
"name": "Accents",
|
|
19787
|
-
"description": "The accent (badges, switches, progress, sliders, active navigation) follows\r\nthe brand by default: a bright shade of its hue, always with dark text.\r\n`accent=\"
|
|
20000
|
+
"description": "The accent (badges, switches, progress, sliders, active navigation) follows\r\nthe brand by default: a bright shade of its hue, always with dark text.\r\n`accent=\"lime\"` restores the original bright yellow-green, and any hex makes a\r\ncustom accent (kept accessible: too-dark colors are lightened)."
|
|
19788
20001
|
},
|
|
19789
20002
|
{
|
|
19790
20003
|
"name": "Accent nesting",
|
|
@@ -19955,55 +20168,55 @@
|
|
|
19955
20168
|
{
|
|
19956
20169
|
"name": "useDisclosure",
|
|
19957
20170
|
"signature": "useDisclosure(initial = false, options: UseDisclosureOptions = {}): [opened: boolean, handlers: DisclosureHandlers]",
|
|
19958
|
-
"description": "Open/closed state for anything that shows and hides (a Modal, Drawer,\
|
|
20171
|
+
"description": "Open/closed state for anything that shows and hides (a Modal, Drawer,\nPopover, a collapsible section), with stable handlers.\n\n```tsx\nconst [opened, { open, close }] = useDisclosure();\n<Button onClick={open}>Edit</Button>\n<Modal open={opened} onOpenChange={(next) => (next ? open() : close())}>…</Modal>\n```",
|
|
19959
20172
|
"module": "hooks/index.ts"
|
|
19960
20173
|
},
|
|
19961
20174
|
{
|
|
19962
20175
|
"name": "useToggle",
|
|
19963
20176
|
"signature": "useToggle(values: readonly T[] = [false, true] as unknown as readonly T[]): [value: T, toggle: (value?: T) => void]",
|
|
19964
|
-
"description": "Cycles through values: `[false, true]` by default (a boolean toggle), or\
|
|
20177
|
+
"description": "Cycles through values: `[false, true]` by default (a boolean toggle), or\nany list (`[\"list\", \"grid\"]`). `toggle()` moves to the next value (wrapping);\n`toggle(value)` jumps to one.\n\n```tsx\nconst [view, toggleView] = useToggle([\"list\", \"grid\"] as const);\n<Button onClick={() => toggleView()}>Show {view === \"list\" ? \"grid\" : \"list\"}</Button>\n```",
|
|
19965
20178
|
"module": "hooks/index.ts"
|
|
19966
20179
|
},
|
|
19967
20180
|
{
|
|
19968
20181
|
"name": "useDebouncedCallback",
|
|
19969
20182
|
"signature": "useDebouncedCallback(callback: (...args: A) => void, wait: number, { leading = false, maxWait }: { leading?: boolean; maxWait?: number } = {}): ControlledCallback<A>",
|
|
19970
|
-
"description": "A function that waits until calls stop for `wait` ms, then runs once with\
|
|
20183
|
+
"description": "A function that waits until calls stop for `wait` ms, then runs once with\nthe last arguments. Always calls the latest `callback`; a pending call is\ndropped on unmount. Use it to save, search, or validate after typing pauses.\n\n```tsx\nconst save = useDebouncedCallback((text: string) => api.saveDraft(text), 500);\n<Textarea onChange={(e) => save(e.target.value)} />\n```",
|
|
19971
20184
|
"module": "hooks/index.ts"
|
|
19972
20185
|
},
|
|
19973
20186
|
{
|
|
19974
20187
|
"name": "useThrottledCallback",
|
|
19975
20188
|
"signature": "useThrottledCallback(callback: (...args: A) => void, wait: number): ControlledCallback<A>",
|
|
19976
|
-
"description": "A function that runs at most once every `wait` ms: the first call runs\
|
|
20189
|
+
"description": "A function that runs at most once every `wait` ms: the first call runs\nright away, and the last call made during the wait runs when it ends.\nUse it for scroll, resize, or pointer-move handlers.\n\n```tsx\nconst track = useThrottledCallback((x: number) => setPosition(x), 100);\n```",
|
|
19977
20190
|
"module": "hooks/index.ts"
|
|
19978
20191
|
},
|
|
19979
20192
|
{
|
|
19980
20193
|
"name": "useDebouncedValue",
|
|
19981
20194
|
"signature": "useDebouncedValue(value: T, wait: number): [debounced: T, controls: { cancel: () => void; flush: () => void }]",
|
|
19982
|
-
"description": "The value, updated only after it stops changing for `wait` ms. Use it to\
|
|
20195
|
+
"description": "The value, updated only after it stops changing for `wait` ms. Use it to\nsearch or fetch as someone types, without a request per keystroke.\n\n```tsx\nconst [query, setQuery] = useState(\"\");\nconst [debouncedQuery] = useDebouncedValue(query, 300);\nuseEffect(() => { search(debouncedQuery); }, [debouncedQuery]);\n```",
|
|
19983
20196
|
"module": "hooks/index.ts"
|
|
19984
20197
|
},
|
|
19985
20198
|
{
|
|
19986
20199
|
"name": "useInterval",
|
|
19987
20200
|
"signature": "useInterval(callback: () => void, delay: number | null): void",
|
|
19988
|
-
"description": "Calls `callback` every `delay` ms while `delay` is a number; pass `null`\
|
|
20201
|
+
"description": "Calls `callback` every `delay` ms while `delay` is a number; pass `null`\nto pause. Always calls the latest `callback`, and stops on unmount.\n\n```tsx\nconst [running, setRunning] = useState(true);\nuseInterval(() => setSeconds((s) => s + 1), running ? 1000 : null);\n```",
|
|
19989
20202
|
"module": "hooks/index.ts"
|
|
19990
20203
|
},
|
|
19991
20204
|
{
|
|
19992
20205
|
"name": "useTimeout",
|
|
19993
20206
|
"signature": "useTimeout(callback: () => void, delay: number | null): { clear: () => void; reset: () => void }",
|
|
19994
|
-
"description": "Calls `callback` once, `delay` ms after mount (or after `delay` changes);\
|
|
20207
|
+
"description": "Calls `callback` once, `delay` ms after mount (or after `delay` changes);\npass `null` to not schedule it. Returns `clear` and `reset` (restart the\nwait).\n\n```tsx\nconst { reset } = useTimeout(() => setSaved(false), 2000);\n```",
|
|
19995
20208
|
"module": "hooks/index.ts"
|
|
19996
20209
|
},
|
|
19997
20210
|
{
|
|
19998
20211
|
"name": "usePrevious",
|
|
19999
20212
|
"signature": "usePrevious(value: T): T | undefined",
|
|
20000
|
-
"description": "The value from before its last change (undefined until it first changes).\
|
|
20213
|
+
"description": "The value from before its last change (undefined until it first changes).\n\n```tsx\nconst previousCount = usePrevious(count);\n```",
|
|
20001
20214
|
"module": "hooks/index.ts"
|
|
20002
20215
|
},
|
|
20003
20216
|
{
|
|
20004
20217
|
"name": "useLocalStorage",
|
|
20005
20218
|
"signature": "useLocalStorage(key: string, defaultValue: T, options?: UseStorageOptions<T>)",
|
|
20006
|
-
"description": "State saved in `localStorage` under `key`: it survives reloads and stays in\
|
|
20219
|
+
"description": "State saved in `localStorage` under `key`: it survives reloads and stays in\nsync across every component using the key and across browser tabs. Values\nare JSON by default. Returns `defaultValue` during server rendering, when\nnothing is stored, or when storage is blocked (it never throws).\n\n```tsx\nconst [density, setDensity] = useLocalStorage(\"table-density\", \"comfortable\");\n```",
|
|
20007
20220
|
"module": "hooks/index.ts"
|
|
20008
20221
|
},
|
|
20009
20222
|
{
|
|
@@ -20015,79 +20228,79 @@
|
|
|
20015
20228
|
{
|
|
20016
20229
|
"name": "useClipboard",
|
|
20017
20230
|
"signature": "useClipboard({ timeout = 2000 }: { timeout?: number } = {}): UseClipboardResult",
|
|
20018
|
-
"description": "Copies text to the clipboard, with a `copied` flag that resets after\
|
|
20231
|
+
"description": "Copies text to the clipboard, with a `copied` flag that resets after\n`timeout` ms. Uses the Clipboard API, with a fallback for browsers or\ncontexts that block it. Pair a visible \"Copied\" change with an\nannouncement (`announce`) so screen readers hear it too.\n\n```tsx\nconst { copy, copied } = useClipboard();\n<Button onClick={() => copy(inviteLink)}>{copied ? \"Copied\" : \"Copy link\"}</Button>\n```",
|
|
20019
20232
|
"module": "hooks/index.ts"
|
|
20020
20233
|
},
|
|
20021
20234
|
{
|
|
20022
20235
|
"name": "useHotkeys",
|
|
20023
20236
|
"signature": "useHotkeys(hotkeys: HotkeyItem[], { enabled = true, target }: UseHotkeysOptions = {}): void",
|
|
20024
|
-
"description": "Keyboard shortcuts for the whole page (or one element). `mod` is ⌘ on Mac\
|
|
20237
|
+
"description": "Keyboard shortcuts for the whole page (or one element). `mod` is ⌘ on Mac\nand Ctrl elsewhere; separate alternatives with commas (`\"mod+k, /\"`).\nShortcuts with ⌘, Ctrl, or Alt work everywhere; plain keys don't fire while\ntyping in a text field. Show each shortcut to users (`Kbd`,\n`aria-keyshortcuts`) so it can be found, and avoid single-key shortcuts that\nscreen readers or browsers use, or let users turn them off (WCAG 2.1.4).\n\n```tsx\nuseHotkeys([\n [\"mod+k\", () => setPaletteOpen(true)],\n [\"/\", () => searchRef.current?.focus()],\n [\"shift+?\", () => setHelpOpen(true)],\n]);\n```",
|
|
20025
20238
|
"module": "hooks/index.ts"
|
|
20026
20239
|
},
|
|
20027
20240
|
{
|
|
20028
20241
|
"name": "useEventListener",
|
|
20029
20242
|
"signature": "useEventListener(type: K, handler: (event: WindowEventMap[K]) => void, target?: ListenerTarget | RefObject<HTMLElement | null>, options?: boolean | AddEventListenerOptions): void",
|
|
20030
|
-
"description": "Adds an event listener and removes it on unmount. Always calls the latest\
|
|
20243
|
+
"description": "Adds an event listener and removes it on unmount. Always calls the latest\n`handler` (no re-subscribing when it changes). `target` defaults to\n`window`; pass an element, `document`, or a ref.\n\n```tsx\nuseEventListener(\"beforeunload\", (e) => { if (dirty) e.preventDefault(); });\nuseEventListener(\"scroll\", onScroll, panelRef, { passive: true });\n```",
|
|
20031
20244
|
"module": "hooks/index.ts"
|
|
20032
20245
|
},
|
|
20033
20246
|
{
|
|
20034
20247
|
"name": "useClickOutside",
|
|
20035
20248
|
"signature": "useClickOutside(handler: (event: Event) => void, { enabled = true, ignore = [] }: { enabled?: boolean; ignore?: Array<RefObject<HTMLElement | null> | HTMLElement | null> } = {}): RefObject<T | null>",
|
|
20036
|
-
"description": "Calls `handler` when a pointer press or a focus move lands outside the\
|
|
20249
|
+
"description": "Calls `handler` when a pointer press or a focus move lands outside the\nreturned ref's element (and outside any `ignore` elements). Use it for\ncustom popups; the library's own overlays already close on outside\npresses, through the shared layer stack.\n\n```tsx\nconst ref = useClickOutside<HTMLDivElement>(() => setOpen(false));\n<div ref={ref}>…</div>\n```",
|
|
20037
20250
|
"module": "hooks/index.ts"
|
|
20038
20251
|
},
|
|
20039
20252
|
{
|
|
20040
20253
|
"name": "useIntersection",
|
|
20041
20254
|
"signature": "useIntersection({ root, rootMargin, threshold = 0, once = false, }: UseIntersectionOptions = {}): { ref: (node: T | null) => void; entry: IntersectionObserverEntry | null; isIntersecting: boolean }",
|
|
20042
|
-
"description": "Whether an element is in view (IntersectionObserver). Attach the returned\
|
|
20255
|
+
"description": "Whether an element is in view (IntersectionObserver). Attach the returned\n`ref`; read `isIntersecting` or the full `entry`. Returns false during\nserver rendering.\n\n```tsx\nconst { ref, isIntersecting } = useIntersection<HTMLDivElement>({ rootMargin: \"200px\", once: true });\n<div ref={ref}>{isIntersecting && <HeavyChart />}</div>\n```",
|
|
20043
20256
|
"module": "hooks/index.ts"
|
|
20044
20257
|
},
|
|
20045
20258
|
{
|
|
20046
20259
|
"name": "useElementSize",
|
|
20047
20260
|
"signature": "useElementSize(): { ref: (node: T | null) => void; width: number; height: number; }",
|
|
20048
|
-
"description": "An element's content-box size, kept current as it resizes\
|
|
20261
|
+
"description": "An element's content-box size, kept current as it resizes\n(ResizeObserver). Attach the returned `ref`. Both are 0 until measured\n(and during server rendering).\n\n```tsx\nconst { ref, width } = useElementSize<HTMLDivElement>();\n<div ref={ref}>{width > 600 ? <WideLayout /> : <NarrowLayout />}</div>\n```",
|
|
20049
20262
|
"module": "hooks/index.ts"
|
|
20050
20263
|
},
|
|
20051
20264
|
{
|
|
20052
20265
|
"name": "useHydrated",
|
|
20053
20266
|
"signature": "useHydrated(): boolean",
|
|
20054
|
-
"description": "False during server rendering and hydration, true after. Use it to render\
|
|
20267
|
+
"description": "False during server rendering and hydration, true after. Use it to render\nbrowser-only content (dates in the user's time zone, `window` values)\nwithout a hydration mismatch.\n\n```tsx\nconst hydrated = useHydrated();\nreturn <span>{hydrated ? new Date().toLocaleTimeString() : null}</span>;\n```",
|
|
20055
20268
|
"module": "hooks/index.ts"
|
|
20056
20269
|
},
|
|
20057
20270
|
{
|
|
20058
20271
|
"name": "useReducedMotion",
|
|
20059
20272
|
"signature": "useReducedMotion(): boolean",
|
|
20060
|
-
"description": "True when the user asked the system to reduce motion. The library's own\
|
|
20273
|
+
"description": "True when the user asked the system to reduce motion. The library's own\ntransitions already respect it (through the duration tokens); use this for\nmotion you drive from JavaScript (autoplay, scroll animations).",
|
|
20061
20274
|
"module": "hooks/index.ts"
|
|
20062
20275
|
},
|
|
20063
20276
|
{
|
|
20064
20277
|
"name": "useColorScheme",
|
|
20065
20278
|
"signature": "useColorScheme(): \"light\" | \"dark\"",
|
|
20066
|
-
"description": "The system color scheme, `\"light\"` or `\"dark\"`, kept in sync (`\"light\"`\
|
|
20279
|
+
"description": "The system color scheme, `\"light\"` or `\"dark\"`, kept in sync (`\"light\"`\nduring server rendering). To follow it, pass it to `Theme mode`.",
|
|
20067
20280
|
"module": "hooks/index.ts"
|
|
20068
20281
|
},
|
|
20069
20282
|
{
|
|
20070
20283
|
"name": "useOnline",
|
|
20071
20284
|
"signature": "useOnline(): boolean",
|
|
20072
|
-
"description": "Whether the browser has a network connection, kept in sync (true during\
|
|
20285
|
+
"description": "Whether the browser has a network connection, kept in sync (true during\nserver rendering). Pair a visible change (a Banner) with a polite\nannouncement when it goes offline.",
|
|
20073
20286
|
"module": "hooks/index.ts"
|
|
20074
20287
|
},
|
|
20075
20288
|
{
|
|
20076
20289
|
"name": "useDocumentVisibility",
|
|
20077
20290
|
"signature": "useDocumentVisibility(): DocumentVisibilityState",
|
|
20078
|
-
"description": "`\"visible\"` or `\"hidden\"` (another tab, minimized window), kept in sync.\
|
|
20291
|
+
"description": "`\"visible\"` or `\"hidden\"` (another tab, minimized window), kept in sync.\nUse it to pause polling, video, or animations in the background.",
|
|
20079
20292
|
"module": "hooks/index.ts"
|
|
20080
20293
|
},
|
|
20081
20294
|
{
|
|
20082
20295
|
"name": "useViewportSize",
|
|
20083
20296
|
"signature": "useViewportSize(): { width: number; height: number }",
|
|
20084
|
-
"description": "The viewport's width and height in CSS pixels, kept in sync (0 × 0 during\
|
|
20297
|
+
"description": "The viewport's width and height in CSS pixels, kept in sync (0 × 0 during\nserver rendering). For layout decisions, prefer CSS or `useMediaQuery`;\nuse this when you need the numbers.",
|
|
20085
20298
|
"module": "hooks/index.ts"
|
|
20086
20299
|
},
|
|
20087
20300
|
{
|
|
20088
20301
|
"name": "useVirtualizer",
|
|
20089
20302
|
"signature": "useVirtualizer({ count, scrollElement, estimateSize, overscan = 6, gap = 0, paddingStart = 0, paddingEnd = 0, initialCount = 10, }: UseVirtualizerOptions): Virtualizer",
|
|
20090
|
-
"description": "Headless list virtualization: renders only the items in view (plus\
|
|
20303
|
+
"description": "Headless list virtualization: renders only the items in view (plus\n`overscan`), so lists of 100,000 rows stay fast. Works with variable\nheights by measuring rendered items, and is SSR-safe (the server renders\nthe first `initialCount` items). `VirtualList` is built on it; use the\nhook directly for custom layouts.\n\n```tsx\nconst scrollRef = useRef<HTMLDivElement>(null);\nconst { virtualItems, totalSize, measureElement } = useVirtualizer({\n count: rows.length, scrollElement: scrollRef, estimateSize: 48,\n});\n<div ref={scrollRef} style={{ overflow: \"auto\", height: 400 }}>\n <div style={{ position: \"relative\", height: totalSize }}>\n {virtualItems.map((item) => (\n <div key={item.index} data-index={item.index} ref={measureElement}\n style={{ position: \"absolute\", top: 0, width: \"100%\", transform: `translateY(${item.start}px)` }}>\n {rows[item.index].name}\n </div>\n ))}\n </div>\n</div>\n```",
|
|
20091
20304
|
"module": "hooks/index.ts"
|
|
20092
20305
|
},
|
|
20093
20306
|
{
|
|
@@ -20107,13 +20320,13 @@
|
|
|
20107
20320
|
{
|
|
20108
20321
|
"name": "getLocaleDirection",
|
|
20109
20322
|
"signature": "getLocaleDirection(locale: string): \"ltr\" | \"rtl\"",
|
|
20110
|
-
"description": "The writing direction of a BCP 47 locale: `\"rtl\"` for Arabic, Hebrew,\
|
|
20323
|
+
"description": "The writing direction of a BCP 47 locale: `\"rtl\"` for Arabic, Hebrew,\nPersian, Urdu, and other right-to-left languages and scripts, else `\"ltr\"`.",
|
|
20111
20324
|
"module": "i18n/index.ts"
|
|
20112
20325
|
},
|
|
20113
20326
|
{
|
|
20114
20327
|
"name": "plural",
|
|
20115
20328
|
"signature": "plural(locale: string): PluralFn",
|
|
20116
|
-
"description": "Plural selector bound to a locale:\
|
|
20329
|
+
"description": "Plural selector bound to a locale:\n\n```ts\nconst p = plural(\"ru\");\np(3, { one: \"# файл\", few: \"# файла\", many: \"# файлов\", other: \"# файла\" }); // \"3 файла\"\n```",
|
|
20117
20330
|
"module": "i18n/index.ts"
|
|
20118
20331
|
},
|
|
20119
20332
|
{
|
|
@@ -20125,7 +20338,7 @@
|
|
|
20125
20338
|
{
|
|
20126
20339
|
"name": "mergeMessages",
|
|
20127
20340
|
"signature": "mergeMessages(base: Messages, override: PartialMessages | undefined): Messages",
|
|
20128
|
-
"description": "Overlays partial messages on a complete set: missing strings keep the\
|
|
20341
|
+
"description": "Overlays partial messages on a complete set: missing strings keep the\nbase value. Functions and arrays replace whole.",
|
|
20129
20342
|
"module": "i18n/index.ts"
|
|
20130
20343
|
},
|
|
20131
20344
|
{
|
|
@@ -20221,7 +20434,7 @@
|
|
|
20221
20434
|
{
|
|
20222
20435
|
"name": "getHotkeyHandler",
|
|
20223
20436
|
"signature": "getHotkeyHandler(hotkeys: HotkeyItem[])",
|
|
20224
|
-
"description": "A `keydown` handler for one element (an editor, a list): pass it to\
|
|
20437
|
+
"description": "A `keydown` handler for one element (an editor, a list): pass it to\n`onKeyDown`. Same shortcut format as `useHotkeys`; shortcuts fire while\ntyping in that element.\n\n```tsx\n<Textarea onKeyDown={getHotkeyHandler([[\"mod+enter\", send]])} />\n```",
|
|
20225
20438
|
"module": "hooks/index.ts"
|
|
20226
20439
|
},
|
|
20227
20440
|
{
|
|
@@ -20356,10 +20569,16 @@
|
|
|
20356
20569
|
"description": "Builds an accessible brand palette, for both modes, from any hex color.\r\nThrows a TypeError for anything that isn't `#rgb` or `#rrggbb`.",
|
|
20357
20570
|
"module": "utils/index.ts"
|
|
20358
20571
|
},
|
|
20572
|
+
{
|
|
20573
|
+
"name": "LIME_ACCENT",
|
|
20574
|
+
"signature": "LIME_ACCENT: AccentPalette",
|
|
20575
|
+
"description": "The lime accent (`accent=\"lime\"`, and the black brand's accent): the original bright yellow-green.",
|
|
20576
|
+
"module": "utils/index.ts"
|
|
20577
|
+
},
|
|
20359
20578
|
{
|
|
20360
20579
|
"name": "VOLT_ACCENT",
|
|
20361
|
-
"signature": "VOLT_ACCENT
|
|
20362
|
-
"description": "
|
|
20580
|
+
"signature": "VOLT_ACCENT",
|
|
20581
|
+
"description": "@deprecated Renamed to `LIME_ACCENT`.",
|
|
20363
20582
|
"module": "utils/index.ts"
|
|
20364
20583
|
},
|
|
20365
20584
|
{
|
|
@@ -20371,7 +20590,7 @@
|
|
|
20371
20590
|
{
|
|
20372
20591
|
"name": "brandAccent",
|
|
20373
20592
|
"signature": "brandAccent(primary: string): AccentPalette",
|
|
20374
|
-
"description": "The default accent of a brand: a bright, lighter shade of its primary\r\nhue (blue gets a sky blue, purple a lavender, gold an amber, green a\r\nmint). Colorless brands (black) keep the
|
|
20593
|
+
"description": "The default accent of a brand: a bright, lighter shade of its primary\r\nhue (blue gets a sky blue, purple a lavender, gold an amber, green a\r\nmint). Colorless brands (black) keep the lime.",
|
|
20375
20594
|
"module": "utils/index.ts"
|
|
20376
20595
|
},
|
|
20377
20596
|
{
|
|
@@ -20401,19 +20620,19 @@
|
|
|
20401
20620
|
{
|
|
20402
20621
|
"name": "isEmail",
|
|
20403
20622
|
"signature": "isEmail(value: string, { requireTld = true }: EmailOptions = {}): boolean",
|
|
20404
|
-
"description": "True for an email address in the usual format: the HTML spec's rules\
|
|
20623
|
+
"description": "True for an email address in the usual format: the HTML spec's rules\n(what browsers check for `type=\"email\"`), plus no leading, trailing, or\ndoubled dots before the `@`, length limits (64 before the `@`, 254 in all),\nand, by default, a top-level domain.",
|
|
20405
20624
|
"module": "utils/index.ts"
|
|
20406
20625
|
},
|
|
20407
20626
|
{
|
|
20408
20627
|
"name": "isIPv4",
|
|
20409
20628
|
"signature": "isIPv4(value: string): boolean",
|
|
20410
|
-
"description": "True for a dotted IPv4 address: four numbers 0-255 (`192.168.0.1`), no\
|
|
20629
|
+
"description": "True for a dotted IPv4 address: four numbers 0-255 (`192.168.0.1`), no\nleading zeros (`010.0.0.1` is ambiguous, so it's rejected).",
|
|
20411
20630
|
"module": "utils/index.ts"
|
|
20412
20631
|
},
|
|
20413
20632
|
{
|
|
20414
20633
|
"name": "isIPv6",
|
|
20415
20634
|
"signature": "isIPv6(value: string): boolean",
|
|
20416
|
-
"description": "True for an IPv6 address: eight groups of 1-4 hex digits, with `::`\
|
|
20635
|
+
"description": "True for an IPv6 address: eight groups of 1-4 hex digits, with `::`\nstanding for one or more zero groups (once at most), and an optional\nIPv4 tail (`::ffff:192.168.0.1`). Zone ids (`%eth0`) and brackets aren't\naccepted.",
|
|
20417
20636
|
"module": "utils/index.ts"
|
|
20418
20637
|
},
|
|
20419
20638
|
{
|
|
@@ -20437,7 +20656,7 @@
|
|
|
20437
20656
|
{
|
|
20438
20657
|
"name": "rules",
|
|
20439
20658
|
"signature": "rules",
|
|
20440
|
-
"description": "Rule helpers for `useFormValidation`, with messages from the locale:\
|
|
20659
|
+
"description": "Rule helpers for `useFormValidation`, with messages from the locale:\n\n```ts\nuseFormValidation({ rules: { email: rules.email(), server: rules.ip({ version: 4 }) } });\n```",
|
|
20441
20660
|
"module": "utils/index.ts"
|
|
20442
20661
|
},
|
|
20443
20662
|
{
|
|
@@ -20449,7 +20668,7 @@
|
|
|
20449
20668
|
{
|
|
20450
20669
|
"name": "parseHotkey",
|
|
20451
20670
|
"signature": "parseHotkey(hotkey: string, platform: HotkeyPlatform = detectPlatform()): ParsedHotkey | null",
|
|
20452
|
-
"description": "Parses a shortcut like `\"mod+k\"`, `\"shift+mod+p\"`, `\"ctrl+/\"`, or\
|
|
20671
|
+
"description": "Parses a shortcut like `\"mod+k\"`, `\"shift+mod+p\"`, `\"ctrl+/\"`, or\n`\"escape\"` into modifiers and a key. `mod` is ⌘ on Mac and Ctrl\nelsewhere. Returns null when there is no key.",
|
|
20453
20672
|
"module": "utils/index.ts"
|
|
20454
20673
|
},
|
|
20455
20674
|
{
|
|
@@ -20473,7 +20692,7 @@
|
|
|
20473
20692
|
{
|
|
20474
20693
|
"name": "copyToClipboard",
|
|
20475
20694
|
"signature": "copyToClipboard(text: string): Promise<void>",
|
|
20476
|
-
"description": "Copies text to the clipboard: the async Clipboard API when it's allowed,\
|
|
20695
|
+
"description": "Copies text to the clipboard: the async Clipboard API when it's allowed,\nelse a hidden-textarea fallback (insecure contexts, denied permission,\nolder browsers). Focus returns to where it was. Rejects when both fail.",
|
|
20477
20696
|
"module": "utils/index.ts"
|
|
20478
20697
|
}
|
|
20479
20698
|
]
|