ionbase-ui 0.76.0 → 0.81.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.
Files changed (82) hide show
  1. package/dist/components/Card.d.ts +61 -0
  2. package/dist/components/Card.d.ts.map +1 -0
  3. package/dist/components/Card.js +37 -0
  4. package/dist/components/Card.js.map +1 -0
  5. package/dist/components/CommandPalette.d.ts +76 -0
  6. package/dist/components/CommandPalette.d.ts.map +1 -0
  7. package/dist/components/CommandPalette.js +194 -0
  8. package/dist/components/CommandPalette.js.map +1 -0
  9. package/dist/components/DateField.d.ts +14 -0
  10. package/dist/components/DateField.d.ts.map +1 -1
  11. package/dist/components/DateField.js +1 -1
  12. package/dist/components/DateField.js.map +1 -1
  13. package/dist/components/Kbd.d.ts +45 -0
  14. package/dist/components/Kbd.d.ts.map +1 -0
  15. package/dist/components/Kbd.js +45 -0
  16. package/dist/components/Kbd.js.map +1 -0
  17. package/dist/components/Table.d.ts +18 -0
  18. package/dist/components/Table.d.ts.map +1 -1
  19. package/dist/components/Table.js +14 -3
  20. package/dist/components/Table.js.map +1 -1
  21. package/dist/components/TagGroup.d.ts +51 -0
  22. package/dist/components/TagGroup.d.ts.map +1 -0
  23. package/dist/components/TagGroup.js +84 -0
  24. package/dist/components/TagGroup.js.map +1 -0
  25. package/dist/components/TimeField.d.ts +72 -0
  26. package/dist/components/TimeField.d.ts.map +1 -0
  27. package/dist/components/TimeField.js +102 -0
  28. package/dist/components/TimeField.js.map +1 -0
  29. package/dist/components/index.d.ts +14 -1
  30. package/dist/components/index.d.ts.map +1 -1
  31. package/dist/components/index.js +6 -0
  32. package/dist/components/index.js.map +1 -1
  33. package/dist/components/iso-time.d.ts +23 -0
  34. package/dist/components/iso-time.d.ts.map +1 -0
  35. package/dist/components/iso-time.js +34 -0
  36. package/dist/components/iso-time.js.map +1 -0
  37. package/dist/components/shortcut.d.ts +19 -0
  38. package/dist/components/shortcut.d.ts.map +1 -0
  39. package/dist/components/shortcut.js +122 -0
  40. package/dist/components/shortcut.js.map +1 -0
  41. package/dist/components/use-table-sort.d.ts +38 -0
  42. package/dist/components/use-table-sort.d.ts.map +1 -0
  43. package/dist/components/use-table-sort.js +33 -0
  44. package/dist/components/use-table-sort.js.map +1 -0
  45. package/dist/figma-descriptions.json +103 -68
  46. package/dist/figma-map.json +343 -2
  47. package/dist/meta/Card.json +153 -0
  48. package/dist/meta/Checkbox.json +2 -2
  49. package/dist/meta/CommandPalette.json +178 -0
  50. package/dist/meta/DatePicker.json +2 -2
  51. package/dist/meta/Kbd.json +97 -0
  52. package/dist/meta/Radio.json +2 -2
  53. package/dist/meta/RadioGroup.json +2 -2
  54. package/dist/meta/Table.json +4 -1
  55. package/dist/meta/TableBody.json +3 -0
  56. package/dist/meta/TableCell.json +49 -5
  57. package/dist/meta/TableHead.json +3 -0
  58. package/dist/meta/TableRow.json +3 -0
  59. package/dist/meta/Tag.json +54 -0
  60. package/dist/meta/TagGroup.json +211 -0
  61. package/dist/meta/TimeField.json +224 -0
  62. package/dist/meta/Toggle.json +2 -2
  63. package/dist/meta/components.json +1010 -36
  64. package/dist/meta/contrast.json +1133 -6
  65. package/dist/meta/index.json +86 -12
  66. package/dist/meta/patterns/DataTable.json +27 -3
  67. package/dist/meta/patterns/PageShell.json +15 -2
  68. package/dist/meta/patterns/index.json +7 -3
  69. package/dist/styles/agent-activity.css +4 -2
  70. package/dist/styles/approval-gate.css +4 -3
  71. package/dist/styles/card.css +75 -0
  72. package/dist/styles/citation.css +4 -2
  73. package/dist/styles/command-palette.css +250 -0
  74. package/dist/styles/confidence-indicator.css +2 -2
  75. package/dist/styles/index.css +5 -0
  76. package/dist/styles/kbd.css +47 -0
  77. package/dist/styles/streaming-text.css +3 -2
  78. package/dist/styles/table.css +51 -0
  79. package/dist/styles/tag-group.css +172 -0
  80. package/dist/styles/time-field.css +31 -0
  81. package/llms.txt +2 -2
  82. package/package.json +1 -1
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "package": "ionbase-ui",
3
- "version": "0.76.0",
3
+ "version": "0.81.0",
4
4
  "generated": "by scripts/build-meta.mjs — do not edit; intent lives in meta/*.json",
5
5
  "hooks": [
6
6
  "useAgentRun",
7
+ "useTableSort",
7
8
  "useToast"
8
9
  ],
9
10
  "helpers": [
@@ -2665,6 +2666,159 @@
2665
2666
  "other": 0
2666
2667
  }
2667
2668
  },
