ionbase-ui 0.75.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 (88) 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/NavItem.d.ts +7 -0
  18. package/dist/components/NavItem.d.ts.map +1 -1
  19. package/dist/components/NavItem.js +3 -1
  20. package/dist/components/NavItem.js.map +1 -1
  21. package/dist/components/Table.d.ts +18 -0
  22. package/dist/components/Table.d.ts.map +1 -1
  23. package/dist/components/Table.js +14 -3
  24. package/dist/components/Table.js.map +1 -1
  25. package/dist/components/TagGroup.d.ts +51 -0
  26. package/dist/components/TagGroup.d.ts.map +1 -0
  27. package/dist/components/TagGroup.js +84 -0
  28. package/dist/components/TagGroup.js.map +1 -0
  29. package/dist/components/TimeField.d.ts +72 -0
  30. package/dist/components/TimeField.d.ts.map +1 -0
  31. package/dist/components/TimeField.js +102 -0
  32. package/dist/components/TimeField.js.map +1 -0
  33. package/dist/components/index.d.ts +14 -1
  34. package/dist/components/index.d.ts.map +1 -1
  35. package/dist/components/index.js +6 -0
  36. package/dist/components/index.js.map +1 -1
  37. package/dist/components/iso-time.d.ts +23 -0
  38. package/dist/components/iso-time.d.ts.map +1 -0
  39. package/dist/components/iso-time.js +34 -0
  40. package/dist/components/iso-time.js.map +1 -0
  41. package/dist/components/shortcut.d.ts +19 -0
  42. package/dist/components/shortcut.d.ts.map +1 -0
  43. package/dist/components/shortcut.js +122 -0
  44. package/dist/components/shortcut.js.map +1 -0
  45. package/dist/components/use-table-sort.d.ts +38 -0
  46. package/dist/components/use-table-sort.d.ts.map +1 -0
  47. package/dist/components/use-table-sort.js +33 -0
  48. package/dist/components/use-table-sort.js.map +1 -0
  49. package/dist/figma-descriptions.json +103 -68
  50. package/dist/figma-map.json +353 -4
  51. package/dist/meta/Card.json +153 -0
  52. package/dist/meta/Checkbox.json +2 -2
  53. package/dist/meta/CommandPalette.json +178 -0
  54. package/dist/meta/DatePicker.json +2 -2
  55. package/dist/meta/Kbd.json +97 -0
  56. package/dist/meta/NavItem.json +17 -3
  57. package/dist/meta/Radio.json +2 -2
  58. package/dist/meta/RadioGroup.json +2 -2
  59. package/dist/meta/Table.json +4 -1
  60. package/dist/meta/TableBody.json +3 -0
  61. package/dist/meta/TableCell.json +49 -5
  62. package/dist/meta/TableHead.json +3 -0
  63. package/dist/meta/TableRow.json +3 -0
  64. package/dist/meta/Tag.json +54 -0
  65. package/dist/meta/TagGroup.json +211 -0
  66. package/dist/meta/TimeField.json +224 -0
  67. package/dist/meta/Toggle.json +2 -2
  68. package/dist/meta/components.json +1027 -39
  69. package/dist/meta/contrast.json +1307 -0
  70. package/dist/meta/index.json +86 -12
  71. package/dist/meta/patterns/DataTable.json +27 -3
  72. package/dist/meta/patterns/PageShell.json +15 -2
  73. package/dist/meta/patterns/index.json +7 -3
  74. package/dist/styles/agent-activity.css +4 -2
  75. package/dist/styles/approval-gate.css +4 -3
  76. package/dist/styles/card.css +75 -0
  77. package/dist/styles/citation.css +4 -2
  78. package/dist/styles/command-palette.css +250 -0
  79. package/dist/styles/confidence-indicator.css +2 -2
  80. package/dist/styles/index.css +5 -0
  81. package/dist/styles/kbd.css +47 -0
  82. package/dist/styles/nav-item.css +37 -2
  83. package/dist/styles/streaming-text.css +3 -2
  84. package/dist/styles/table.css +51 -0
  85. package/dist/styles/tag-group.css +172 -0
  86. package/dist/styles/time-field.css +31 -0
  87. package/llms.txt +2 -2
  88. package/package.json +1 -1
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "package": "ionbase-ui",
3
- "version": "0.75.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": {
@@ -7115,7 +7544,8 @@
7115
7544
  "guarantees": [
7116
7545
  "hover state comes from React Aria's pointer-aware `useHover`, so a tap does not stay hovered until the next tap elsewhere",
7117
7546
  "the focus ring shows for keyboard navigation only",
7118
- "the CSS keeps its own `:hover` / `:focus-visible` rules, so the styling survives without React"
7547
+ "the CSS keeps its own `:hover` / `:focus-visible` rules, so the styling survives without React",
7548
+ "`isCurrent` sets `aria-current=\"page\"`, and the current item is marked by an underline as well as colour, which survives forced-colours mode"
7119
7549
  ],
7120
7550
  "requires": [
7121
7551
  "an accessible name — `children`, or `aria-label` on an icon-only item"
@@ -7133,6 +7563,10 @@
7133
7563
  "dont": "marking the current page with colour only",
7134
7564
  "do": "`aria-current=\"page\"`",
7135
7565
  "why": "a screen-reader user is otherwise never told where they are"
7566
+ },
7567
+ {
7568
+ "dont": "marking the current page with a className or colour of your own instead of `isCurrent`",
7569
+ "why": "a screen reader hears nothing without `aria-current`, and a colour-only marker fails anyone who cannot see the difference"
7136
7570
  }
7137
7571
  ],