2669
+ "Card": {
2670
+ "name": "Card",
2671
+ "source": "src/components/Card.tsx",
2672
+ "propsType": "CardProps",
2673
+ "description": "Card — a bordered surface that groups one part of a page.\n\nPromoted from the demo app, where the same surface was hand-written as\n`.demo-panel` in nine screens: a settings group, a chart panel, a runs log,\nthe danger zone. For a feature row with media use FullCard; for one\nheadline figure use StatTile.\n\nTHE TITLE DECIDES THE ELEMENT\n\nA titled card is a `<section aria-labelledby>` — a named region, so a\nscreen reader user can jump between cards the way a sighted one scans\nthem. An untitled card is a `<div>`: a `<section>` with no name is not a\nlandmark at all, and pretending otherwise only adds noise.\n\nNo `'use client'`: `useId` resolves on the server, and nothing here is\ninteractive. Interactive parts arrive through `action` and `children`.",
2674
+ "import": "import { Card } from 'ionbase-ui';",
2675
+ "status": "stable",
2676
+ "since": "0.77.0",
2677
+ "summary": "A bordered surface that groups one part of a page, with an optional title row. A titled card is a named region; an untitled one is a plain surface.",
2678
+ "useWhen": [
2679
+ "a page is several distinct groups — settings sections, dashboard panels, a log beside a chart",
2680
+ "a group needs a title, a one-line description or a single action like \"All runs\"",
2681
+ "a loading placeholder needs the same frame as the content it stands in for"
2682
+ ],
2683
+ "useInstead": [
2684
+ {
2685
+ "when": "it is a feature row with a headline, text and a media panel",
2686
+ "use": "FullCard"
2687
+ },
2688
+ {
2689
+ "when": "it is one headline figure and how it moved",
2690
+ "use": "StatTile"
2691
+ },
2692
+ {
2693
+ "when": "the content is a list of settings, each with a label and a control",
2694
+ "use": "SettingRow"
2695
+ },
2696
+ {
2697
+ "when": "the message needs the user's attention now rather than a place on the page",
2698
+ "use": "Alert"
2699
+ }
2700
+ ],
2701
+ "composition": {
2702
+ "note": "The title row is built from props — `title`, `description`, `action` — so every card lays it out the same way. The body is `children`, spaced 12 apart by the card's grid.",
2703
+ "example": "<Card title=\"Recent runs\" action={<Link variant=\"standalone\" href=\"/runs\">All runs</Link>}><Table aria-label=\"Recent runs\">…</Table></Card>"
2704
+ },
2705
+ "variants": {
2706
+ "intent": {
2707
+ "default": {
2708
+ "use": "every card that is not a danger zone"
2709
+ },
2710
+ "danger": {
2711
+ "use": "a card of irreversible actions — delete the workspace, transfer ownership. The rule turns red; the title must still say what is at stake"
2712
+ }
2713
+ }
2714
+ },
2715
+ "a11y": {
2716
+ "guarantees": [
2717
+ "a titled card renders a <section> named by its heading, so it is a landmark a screen reader can jump to",
2718
+ "an untitled card renders a <div>, never an unnamed <section>",
2719
+ "the danger intent is a border colour only, so the meaning stays in the words"
2720
+ ],
2721
+ "requires": [
2722
+ "the right `headingLevel` for where the card sits in the page outline",
2723
+ "`isRegion={false}` when the card's only content is a landmark named by the same words, such as a Table labelled by the card's title"
2724
+ ],
2725
+ "notes": [
2726
+ "`headingLevel` takes 2-6 and defaults to 2. It is a number rather than a variant because it is an outline decision, not a visual one: the title looks the same at every level.",
2727
+ "`description` renders only under a `title`. An untitled card has no title row to put it in."
2728
+ ]
2729
+ },
2730
+ "antiPatterns": [
2731
+ {
2732
+ "dont": "a heading passed in `children` instead of `title`",
2733
+ "why": "the card does not become a named region, and the title row loses its layout for `description` and `action`"
2734
+ },
2735
+ {
2736
+ "dont": "several buttons in `action`",
2737
+ "why": "the title row wraps and the card reads as a toolbar; `action` is for one control — put the rest in the body"
2738
+ },
2739
+ {
2740
+ "dont": "relying on `intent=\"danger\"` alone to warn",
2741
+ "why": "a red border is not read aloud and is easy to miss; the title and the button labels must say what cannot be undone"
2742
+ },
2743
+ {
2744
+ "dont": "cards nested inside cards",
2745
+ "why": "border inside border reads as a mistake; group the inner content with a heading or a Divider instead"
2746
+ }
2747
+ ],
2748
+ "stylesheet": "src/styles/card.css",
2749
+ "tokens": [
2750
+ "--border-error-subtle",
2751
+ "--border-subtle",
2752
+ "--border-width-default",
2753
+ "--font-family-sans",
2754
+ "--font-weight-semibold",
2755
+ "--radius-lg",
2756
+ "--spacing-12",
2757
+ "--spacing-16",
2758
+ "--spacing-2",
2759
+ "--spacing-8",
2760
+ "--surface-default",
2761
+ "--text-default",
2762
+ "--text-secondary",
2763
+ "--type-body-sm",
2764
+ "--type-body-sm-line-height",
2765
+ "--type-h6",
2766
+ "--type-h6-line-height"
2767
+ ],
2768
+ "props": {
2769
+ "title": {
2770
+ "type": "React.ReactNode",
2771
+ "required": false,
2772
+ "origin": "own",
2773
+ "description": "The card's heading. With a title the card is a `<section>` named by it;\nwithout one it is a plain surface — a `<div>`, for skeletons and for\ncontent that already has its own heading."
2774
+ },
2775
+ "headingLevel": {
2776
+ "type": "CardHeadingLevel | undefined",
2777
+ "required": false,
2778
+ "origin": "own",
2779
+ "description": "Heading element for the title. Defaults to `h2`: most cards are the\nfirst level of section under the page's `h1`. The outline is the page's\ndecision, so it is a prop, not a constant."
2780
+ },
2781
+ "description": {
2782
+ "type": "React.ReactNode",
2783
+ "required": false,
2784
+ "origin": "own",
2785
+ "description": "One line under the title — what the card holds or when changes apply."
2786
+ },
2787
+ "action": {
2788
+ "type": "React.ReactNode",
2789
+ "required": false,
2790
+ "origin": "own",
2791
+ "description": "One control at the end of the title row: a standalone Link (\"All runs\")\nor a small Button. More than one belongs in the body."
2792
+ },
2793
+ "intent": {
2794
+ "type": "CardIntent | undefined",
2795
+ "required": false,
2796
+ "origin": "own",
2797
+ "description": "`danger` for a card of irreversible actions — the danger zone.",
2798
+ "values": [
2799
+ "default",
2800
+ "danger"
2801
+ ]
2802
+ },
2803
+ "isRegion": {
2804
+ "type": "boolean | undefined",
2805
+ "required": false,
2806
+ "origin": "own",
2807
+ "description": "Whether a titled card is a landmark. Defaults to `true`. Set `false`\nwhen the card's only content is itself a landmark named by the same\nwords — a Table's scroll region — or the page gets two regions with one\nname. The heading still renders; only the `<section>` becomes a `<div>`."
2808
+ },
2809
+ "children": {
2810
+ "type": "React.ReactNode",
2811
+ "required": false,
2812
+ "origin": "own"
2813
+ }
2814
+ },
2815
+ "propCounts": {
2816
+ "own": 7,
2817
+ "aria": 0,
2818
+ "dom": 276,
2819
+ "other": 0
2820
+ }
2821
+ },
2668
2822
  "ChartLegend": {
2669
2823
  "name": "ChartLegend",
2670
2824
  "source": "src/components/Chart.tsx",
@@ -3076,8 +3230,8 @@
3076
3230
  "description": "Matches the Figma `Color` variant: Brand, Neutral, Danger.",
3077
3231
  "values": [
3078
3232
  "neutral",
3079
- "brand",
3080
- "danger"
3233
+ "danger",
3234
+ "brand"
3081
3235
  ],
3082
3236
  "default": "brand"
3083
3237
  },
@@ -3676,6 +3830,184 @@
3676
3830
  "other": 0
3677
3831
  }
3678
3832
  },