7138
7572
  "deprecated": [
@@ -7144,11 +7578,13 @@
7144
7578
  "stylesheet": "src/styles/nav-item.css",
7145
7579
  "tokens": [
7146
7580
  "--border-focus",
7581
+ "--border-primary",
7147
7582
  "--border-width-thick",
7148
7583
  "--font-family-sans",
7149
- "--font-weight-regular",
7584
+ "--font-weight-medium",
7150
7585
  "--icon-disabled",
7151
7586
  "--icon-interactive-hover",
7587
+ "--icon-primary",
7152
7588
  "--icon-size-sm",
7153
7589
  "--icon-tertiary",
7154
7590
  "--ion-duration-base",
@@ -7156,6 +7592,7 @@
7156
7592
  "--radius-sm",
7157
7593
  "--spacing-4",
7158
7594
  "--spacing-8",
7595
+ "--text-default",
7159
7596
  "--text-disabled",
7160
7597
  "--text-interactive-hover",
7161
7598
  "--text-secondary",
@@ -7175,6 +7612,12 @@
7175
7612
  "origin": "own",
7176
7613
  "description": "Figma's `Show Chevron`. Set on a nav item that opens a menu, not a plain link."
7177
7614
  },
7615
+ "isCurrent": {
7616
+ "type": "boolean | undefined",
7617
+ "required": false,
7618
+ "origin": "own",
7619
+ "description": "The page the user is on. Marks the item `aria-current=\"page\"` and draws\nthe same indicator Tabs' underline does — so a section switcher built from\nNavItems says where you are, in words for a screen reader and in more than\ncolour for everyone else."
7620
+ },
7178
7621
  "isDisabled": {
7179
7622
  "type": "boolean | undefined",
7180
7623
  "required": false,
@@ -7205,7 +7648,7 @@
7205
7648
  }
7206
7649
  },
7207
7650
  "propCounts": {
7208
- "own": 7,
7651
+ "own": 8,
7209
7652
  "aria": 0,
7210
7653
  "dom": 276,
7211
7654
  "other": 0
@@ -9094,8 +9537,8 @@
9094
9537
  "origin": "own",
9095
9538
  "values": [
9096
9539
  "neutral",
9097
- "brand",
9098
- "danger"
9540
+ "danger",
9541
+ "brand"
9099
9542
  ]
9100
9543
  },
9101
9544
  "isDisabled": {
@@ -9304,8 +9747,8 @@
9304
9747
  "origin": "own",
9305
9748
  "values": [
9306
9749
  "neutral",
9307
- "brand",
9308
- "danger"
9750
+ "danger",
9751
+ "brand"
9309
9752
  ]
9310
9753
  },
9311
9754
  "isDisabled": {
@@ -11574,6 +12017,7 @@
11574
12017
  "--font-family-sans",
11575
12018
  "--font-weight-medium",
11576
12019
  "--font-weight-regular",
12020
+ "--icon-default",
11577
12021
  "--icon-interactive",
11578
12022
  "--icon-secondary",
11579
12023
  "--icon-size-sm",
@@ -11581,6 +12025,8 @@
11581
12025
  "--ion-duration-base",
11582
12026
  "--ion-ease-out",
11583
12027
  "--radius-md",
12028
+ "--radius-xs",
12029
+ "--ring-focus",
11584
12030
  "--spacing-16",
11585
12031
  "--spacing-20",
11586
12032
  "--spacing-6",
@@ -11602,8 +12048,8 @@
11602
12048
  "origin": "own",
11603
12049
  "description": "Matches Figma's `Density` variant on `Table Cell` / `Table Row`.",
11604
12050
  "values": [
11605
- "compact",
11606
12051
  "default",
12052
+ "compact",
11607
12053
  "relaxed"
11608
12054
  ]
11609
12055
  },
@@ -11671,6 +12117,7 @@
11671
12117
  "--font-family-sans",
11672
12118
  "--font-weight-medium",
11673
12119
  "--font-weight-regular",
12120
+ "--icon-default",
11674
12121
  "--icon-interactive",
11675
12122
  "--icon-secondary",
11676
12123
  "--icon-size-sm",
@@ -11678,6 +12125,8 @@
11678
12125
  "--ion-duration-base",
11679
12126
  "--ion-ease-out",
11680
12127
  "--radius-md",
12128
+ "--radius-xs",
12129
+ "--ring-focus",
11681
12130
  "--spacing-16",
11682
12131
  "--spacing-20",
11683
12132
  "--spacing-6",
@@ -11751,6 +12200,17 @@
11751
12200
  "link": {
11752
12201
  "use": "recolours the content to `text/link` / `icon/primary` for a cell whose content navigates"
11753
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
+ }
11754
12214
  }
11755
12215
  },
11756
12216
  "slots": {
@@ -11769,10 +12229,15 @@
11769
12229
  "a11y": {
11770
12230
  "guarantees": [
11771
12231
  "`scope` is inferred when omitted — `col` in TableHead, `row` in TableBody — and can be set explicitly when the inference is wrong",
11772
- "`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"
11773
12235
  ],
11774
12236
  "requires": [
11775
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."
11776
12241
  ]
11777
12242
  },
11778
12243
  "antiPatterns": [
@@ -11787,6 +12252,14 @@
11787
12252
  {
11788
12253
  "dont": "`showDivider` on every cell",
11789
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"
11790
12263
  }
11791
12264
  ],
11792
12265
  "stylesheet": "src/styles/table.css",
@@ -11798,6 +12271,7 @@
11798
12271
  "--font-family-sans",
11799
12272
  "--font-weight-medium",
11800
12273
  "--font-weight-regular",
12274
+ "--icon-default",
11801
12275
  "--icon-interactive",
11802
12276
  "--icon-secondary",
11803
12277
  "--icon-size-sm",
@@ -11805,6 +12279,8 @@
11805
12279
  "--ion-duration-base",
11806
12280
  "--ion-ease-out",
11807
12281
  "--radius-md",
12282
+ "--radius-xs",
12283
+ "--ring-focus",
11808
12284
  "--spacing-16",
11809
12285
  "--spacing-20",
11810
12286
  "--spacing-6",
@@ -11849,13 +12325,13 @@
11849
12325
  ]
11850
12326
  },
11851
12327
  "variant": {
11852
- "type": "\"link\" | \"default\" | undefined",
12328
+ "type": "\"default\" | \"link\" | undefined",
11853
12329
  "required": false,
11854
12330
  "origin": "own",
11855
12331
  "description": "Figma's `Type=Link` — recolours the content to `text/link` /\n`icon/primary` rather than the body defaults.",
11856
12332
  "values": [
11857
- "link",
11858
- "default"
12333
+ "default",
12334
+ "link"
11859
12335
  ]
11860
12336
  },
11861
12337
  "showDivider": {
@@ -11875,6 +12351,23 @@
11875
12351
  "required": false,
11876
12352
  "origin": "own"
11877
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
+ },
11878
12371
  "children": {
11879
12372
  "type": "React.ReactNode",
11880
12373
  "required": false,
@@ -11882,7 +12375,7 @@
11882
12375
  }
11883
12376
  },
11884
12377
  "propCounts": {
11885
- "own": 8,
12378
+ "own": 10,
11886
12379
  "aria": 0,
11887
12380
  "dom": 284,
11888
12381
  "other": 0
@@ -11941,6 +12434,7 @@
11941
12434
  "--font-family-sans",
11942
12435
  "--font-weight-medium",
11943
12436
  "--font-weight-regular",
12437
+ "--icon-default",
11944
12438
  "--icon-interactive",
11945
12439
  "--icon-secondary",
11946
12440
  "--icon-size-sm",
@@ -11948,6 +12442,8 @@
11948
12442
  "--ion-duration-base",
11949
12443
  "--ion-ease-out",
11950
12444
  "--radius-md",
12445
+ "--radius-xs",
12446
+ "--ring-focus",
11951
12447
  "--spacing-16",
11952
12448
  "--spacing-20",
11953
12449
  "--spacing-6",
@@ -12030,6 +12526,7 @@
12030
12526
  "--font-family-sans",
12031
12527
  "--font-weight-medium",
12032
12528
  "--font-weight-regular",
12529
+ "--icon-default",
12033
12530
  "--icon-interactive",
12034
12531
  "--icon-secondary",
12035
12532
  "--icon-size-sm",
@@ -12037,6 +12534,8 @@
12037
12534
  "--ion-duration-base",
12038
12535
  "--ion-ease-out",
12039
12536
  "--radius-md",
12537
+ "--radius-xs",
12538
+ "--ring-focus",
12040
12539
  "--spacing-16",
12041
12540
  "--spacing-20",
12042
12541
  "--spacing-6",
@@ -12347,6 +12846,271 @@
12347
12846
  "other": 4
12348
12847
  }
12349
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
+ },
12350
13114
  "Textarea": {
12351
13115
  "name": "Textarea",
12352
13116
  "source": "src/components/Textarea.tsx",
@@ -12867,6 +13631,230 @@
12867
13631
  "other": 0
12868
13632
  }
12869
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
+ },
12870
13858
  "Toast": {
12871
13859
  "name": "Toast",
12872
13860
  "source": "src/components/Toast.tsx",
@@ -13343,8 +14331,8 @@
13343
14331
  "description": "Matches the Figma `Color` variant: Brand, Neutral, Danger.",
13344
14332
  "values": [
13345
14333
  "neutral",
13346
- "brand",
13347
- "danger"
14334
+ "danger",
14335
+ "brand"
13348
14336
  ],
13349
14337
  "default": "brand"
13350
14338
  },