3833
+ "CommandPalette": {
3834
+ "name": "CommandPalette",
3835
+ "source": "src/components/CommandPalette.tsx",
3836
+ "propsType": "CommandPaletteProps",
3837
+ "description": "CommandPalette — every action in the product, one search away.\n\n⌘K (Ctrl+K elsewhere) opens it from anywhere; typing filters; Enter runs.\nMount it once, near the root, and hand it every command the product has.\n\nWHY NOT A MODAL WITH A COMBOBOX IN IT\n\nA Combobox's list is a popover that opens and closes on its own rules, and\nit SELECTS a value that stays selected. A palette's list is always open and\neach row is RUN, once, and the palette goes away. The ARIA shape is the same\n— a combobox driving a listbox through `aria-activedescendant` — so this\nuses it, without the parts of Combobox that exist for choosing a value.\n\nNOT A NAVIGATION MENU\n\nEverything in the palette must also be reachable without it. It is a faster\nroute for people who know what they want, and a shortcut nobody told you\nabout is not a route at all.",
3838
+ "import": "import { CommandPalette } from 'ionbase-ui';",
3839
+ "status": "stable",
3840
+ "since": "0.81.0",
3841
+ "summary": "A searchable list of every command in the product, opened with ⌘K / Ctrl+K from anywhere, that runs the chosen command and closes.",
3842
+ "useWhen": [
3843
+ "the product has more actions and destinations than its navigation shows at once",
3844
+ "people who use it daily want to reach anything without the pointer",
3845
+ "mounted once, near the root, with every command the product has"
3846
+ ],
3847
+ "useInstead": [
3848
+ {
3849
+ "when": "the user picks a value that stays chosen — an assignee, a country",
3850
+ "use": "Combobox",
3851
+ "why": "a combobox selects and keeps a value in a form; a palette runs a command once and goes away"
3852
+ },
3853
+ {
3854
+ "when": "a few actions belong to one object — a row's Edit, Duplicate, Delete",
3855
+ "use": "Menu",
3856
+ "why": "those actions belong beside the thing they act on, where people look for them"
3857
+ },
3858
+ {
3859
+ "when": "the search is over records — agents, runs — shown as a page of results",
3860
+ "use": "Input",
3861
+ "why": "a filtered table is a page with its own URL, and a palette is gone the moment it is used"
3862
+ }
3863
+ ],
3864
+ "a11y": {
3865
+ "role": "dialog",
3866
+ "guarantees": [
3867
+ "a modal dialog named by `label`: focus is trapped, the page behind is hidden from assistive tech, Escape and a scrim click close it, and focus returns to what had it",
3868
+ "focus stays in the search field, a combobox whose always-open listbox is driven by aria-activedescendant — typing and choosing never compete for focus",
3869
+ "sections are role=group, named by their heading",
3870
+ "disabled commands are listed with aria-disabled, skipped by the arrow keys, and do nothing on click",
3871
+ "the number of matches is announced through a polite live region as the query changes",
3872
+ "`onAction` runs after the palette has closed and handed focus back, so an action that moves focus keeps it",
3873
+ "shortcut hints are read as key names — \"Command Shift N\" — not as symbols"
3874
+ ],
3875
+ "requires": [
3876
+ "every command also reachable without the palette — it is a faster route, never the only one",
3877
+ "a visible way to open it for people who do not know the shortcut, such as a header button showing the keys"
3878
+ ]
3879
+ },
3880
+ "antiPatterns": [
3881
+ {
3882
+ "dont": "showing a `shortcut` on a command without binding that shortcut yourself",
3883
+ "why": "the palette displays a command's shortcut and does not listen for it; a hint for a key that does nothing is worse than no hint"
3884
+ },
3885
+ {
3886
+ "dont": "moving focus from inside a command's handler by running it before closing, or wrapping onAction to close the palette first",
3887
+ "why": "onAction already runs after close. While the dialog is mounted its focus scope pulls focus back to the search field"
3888
+ },
3889
+ {
3890
+ "dont": "rendering <CommandPalette> only when a state flag is true",
3891
+ "do": "mount it always and drive `isOpen`, or let it open itself on the shortcut",
3892
+ "why": "unmounted, it cannot listen for ⌘K"
3893
+ },
3894
+ {
3895
+ "dont": "labels that are nouns — \"Agents\", \"Dark mode\"",
3896
+ "do": "start with a verb — \"Go to Agents\", \"Switch to dark theme\"",
3897
+ "why": "a row says what pressing Enter will do; a noun leaves the reader guessing whether it opens, toggles or deletes"
3898
+ },
3899
+ {
3900
+ "dont": "two palettes on one page, or a second listener for ⌘K",
3901
+ "why": "both toggle on the same key press and cancel each other out"
3902
+ }
3903
+ ],
3904
+ "slots": {
3905
+ "emptyLabel": {
3906
+ "expects": "what to show when nothing matches"
3907
+ }
3908
+ },
3909
+ "stylesheet": "src/styles/command-palette.css",
3910
+ "tokens": [
3911
+ "--border-subtle",
3912
+ "--border-width-default",
3913
+ "--border-width-thick",
3914
+ "--font-family-sans",
3915
+ "--font-weight-medium",
3916
+ "--icon-disabled",
3917
+ "--icon-secondary",
3918
+ "--icon-size-md",
3919
+ "--icon-size-sm",
3920
+ "--ion-shadow-shadow-xl",
3921
+ "--radius-sm",
3922
+ "--radius-xl",
3923
+ "--spacing-12",
3924
+ "--spacing-16",
3925
+ "--spacing-2",
3926
+ "--spacing-32",
3927
+ "--spacing-4",
3928
+ "--spacing-40",
3929
+ "--spacing-48",
3930
+ "--spacing-8",
3931
+ "--spacing-96",
3932
+ "--surface-hover",
3933
+ "--surface-overlay",
3934
+ "--surface-scrim",
3935
+ "--text-default",
3936
+ "--text-disabled",
3937
+ "--text-tertiary",
3938
+ "--type-body",
3939
+ "--type-body-lg",
3940
+ "--type-body-lg-line-height",
3941
+ "--type-body-line-height",
3942
+ "--type-body-sm",
3943
+ "--type-body-sm-line-height",
3944
+ "--type-caption",
3945
+ "--type-caption-line-height"
3946
+ ],
3947
+ "props": {
3948
+ "commands": {
3949
+ "type": "readonly CommandPaletteItem[]",
3950
+ "required": true,
3951
+ "origin": "own",
3952
+ "description": "Every command. Filtering happens here, against what is typed."
3953
+ },
3954
+ "onAction": {
3955
+ "type": "(id: string) => void",
3956
+ "required": true,
3957
+ "origin": "own",
3958
+ "description": "Runs the chosen command. Called AFTER the palette has closed and handed\nfocus back, so an action that moves focus — to a search field, into a\nform — keeps it."
3959
+ },
3960
+ "isOpen": {
3961
+ "type": "boolean | undefined",
3962
+ "required": false,
3963
+ "origin": "own"
3964
+ },
3965
+ "defaultOpen": {
3966
+ "type": "boolean | undefined",
3967
+ "required": false,
3968
+ "origin": "own"
3969
+ },
3970
+ "onOpenChange": {
3971
+ "type": "((isOpen: boolean) => void) | undefined",
3972
+ "required": false,
3973
+ "origin": "own"
3974
+ },
3975
+ "openShortcut": {
3976
+ "type": "string | null | undefined",
3977
+ "required": false,
3978
+ "origin": "own",
3979
+ "description": "The shortcut that opens it from anywhere on the page, and closes it again.\nDefaults to `mod+k` — ⌘K on a Mac, Ctrl+K elsewhere. `null` turns it off,\nfor a page that opens the palette some other way."
3980
+ },
3981
+ "label": {
3982
+ "type": "string | undefined",
3983
+ "required": false,
3984
+ "origin": "own",
3985
+ "description": "Names the dialog and the search field."
3986
+ },
3987
+ "placeholder": {
3988
+ "type": "string | undefined",
3989
+ "required": false,
3990
+ "origin": "own"
3991
+ },
3992
+ "emptyLabel": {
3993
+ "type": "React.ReactNode",
3994
+ "required": false,
3995
+ "origin": "own",
3996
+ "description": "Shown in place of the list when nothing matches."
3997
+ },
3998
+ "className": {
3999
+ "type": "string | undefined",
4000
+ "required": false,
4001
+ "origin": "own"
4002
+ }
4003
+ },
4004
+ "propCounts": {
4005
+ "own": 10,
4006
+ "aria": 0,
4007
+ "dom": 0,
4008
+ "other": 0
4009
+ }
4010
+ },
3679
4011
  "ConfidenceIndicator": {
3680
4012
  "name": "ConfidenceIndicator",
3681
4013
  "source": "src/components/ConfidenceIndicator.tsx",
@@ -3822,8 +4154,8 @@
3822
4154
  },
3823
4155
  {
3824
4156
  "when": "a time of day is part of the value",
3825
- "use": "Input",
3826
- "why": "this picker is fixed to day granularity; a time picker is not yet in this system"
4157
+ "use": "TimeField",
4158
+ "why": "this picker is fixed to day granularity; put a TimeField beside it — the two share one box and one set of states"
3827
4159
  }
3828
4160
  ],
3829
4161
  "variants": {
@@ -6131,32 +6463,129 @@
6131
6463
  "other": 0
6132
6464
  }
6133
6465
  },
6134
- "Link": {
6135
- "name": "Link",
6136
- "source": "src/components/Link.tsx",
6137
- "propsType": "LinkProps",
6138
- "description": "Link — Figma `Link` (774:1516).\n\nRenders an `<a>` when given an `href` and a `<button>` otherwise, the same\njudgment `NavItem` makes: the element follows what the caller is actually\nbuilding rather than a separate `as` prop. A link that does not navigate is\na button, and shipping one as an anchor breaks middle-click, \"open in new\ntab\" and the screen-reader announcement all at once.\n\nNO SIZE PROP, DELIBERATELY. A link is an inline element and inherits its\ntype from the text around it — `font-size: inherit` in the stylesheet, and\nicons sized in `em` so they scale with it. Figma has to draw its variants at\nsome concrete size (16/24, `type/body`), but that is one sample of an\ninheriting element, not a specification. Do not add a size ladder to match\nthe drawing.",
6139
- "import": "import { Link } from 'ionbase-ui';",
6466
+ "Kbd": {
6467
+ "name": "Kbd",
6468
+ "source": "src/components/Kbd.tsx",
6469
+ "propsType": "KbdProps",
6470
+ "description": "Kbd — a key, or a keyboard shortcut, as printed on the keyboard.\n\n`<kbd>` is the element HTML has for this, and a combination is a `<kbd>` of\n`<kbd>`s — the spec's own shape for \"press these together\".\n\nWHAT A SCREEN READER HEARS\n\nThe symbols do not survive being read aloud: VoiceOver says \"⌘\" as \"place of\ninterest sign\" in some voices and \"⇧\" as \"upwards white arrow\". So a\nshortcut's drawn keys are hidden and a visually hidden copy spells them out\n— \"Command K\", \"Control Shift P\".",
6471
+ "import": "import { Kbd } from 'ionbase-ui';",
6140
6472
  "status": "stable",
6141
- "summary": "Navigation to somewhere else. Renders an <a> when given `href`, a <button> otherwise.",
6473
+ "since": "0.81.0",
6474
+ "summary": "A key, or a keyboard shortcut, drawn as keycaps in the reader's platform's own symbols and read aloud by name.",
6142
6475
  "useWhen": [
6143
- "the interaction takes the user to a different page, view or resource",
6144
- "the destination should support middle-click, open-in-new-tab and copy-link"
6476
+ "telling people a shortcut exists — beside a button, in a tooltip, in a command palette row",
6477
+ "instructions that name a key: \"press Esc to cancel\""
6145
6478
  ],
6146
6479
  "useInstead": [
6147
6480
  {
6148
- "when": "the interaction changes something rather than going somewhere",
6149
- "use": "Button",
6150
- "why": "a link that does not navigate breaks middle-click, open-in-new-tab and the screen-reader announcement all at once"
6151
- },
6152
- {
6153
- "when": "it is a primary navigation destination in a header or sidebar",
6154
- "use": "NavItem"
6481
+ "when": "the text is code, a file name or a value",
6482
+ "use": "render it directly",
6483
+ "why": "Kbd means keys to press; `<code>` is the element for code"
6155
6484
  }
6156
6485
  ],
6157
- "variants": {
6158
- "variant": {
6159
- "inline": {
6486
+ "a11y": {
6487
+ "guarantees": [
6488
+ "a combination is a <kbd> of <kbd>s, the HTML spec's shape for keys pressed together",
6489
+ "the drawn symbols are hidden and a visually hidden copy spells the keys out — \"Command K\", \"Control Shift P\" — because ⌘ and ⇧ are read as symbol names or not at all",
6490
+ "`mod` resolves to ⌘ on Apple platforms and Ctrl elsewhere; server rendering shows Ctrl and corrects after hydration without a mismatch"
6491
+ ],
6492
+ "requires": [
6493
+ "the shortcut to be bound somewhere — Kbd shows a key, it does not listen for one"
6494
+ ]
6495
+ },
6496
+ "antiPatterns": [
6497
+ {
6498
+ "dont": "writing `ctrl+k` or `cmd+k` for the platform's command key",
6499
+ "do": "write `mod+k`",
6500
+ "why": "`ctrl` shows a Mac user a key they do not press for it; `cmd` shows everyone else a key they do not have"
6501
+ },
6502
+ {
6503
+ "dont": "children like \"⌘K\" or \"Ctrl+K\"",
6504
+ "do": "`shortcut=\"mod+k\"`",
6505
+ "why": "hand-typed symbols are wrong on the other platform and are read aloud as symbol names"
6506
+ },
6507
+ {
6508
+ "dont": "setting `platform`",
6509
+ "why": "it overrides detection, so half the readers see the other platform's keys; it exists for docs that compare the two"
6510
+ }
6511
+ ],
6512
+ "stylesheet": "src/styles/kbd.css",
6513
+ "tokens": [
6514
+ "--border-default",
6515
+ "--border-width-default",
6516
+ "--font-family-sans",
6517
+ "--font-weight-medium",
6518
+ "--radius-xs",
6519
+ "--spacing-2",
6520
+ "--spacing-20",
6521
+ "--spacing-4",
6522
+ "--surface-subtle",
6523
+ "--text-secondary",
6524
+ "--type-caption",
6525
+ "--type-caption-line-height"
6526
+ ],
6527
+ "props": {
6528
+ "shortcut": {
6529
+ "type": "string | undefined",
6530
+ "required": false,
6531
+ "origin": "own",
6532
+ "description": "A whole shortcut, `+`-joined: `mod+k`, `mod+shift+p`, `esc`. `mod` is ⌘\non a Mac and Ctrl elsewhere, so write `mod` rather than guessing. Drawn as\none key per part, in the platform's order."
6533
+ },
6534
+ "children": {
6535
+ "type": "React.ReactNode",
6536
+ "required": false,
6537
+ "origin": "own",
6538
+ "description": "One key, as printed: `K`, `Esc`, `/`. Use `shortcut` for a combination."
6539
+ },
6540
+ "platform": {
6541
+ "type": "KbdPlatform | undefined",
6542
+ "required": false,
6543
+ "origin": "own",
6544
+ "description": "Leave it out: the reader's platform is detected. Set it only to show a\nspecific platform's keys — a docs page comparing the two.",
6545
+ "values": [
6546
+ "mac",
6547
+ "other"
6548
+ ]
6549
+ },
6550
+ "className": {
6551
+ "type": "string | undefined",
6552
+ "required": false,
6553
+ "origin": "own"
6554
+ }
6555
+ },
6556
+ "propCounts": {
6557
+ "own": 4,
6558
+ "aria": 0,
6559
+ "dom": 0,
6560
+ "other": 0
6561
+ }
6562
+ },
6563
+ "Link": {
6564
+ "name": "Link",
6565
+ "source": "src/components/Link.tsx",
6566
+ "propsType": "LinkProps",
6567
+ "description": "Link — Figma `Link` (774:1516).\n\nRenders an `<a>` when given an `href` and a `<button>` otherwise, the same\njudgment `NavItem` makes: the element follows what the caller is actually\nbuilding rather than a separate `as` prop. A link that does not navigate is\na button, and shipping one as an anchor breaks middle-click, \"open in new\ntab\" and the screen-reader announcement all at once.\n\nNO SIZE PROP, DELIBERATELY. A link is an inline element and inherits its\ntype from the text around it — `font-size: inherit` in the stylesheet, and\nicons sized in `em` so they scale with it. Figma has to draw its variants at\nsome concrete size (16/24, `type/body`), but that is one sample of an\ninheriting element, not a specification. Do not add a size ladder to match\nthe drawing.",
6568
+ "import": "import { Link } from 'ionbase-ui';",
6569
+ "status": "stable",
6570
+ "summary": "Navigation to somewhere else. Renders an <a> when given `href`, a <button> otherwise.",
6571
+ "useWhen": [
6572
+ "the interaction takes the user to a different page, view or resource",
6573
+ "the destination should support middle-click, open-in-new-tab and copy-link"
6574
+ ],
6575
+ "useInstead": [
6576
+ {
6577
+ "when": "the interaction changes something rather than going somewhere",
6578
+ "use": "Button",
6579
+ "why": "a link that does not navigate breaks middle-click, open-in-new-tab and the screen-reader announcement all at once"
6580
+ },
6581
+ {
6582
+ "when": "it is a primary navigation destination in a header or sidebar",
6583
+ "use": "NavItem"
6584
+ }
6585
+ ],
6586
+ "variants": {
6587
+ "variant": {
6588
+ "inline": {
6160
6589
  "use": "the default — a link inside a paragraph. Underlined in every state, because a link in body copy must not rely on colour alone (WCAG 1.4.1)"
6161
6590
  },
6162
6591
  "standalone": {
@@ -9108,8 +9537,8 @@
9108
9537
  "origin": "own",
9109
9538
  "values": [
9110
9539
  "neutral",
9111
- "brand",
9112
- "danger"
9540
+ "danger",
9541
+ "brand"
9113
9542
  ]
9114
9543
  },
9115
9544
  "isDisabled": {
@@ -9318,8 +9747,8 @@
9318
9747
  "origin": "own",
9319
9748
  "values": [
9320
9749
  "neutral",
9321
- "brand",
9322
- "danger"
9750
+ "danger",
9751
+ "brand"
9323
9752
  ]
9324
9753
  },
9325
9754
  "isDisabled": {
@@ -11588,6 +12017,7 @@
11588
12017
  "--font-family-sans",
11589
12018
  "--font-weight-medium",
11590
12019
  "--font-weight-regular",
12020
+ "--icon-default",
11591
12021
  "--icon-interactive",
11592
12022
  "--icon-secondary",
11593
12023
  "--icon-size-sm",
@@ -11595,6 +12025,8 @@
11595
12025
  "--ion-duration-base",
11596
12026
  "--ion-ease-out",
11597
12027
  "--radius-md",
12028
+ "--radius-xs",
12029
+ "--ring-focus",
11598
12030
  "--spacing-16",
11599
12031
  "--spacing-20",
11600
12032
  "--spacing-6",
@@ -11616,8 +12048,8 @@
11616
12048
  "origin": "own",
11617
12049
  "description": "Matches Figma's `Density` variant on `Table Cell` / `Table Row`.",
11618
12050
  "values": [
11619
- "compact",
11620
12051
  "default",
12052
+ "compact",
11621
12053
  "relaxed"
11622
12054
  ]
11623
12055
  },
@@ -11685,6 +12117,7 @@
11685
12117
  "--font-family-sans",
11686
12118
  "--font-weight-medium",
11687
12119
  "--font-weight-regular",
12120
+ "--icon-default",
11688
12121
  "--icon-interactive",
11689
12122
  "--icon-secondary",
11690
12123
  "--icon-size-sm",
@@ -11692,6 +12125,8 @@
11692
12125
  "--ion-duration-base",
11693
12126
  "--ion-ease-out",
11694
12127
  "--radius-md",
12128
+ "--radius-xs",
12129
+ "--ring-focus",
11695
12130
  "--spacing-16",
11696
12131
  "--spacing-20",
11697
12132
  "--spacing-6",
@@ -11765,6 +12200,17 @@
11765
12200
  "link": {
11766
12201
  "use": "recolours the content to `text/link` / `icon/primary` for a cell whose content navigates"
11767
12202
  }
12203
+ },
12204
+ "sortDirection": {
12205
+ "ascending": {
12206
+ "use": "the column the rows are sorted by, smallest or earliest first"
12207
+ },
12208
+ "descending": {
12209
+ "use": "the column the rows are sorted by, largest or newest first"
12210
+ },
12211
+ "none": {
12212
+ "use": "a column that can be sorted but is not the current sort — it shows the neutral indicator"
12213
+ }
11768
12214
  }
11769
12215
  },
11770
12216
  "slots": {
@@ -11783,10 +12229,15 @@
11783
12229
  "a11y": {
11784
12230
  "guarantees": [
11785
12231
  "`scope` is inferred when omitted — `col` in TableHead, `row` in TableBody — and can be set explicitly when the inference is wrong",
11786
- "`header` decides the element directly rather than a `type` prop that could disagree with where the cell sits"
12232
+ "`header` decides the element directly rather than a `type` prop that could disagree with where the cell sits",
12233
+ "a sortable header renders a real <button> inside the <th>, so it is focusable and answers Enter and Space, and the header keeps its column-header role",
12234
+ "`aria-sort` is set on the sorted header only, never `none` on the rest"
11787
12235
  ],
11788
12236
  "requires": [
11789
12237
  "`scope=\"col\"` on header cells — inferred inside TableHead, but explicit for a header rendered outside it"
12238
+ ],
12239
+ "notes": [
12240
+ "`useTableSort` returns `sortDirection` and `onSort` for each column. The table never reorders rows itself: they may be one page of a server's thousands."
11790
12241
  ]
11791
12242
  },
11792
12243
  "antiPatterns": [
@@ -11801,6 +12252,14 @@
11801
12252
  {
11802
12253
  "dont": "`showDivider` on every cell",
11803
12254
  "why": "it is a column rule, not a row rule — on every cell it becomes a grid"
12255
+ },
12256
+ {
12257
+ "dont": "`onSort` on a header without `sortDirection`",
12258
+ "why": "the cell stays plain text, so nothing can be clicked; `sortDirection` is what makes it a sort control — pass `none` for an unsorted column"
12259
+ },
12260
+ {
12261
+ "dont": "`trailingIcon` on a sortable header",
12262
+ "why": "the sort indicator takes the trailing slot; the icon is not rendered"
11804
12263
  }
11805
12264
  ],
11806
12265
  "stylesheet": "src/styles/table.css",
@@ -11812,6 +12271,7 @@
11812
12271
  "--font-family-sans",
11813
12272
  "--font-weight-medium",
11814
12273
  "--font-weight-regular",
12274
+ "--icon-default",
11815
12275
  "--icon-interactive",
11816
12276
  "--icon-secondary",
11817
12277
  "--icon-size-sm",
@@ -11819,6 +12279,8 @@
11819
12279
  "--ion-duration-base",
11820
12280
  "--ion-ease-out",
11821
12281
  "--radius-md",
12282
+ "--radius-xs",
12283
+ "--ring-focus",
11822
12284
  "--spacing-16",
11823
12285
  "--spacing-20",
11824
12286
  "--spacing-6",
@@ -11863,13 +12325,13 @@
11863
12325
  ]
11864
12326
  },
11865
12327
  "variant": {
11866
- "type": "\"link\" | \"default\" | undefined",
12328
+ "type": "\"default\" | \"link\" | undefined",
11867
12329
  "required": false,
11868
12330
  "origin": "own",
11869
12331
  "description": "Figma's `Type=Link` — recolours the content to `text/link` /\n`icon/primary` rather than the body defaults.",
11870
12332
  "values": [
11871
- "link",
11872
- "default"
12333
+ "default",
12334
+ "link"
11873
12335
  ]
11874
12336
  },
11875
12337
  "showDivider": {
@@ -11889,6 +12351,23 @@
11889
12351
  "required": false,
11890
12352
  "origin": "own"
11891
12353
  },
12354
+ "sortDirection": {
12355
+ "type": "TableSortDirection | undefined",
12356
+ "required": false,
12357
+ "origin": "own",
12358
+ "description": "Makes a header cell a sort control. Set it on every sortable column:\n`ascending` or `descending` on the one the rows are sorted by, `none` on\nthe rest. Header cells only — ignored on a body cell. `useTableSort`\nreturns this and `onSort` for each column.",
12359
+ "values": [
12360
+ "none",
12361
+ "ascending",
12362
+ "descending"
12363
+ ]
12364
+ },
12365
+ "onSort": {
12366
+ "type": "(() => void) | undefined",
12367
+ "required": false,
12368
+ "origin": "own",
12369
+ "description": "Called when the header is activated. The table does not reorder rows:\nsorting is the caller's, since only it knows whether the data is local\nor paged from a server."
12370
+ },
11892
12371
  "children": {
11893
12372
  "type": "React.ReactNode",
11894
12373
  "required": false,
@@ -11896,7 +12375,7 @@
11896
12375
  }
11897
12376
  },
11898
12377
  "propCounts": {
11899
- "own": 8,
12378
+ "own": 10,
11900
12379
  "aria": 0,
11901
12380
  "dom": 284,
11902
12381
  "other": 0
@@ -11955,6 +12434,7 @@
11955
12434
  "--font-family-sans",
11956
12435
  "--font-weight-medium",
11957
12436
  "--font-weight-regular",
12437
+ "--icon-default",
11958
12438
  "--icon-interactive",
11959
12439
  "--icon-secondary",
11960
12440
  "--icon-size-sm",
@@ -11962,6 +12442,8 @@
11962
12442
  "--ion-duration-base",
11963
12443
  "--ion-ease-out",
11964
12444
  "--radius-md",
12445
+ "--radius-xs",
12446
+ "--ring-focus",
11965
12447
  "--spacing-16",
11966
12448
  "--spacing-20",
11967
12449
  "--spacing-6",
@@ -12044,6 +12526,7 @@
12044
12526
  "--font-family-sans",
12045
12527
  "--font-weight-medium",
12046
12528
  "--font-weight-regular",
12529
+ "--icon-default",
12047
12530
  "--icon-interactive",
12048
12531
  "--icon-secondary",
12049
12532
  "--icon-size-sm",
@@ -12051,6 +12534,8 @@
12051
12534
  "--ion-duration-base",
12052
12535
  "--ion-ease-out",
12053
12536
  "--radius-md",
12537
+ "--radius-xs",
12538
+ "--ring-focus",
12054
12539
  "--spacing-16",
12055
12540
  "--spacing-20",
12056
12541
  "--spacing-6",
@@ -12361,6 +12846,271 @@
12361
12846
  "other": 4
12362
12847
  }
12363
12848
  },
12849
+ "Tag": {
12850
+ "name": "Tag",
12851
+ "source": "../../node_modules/.pnpm/react-stately@3.48.0_react@19.2.8/node_modules/react-stately/dist/types/src/collections/Item.d.ts",
12852
+ "propsType": null,
12853
+ "import": "import { Tag } from 'ionbase-ui';",
12854
+ "status": "stable",
12855
+ "since": "0.79.0",
12856
+ "summary": "One tag in a TagGroup. Re-exported from react-stately's collection Item — `key` identifies it, children are its label.",
12857
+ "useWhen": [
12858
+ "a child of TagGroup — it is not usable anywhere else"
12859
+ ],
12860
+ "useInstead": [
12861
+ {
12862
+ "when": "it is a status label on its own",
12863
+ "use": "Badge"
12864
+ }
12865
+ ],
12866
+ "composition": {
12867
+ "order": [
12868
+ "TagGroup",
12869
+ "Tag"
12870
+ ],
12871
+ "note": "A collection item, not a rendered component: TagGroup reads it to build the collection, so it never appears in the DOM itself. That is why it exposes no props of its own here — `key` and `textValue` are the collection's API.",
12872
+ "example": "<Tag key=\"team:finance\">Team: Finance</Tag>"
12873
+ },
12874
+ "a11y": {
12875
+ "guarantees": [
12876
+ "the row, cell and remove-button roles and names are supplied by TagGroup, not by this element"
12877
+ ],
12878
+ "requires": [
12879
+ "a stable `key` — it is what `onRemove` hands back",
12880
+ "`textValue` when the label is not plain text, so type-ahead and the remove button's name have words to use"
12881
+ ]
12882
+ },
12883
+ "antiPatterns": [
12884
+ {
12885
+ "dont": "array indexes as keys in a list that shrinks",
12886
+ "why": "removing the first tag renumbers the rest, and the next removal drops the wrong one"
12887
+ },
12888
+ {
12889
+ "dont": "wrapping Tag in a <div> or a fragment inside TagGroup",
12890
+ "why": "TagGroup reads the collection from its direct children; a wrapper makes the tag invisible to it"
12891
+ }
12892
+ ],
12893
+ "stylesheet": null,
12894
+ "tokens": [],
12895
+ "props": {},
12896
+ "propCounts": {
12897
+ "own": 0,
12898
+ "aria": 0,
12899
+ "dom": 0,
12900
+ "other": 0
12901
+ }
12902
+ },
12903
+ "TagGroup": {
12904
+ "name": "TagGroup",
12905
+ "source": "src/components/TagGroup.tsx",
12906
+ "propsType": "TagGroupProps",
12907
+ "description": "TagGroup — labels a person applied and can take away: active filters, the\nlabels on a record, the recipients of a message.\n\nNOT A BADGE\n\nA Badge reports a state the system decided — Failing, Paid. A Tag is a\nchoice someone made, which is why it is removable and why it is neutral:\ncolouring tags by intent would make \"Status: Failing\" as a filter look like\nthe agent is failing.\n\nWHAT REACT ARIA GIVES IT\n\n`useTagGroup` makes the group one tab stop with arrow keys between tags, a\nremove button per tag named \"Remove <tag>\", and Delete / Backspace to remove\nthe focused one. When a tag goes, focus moves to its neighbour. The one case\nReact Aria leaves open is the last tag: focus would fall to <body>, so the\ngroup takes it instead and reads out `emptyLabel`.\n\nNo selection: a chip that toggles on and off is a SegmentedControl or a\nCheckbox, which already say \"selected\" the way a screen reader expects.",
12908
+ "import": "import { TagGroup } from 'ionbase-ui';",
12909
+ "status": "stable",
12910
+ "since": "0.79.0",
12911
+ "summary": "Labels a person applied and can take away: active filters, the labels on a record, recipients. Neutral and removable, unlike Badge.",
12912
+ "useWhen": [
12913
+ "showing which filters are applied, each removable on its own",
12914
+ "the labels or categories someone attached to a record",
12915
+ "a set of chosen values — recipients, assignees — shown after they are picked"
12916
+ ],
12917
+ "useInstead": [
12918
+ {
12919
+ "when": "it is a status the system reports — Failing, Paid, Draft",
12920
+ "use": "Badge"
12921
+ },
12922
+ {
12923
+ "when": "the chips toggle on and off rather than being removed",
12924
+ "use": "SegmentedControl"
12925
+ },
12926
+ {
12927
+ "when": "several options can be switched on at once from a fixed list",
12928
+ "use": "Checkbox"
12929
+ }
12930
+ ],
12931
+ "composition": {
12932
+ "order": [
12933
+ "TagGroup",
12934
+ "Tag"
12935
+ ],
12936
+ "note": "Tag is react-stately's collection Item: `key` identifies it and is what `onRemove` hands back, children are its label. Pass `items` and a render function for a list that changes.",
12937
+ "example": "<TagGroup label=\"Active filters\" onRemove={(keys) => drop(keys)}><Tag key=\"status\">Status: Failing</Tag><Tag key=\"team\">Team: Finance</Tag></TagGroup>"
12938
+ },
12939
+ "variants": {
12940
+ "size": {
12941
+ "sm": {
12942
+ "use": "the default — filter rows, table toolbars, dense record headers"
12943
+ },
12944
+ "md": {
12945
+ "use": "a group that is the main content of its area, or sits beside md controls"
12946
+ }
12947
+ }
12948
+ },
12949
+ "a11y": {
12950
+ "guarantees": [
12951
+ "the group is one tab stop; arrow keys move between tags",
12952
+ "each remove button is named \"Remove\" plus the tag's label, so a screen reader hears which one",
12953
+ "Delete or Backspace on a focused tag removes it, and focus moves to its neighbour",
12954
+ "removing the last tag moves focus to the group, which reads `emptyLabel`, instead of dropping it to the page"
12955
+ ],
12956
+ "requires": [
12957
+ "a `label` or `aria-label` — it names the group, so \"Remove Finance\" is heard as a filter being removed",
12958
+ "`textValue` on a Tag whose children are not plain text",
12959
+ "`onRemove` to drop the keys from your own list; the group removes nothing itself"
12960
+ ],
12961
+ "notes": [
12962
+ "No selection. A chip that toggles is a SegmentedControl or a Checkbox, which already announce \"selected\" the way screen readers expect."
12963
+ ]
12964
+ },
12965
+ "antiPatterns": [
12966
+ {
12967
+ "dont": "using a TagGroup for statuses",
12968
+ "why": "tags are neutral and removable; a status is neither — use Badge"
12969
+ },
12970
+ {
12971
+ "dont": "`onRemove` that does not remove the tag",
12972
+ "why": "the button says \"Remove\", focus moves on, and the tag is still there — the one thing the control promised did not happen"
12973
+ },
12974
+ {
12975
+ "dont": "a filter row with no way to clear everything at once",
12976
+ "why": "removing six filters one by one is six targets; pair the group with a \"Clear filters\" Button"
12977
+ }
12978
+ ],
12979
+ "stylesheet": "src/styles/tag-group.css",
12980
+ "tokens": [
12981
+ "--border-disabled",
12982
+ "--border-subtle",
12983
+ "--border-width-default",
12984
+ "--border-width-thick",
12985
+ "--font-family-sans",
12986
+ "--font-weight-medium",
12987
+ "--icon-default",
12988
+ "--icon-disabled",
12989
+ "--icon-secondary",
12990
+ "--icon-size-2xs",
12991
+ "--ion-duration-base",
12992
+ "--ion-ease-out",
12993
+ "--radius-md",
12994
+ "--radius-sm",
12995
+ "--radius-xs",
12996
+ "--ring-focus",
12997
+ "--spacing-12",
12998
+ "--spacing-16",
12999
+ "--spacing-20",
13000
+ "--spacing-24",
13001
+ "--spacing-32",
13002
+ "--spacing-4",
13003
+ "--spacing-6",
13004
+ "--spacing-8",
13005
+ "--surface-disabled",
13006
+ "--surface-hover",
13007
+ "--surface-subtle",
13008
+ "--text-default",
13009
+ "--text-disabled",
13010
+ "--text-secondary",
13011
+ "--text-tertiary",
13012
+ "--type-body",
13013
+ "--type-body-line-height",
13014
+ "--type-body-sm",
13015
+ "--type-body-sm-line-height"
13016
+ ],
13017
+ "props": {
13018
+ "onRemove": {
13019
+ "type": "((keys: Set<Key>) => void) | undefined",
13020
+ "required": false,
13021
+ "origin": "own",
13022
+ "description": "Called with the keys of the tags to remove — from a tag's × button, or\nDelete / Backspace on a focused tag. Pass it and every tag shows a remove\nbutton; leave it out and the tags are read-only labels. The group does\nnot remove anything itself: drop the keys from your own list."
13023
+ },
13024
+ "size": {
13025
+ "type": "TagGroupSize | undefined",
13026
+ "required": false,
13027
+ "origin": "own",
13028
+ "description": "Figma's `Size`. Set once here, so every tag in a group matches.",
13029
+ "values": [
13030
+ "sm",
13031
+ "md"
13032
+ ],
13033
+ "default": "sm"
13034
+ },
13035
+ "emptyLabel": {
13036
+ "type": "React.ReactNode",
13037
+ "required": false,
13038
+ "origin": "own",
13039
+ "description": "Shown in place of the tags when there are none — \"No filters applied\".\nWithout it an empty group renders nothing but its label."
13040
+ },
13041
+ "className": {
13042
+ "type": "string | undefined",
13043
+ "required": false,
13044
+ "origin": "own"
13045
+ },
13046
+ "id": {
13047
+ "type": "string | undefined",
13048
+ "required": false,
13049
+ "origin": "aria",
13050
+ "description": "The element's unique identifier. See\n[MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id)."
13051
+ },
13052
+ "aria-describedby": {
13053
+ "type": "string | undefined",
13054
+ "required": false,
13055
+ "origin": "aria",
13056
+ "description": "Identifies the element (or elements) that describes the object."
13057
+ },
13058
+ "aria-details": {
13059
+ "type": "string | undefined",
13060
+ "required": false,
13061
+ "origin": "aria",
13062
+ "description": "Identifies the element (or elements) that provide a detailed, extended description for the\nobject."
13063
+ },
13064
+ "aria-label": {
13065
+ "type": "string | undefined",
13066
+ "required": false,
13067
+ "origin": "aria",
13068
+ "description": "Defines a string value that labels the current element."
13069
+ },
13070
+ "aria-labelledby": {
13071
+ "type": "string | undefined",
13072
+ "required": false,
13073
+ "origin": "aria",
13074
+ "description": "Identifies the element (or elements) that labels the current element."
13075
+ },
13076
+ "children": {
13077
+ "type": "CollectionChildren<T>",
13078
+ "required": true,
13079
+ "origin": "aria",
13080
+ "description": "The contents of the collection."
13081
+ },
13082
+ "description": {
13083
+ "type": "ReactNode",
13084
+ "required": false,
13085
+ "origin": "aria",
13086
+ "description": "A description for the field. Provides a hint such as specific requirements for what to choose."
13087
+ },
13088
+ "label": {
13089
+ "type": "ReactNode",
13090
+ "required": false,
13091
+ "origin": "aria",
13092
+ "description": "The content to display as the label."
13093
+ },
13094
+ "items": {
13095
+ "type": "Iterable<T> | undefined",
13096
+ "required": false,
13097
+ "origin": "aria",
13098
+ "description": "Item objects in the collection."
13099
+ },
13100
+ "disabledKeys": {
13101
+ "type": "Iterable<Key> | undefined",
13102
+ "required": false,
13103
+ "origin": "aria",
13104
+ "description": "The item keys that are disabled. These items cannot be selected, focused, or otherwise\ninteracted with."
13105
+ }
13106
+ },
13107
+ "propCounts": {
13108
+ "own": 4,
13109
+ "aria": 10,
13110
+ "dom": 0,
13111
+ "other": 0
13112
+ }
13113
+ },
12364
13114
  "Textarea": {
12365
13115
  "name": "Textarea",
12366
13116
  "source": "src/components/Textarea.tsx",
@@ -12881,6 +13631,230 @@
12881
13631
  "other": 0
12882
13632
  }
12883
13633
  },
13634
+ "TimeField": {
13635
+ "name": "TimeField",
13636
+ "source": "src/components/TimeField.tsx",
13637
+ "propsType": "TimeFieldProps",
13638
+ "description": "TimeField — a time of day, typed into segments.\n\nDatePicker's text half, for times: hour, minute and, where the locale uses\none, AM/PM — each a spinbutton that takes digits or arrow keys. The order,\nthe separator and the 12 or 24-hour clock come from the reader's locale, so\n\"2:30 PM\" and \"14:30\" are the same field and the same value.\n\nWHY NOT A TEXT INPUT OR `<input type=\"time\">`\n\nFree text means guessing whether \"2:30\" is afternoon. The native control\ncannot be styled to match the other fields, and Safari's has no picker at\nall. Segments cannot hold a time that does not exist, and every browser\ndraws them the same.\n\nNo dropdown of times. A list of 96 quarter-hours is slower to scan than two\ndigits are to type, and it makes 09:07 impossible. Where only a few times\nare allowed, that is a Select, not a TimeField.",
13639
+ "import": "import { TimeField } from 'ionbase-ui';",
13640
+ "status": "stable",
13641
+ "since": "0.80.0",
13642
+ "summary": "A time of day typed into hour, minute and AM/PM segments in the reader's own format, taking and returning a 24-hour HH:MM string.",
13643
+ "useWhen": [
13644
+ "the user sets a time of day — a schedule, a reminder, the start of a window",
13645
+ "any minute is allowed, so a list of choices would be long or would leave times out",
13646
+ "beside a DatePicker when the value is a date and a time; the two sit side by side and share one box"
13647
+ ],
13648
+ "useInstead": [
13649
+ {
13650
+ "when": "only a handful of times are allowed — delivery slots, office hours",
13651
+ "use": "Select",
13652
+ "why": "picking from four slots is a choice from a list, and a field that accepts any minute invites times the product will refuse"
13653
+ },
13654
+ {
13655
+ "when": "the value is a duration — 30 minutes, 2 hours",
13656
+ "use": "NumberInput",
13657
+ "why": "a duration is an amount, not a time of day; 02:00 as a length reads as two in the morning"
13658
+ },
13659
+ {
13660
+ "when": "the value is a calendar date",
13661
+ "use": "DatePicker"
13662
+ }
13663
+ ],
13664
+ "variants": {
13665
+ "size": {
13666
+ "sm": {
13667
+ "use": "dense forms, table filters"
13668
+ },
13669
+ "md": {
13670
+ "use": "the default"
13671
+ },
13672
+ "lg": {
13673
+ "use": "prominent single-field forms"
13674
+ }
13675
+ },
13676
+ "granularity": {
13677
+ "hour": {
13678
+ "use": "on-the-hour schedules — the minute segment is not shown"
13679
+ },
13680
+ "minute": {
13681
+ "use": "the default — almost every time of day a person sets"
13682
+ },
13683
+ "second": {
13684
+ "use": "logs and precise triggers; the value becomes HH:MM:SS"
13685
+ }
13686
+ }
13687
+ },
13688
+ "a11y": {
13689
+ "guarantees": [
13690
+ "each segment is a spinbutton that announces its own name, value and bounds — 'hour, 2, minimum 1, maximum 12' — and takes digits or arrow keys",
13691
+ "each editable segment is its own tab stop, like DatePicker and the native time input; the separator is not",
13692
+ "the 12 or 24-hour clock, the separator and the AM/PM wording come from the reader's locale, so '2:30 PM' and '14:30' are the same field and the same value",
13693
+ "the segments sit in one labelled group, so the name is announced once on entry",
13694
+ "a time that does not exist cannot be typed",
13695
+ "a time outside `minValue`/`maxValue` turns the field invalid and names the bound in the reader's own format, without `isInvalid` — the value is kept, not silently clamped"
13696
+ ],
13697
+ "requires": [
13698
+ "`label`, or `aria-label` when no visible label is rendered",
13699
+ "a `description` saying which timezone the time is in, whenever the reader could be in a different one from the thing being scheduled"
13700
+ ],
13701
+ "notes": [
13702
+ "`value` is a wall-clock time with no date and no zone. Keep the timezone as its own field or setting; a time pinned to a `Date` drifts an hour at every daylight-saving change.",
13703
+ "The clock icon is decorative. There is no dropdown of times: two digits are faster to type than 96 quarter-hours are to scan, and a list makes 09:07 impossible."
13704
+ ]
13705
+ },
13706
+ "antiPatterns": [
13707
+ {
13708
+ "dont": "passing a Date, a timestamp or a locale-formatted string such as \"2:30 PM\" as `value`",
13709
+ "why": "the value is 24-hour HH:MM. A malformed value throws with the prop name rather than rendering an empty field"
13710
+ },
13711
+ {
13712
+ "dont": "forcing `hourCycle={12}` for everyone",
13713
+ "why": "a reader whose locale uses 24 hours has to translate every value, which is the misreading the segments exist to prevent. Leave it to the locale"
13714
+ },
13715
+ {
13716
+ "dont": "a schedule time with no timezone anywhere on the form",
13717
+ "why": "\"09:00\" is nine o'clock somewhere; if the reader and the workspace are in different zones, they will each assume their own"
13718
+ },
13719
+ {
13720
+ "dont": "using `minValue` and `maxValue` for a window that crosses midnight",
13721
+ "why": "22:00 to 02:00 has a minimum later than its maximum, so every time is refused; validate the window yourself and set `isInvalid`"
13722
+ }
13723
+ ],
13724
+ "stylesheet": "src/styles/time-field.css",
13725
+ "tokens": [],
13726
+ "props": {
13727
+ "label": {
13728
+ "type": "React.ReactNode",
13729
+ "required": false,
13730
+ "origin": "own",
13731
+ "description": "Field label. Required for a usable control — see `a11y.requires`."
13732
+ },
13733
+ "aria-label": {
13734
+ "type": "string | undefined",
13735
+ "required": false,
13736
+ "origin": "own",
13737
+ "description": "Names the field when there is no visible `label`."
13738
+ },
13739
+ "description": {
13740
+ "type": "React.ReactNode",
13741
+ "required": false,
13742
+ "origin": "own",
13743
+ "description": "Helper text below the field — \"In the workspace's timezone\"."
13744
+ },
13745
+ "errorMessage": {
13746
+ "type": "React.ReactNode",
13747
+ "required": false,
13748
+ "origin": "own",
13749
+ "description": "Replaces the helper text when `isInvalid` is set."
13750
+ },
13751
+ "isInvalid": {
13752
+ "type": "boolean | undefined",
13753
+ "required": false,
13754
+ "origin": "own"
13755
+ },
13756
+ "isDisabled": {
13757
+ "type": "boolean | undefined",
13758
+ "required": false,
13759
+ "origin": "own"
13760
+ },
13761
+ "isReadOnly": {
13762
+ "type": "boolean | undefined",
13763
+ "required": false,
13764
+ "origin": "own"
13765
+ },
13766
+ "isRequired": {
13767
+ "type": "boolean | undefined",
13768
+ "required": false,
13769
+ "origin": "own"
13770
+ },
13771
+ "size": {
13772
+ "type": "TimeFieldSize | undefined",
13773
+ "required": false,
13774
+ "origin": "own",
13775
+ "description": "Matches Input's `Size` variant: Small, Medium, Large.",
13776
+ "values": [
13777
+ "sm",
13778
+ "md",
13779
+ "lg"
13780
+ ]
13781
+ },
13782
+ "value": {
13783
+ "type": "string | null | undefined",
13784
+ "required": false,
13785
+ "origin": "own",
13786
+ "description": "The time as `HH:MM` (or `HH:MM:SS`), 24-hour, whatever the reader sees.\n`null` means no time is chosen. A wall-clock reading — no date, no\ntimezone. A malformed value throws rather than rendering an empty field."
13787
+ },
13788
+ "defaultValue": {
13789
+ "type": "string | undefined",
13790
+ "required": false,
13791
+ "origin": "own",
13792
+ "description": "The initial time, for an uncontrolled field."
13793
+ },
13794
+ "onChange": {
13795
+ "type": "((value: IsoTime | null) => void) | undefined",
13796
+ "required": false,
13797
+ "origin": "own",
13798
+ "description": "Fires with `HH:MM` (`HH:MM:SS` at second granularity), or `null` when cleared."
13799
+ },
13800
+ "minValue": {
13801
+ "type": "string | undefined",
13802
+ "required": false,
13803
+ "origin": "own",
13804
+ "description": "Earliest allowed time, `HH:MM`. Later is not wrapped past midnight."
13805
+ },
13806
+ "maxValue": {
13807
+ "type": "string | undefined",
13808
+ "required": false,
13809
+ "origin": "own",
13810
+ "description": "Latest allowed time, `HH:MM`."
13811
+ },
13812
+ "granularity": {
13813
+ "type": "TimeFieldGranularity | undefined",
13814
+ "required": false,
13815
+ "origin": "own",
13816
+ "description": "Defaults to `minute`. `second` adds a seconds segment and value.",
13817
+ "values": [
13818
+ "hour",
13819
+ "minute",
13820
+ "second"
13821
+ ]
13822
+ },
13823
+ "hourCycle": {
13824
+ "type": "12 | 24 | undefined",
13825
+ "required": false,
13826
+ "origin": "own",
13827
+ "description": "12 or 24-hour display. Leave it out: the reader's locale already knows,\nand forcing 12-hour on a 24-hour reader is the misreading this field\nexists to prevent. The value is 24-hour either way."
13828
+ },
13829
+ "name": {
13830
+ "type": "string | undefined",
13831
+ "required": false,
13832
+ "origin": "own",
13833
+ "description": "Posts the value under this name, for an uncontrolled form."
13834
+ },
13835
+ "className": {
13836
+ "type": "string | undefined",
13837
+ "required": false,
13838
+ "origin": "own"
13839
+ },
13840
+ "wrapperClassName": {
13841
+ "type": "string | undefined",
13842
+ "required": false,
13843
+ "origin": "own"
13844
+ },
13845
+ "id": {
13846
+ "type": "string | undefined",
13847
+ "required": false,
13848
+ "origin": "own"
13849
+ }
13850
+ },
13851
+ "propCounts": {
13852
+ "own": 20,
13853
+ "aria": 0,
13854
+ "dom": 0,
13855
+ "other": 0
13856
+ }
13857
+ },
12884
13858
  "Toast": {
12885
13859
  "name": "Toast",
12886
13860
  "source": "src/components/Toast.tsx",
@@ -13357,8 +14331,8 @@
13357
14331
  "description": "Matches the Figma `Color` variant: Brand, Neutral, Danger.",
13358
14332
  "values": [
13359
14333
  "neutral",
13360
- "brand",
13361
- "danger"
14334
+ "danger",
14335
+ "brand"
13362
14336
  ],
13363
14337
  "default": "brand"
13364
14338
  },