@ratio-ui/core 0.3.0 → 0.4.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 (65) hide show
  1. package/dist/catalogue.json +887 -27
  2. package/dist/components/accordion/Accordion.d.ts +19 -0
  3. package/dist/components/accordion/Accordion.js +52 -0
  4. package/dist/components/accordion/index.d.ts +2 -0
  5. package/dist/components/accordion/index.js +1 -0
  6. package/dist/components/breadcrumb/Breadcrumb.d.ts +19 -0
  7. package/dist/components/breadcrumb/Breadcrumb.js +50 -0
  8. package/dist/components/breadcrumb/index.d.ts +2 -0
  9. package/dist/components/breadcrumb/index.js +1 -0
  10. package/dist/components/chart/Chart.d.ts +137 -0
  11. package/dist/components/chart/Chart.js +238 -0
  12. package/dist/components/chart/index.d.ts +2 -0
  13. package/dist/components/chart/index.js +1 -0
  14. package/dist/components/combobox/Combobox.d.ts +52 -0
  15. package/dist/components/combobox/Combobox.js +92 -0
  16. package/dist/components/combobox/index.d.ts +2 -0
  17. package/dist/components/combobox/index.js +1 -0
  18. package/dist/components/data-table/DataTable.js +1 -1
  19. package/dist/components/date-picker/DatePicker.d.ts +3 -2
  20. package/dist/components/date-picker/DatePicker.js +7 -3
  21. package/dist/components/date-range-picker/DateRangePicker.d.ts +3 -2
  22. package/dist/components/date-range-picker/DateRangePicker.js +7 -3
  23. package/dist/components/drawer/Drawer.d.ts +52 -0
  24. package/dist/components/drawer/Drawer.js +120 -0
  25. package/dist/components/drawer/index.d.ts +2 -0
  26. package/dist/components/drawer/index.js +1 -0
  27. package/dist/components/field/Field.d.ts +8 -5
  28. package/dist/components/field/Field.js +9 -3
  29. package/dist/components/input-otp/InputOtp.d.ts +43 -0
  30. package/dist/components/input-otp/InputOtp.js +45 -0
  31. package/dist/components/input-otp/index.d.ts +2 -0
  32. package/dist/components/input-otp/index.js +1 -0
  33. package/dist/components/key-value-list/KeyValueList.d.ts +25 -0
  34. package/dist/components/key-value-list/KeyValueList.js +40 -0
  35. package/dist/components/key-value-list/index.d.ts +2 -0
  36. package/dist/components/key-value-list/index.js +1 -0
  37. package/dist/components/link/Link.js +1 -1
  38. package/dist/components/page-header/PageHeader.js +8 -8
  39. package/dist/components/progress/Progress.d.ts +25 -0
  40. package/dist/components/progress/Progress.js +38 -0
  41. package/dist/components/progress/index.d.ts +2 -0
  42. package/dist/components/progress/index.js +1 -0
  43. package/dist/components/radio-group/RadioGroup.d.ts +18 -0
  44. package/dist/components/radio-group/RadioGroup.js +32 -0
  45. package/dist/components/radio-group/index.d.ts +2 -0
  46. package/dist/components/radio-group/index.js +1 -0
  47. package/dist/components/scroll-area/ScrollArea.d.ts +12 -0
  48. package/dist/components/scroll-area/ScrollArea.js +34 -0
  49. package/dist/components/scroll-area/index.d.ts +2 -0
  50. package/dist/components/scroll-area/index.js +1 -0
  51. package/dist/components/separator/Separator.d.ts +15 -0
  52. package/dist/components/separator/Separator.js +25 -0
  53. package/dist/components/separator/index.d.ts +2 -0
  54. package/dist/components/separator/index.js +1 -0
  55. package/dist/components/sheet/Sheet.d.ts +52 -0
  56. package/dist/components/sheet/Sheet.js +89 -0
  57. package/dist/components/sheet/index.d.ts +2 -0
  58. package/dist/components/sheet/index.js +1 -0
  59. package/dist/components/summary-chips/SummaryChips.js +1 -1
  60. package/dist/components/tag-input/TagInput.js +1 -1
  61. package/dist/index.d.ts +28 -4
  62. package/dist/index.js +13 -1
  63. package/dist/lib/cn.js +2 -1
  64. package/dist/styles.css +1 -1
  65. package/package.json +52 -1
@@ -1,6 +1,37 @@
1
1
  {
2
- "version": "0.3.0",
2
+ "version": "0.4.0",
3
3
  "components": [
4
+ {
5
+ "name": "Accordion",
6
+ "import": "import { Accordion } from '@ratio-ui/core'",
7
+ "description": "A stack of sections that open and close. One section is open at a time unless multiple is set.",
8
+ "props": [],
9
+ "usage": {
10
+ "whenToUse": "- Grouped help or settings where the reader opens only the section they need\n- A short list of sections with a heading each, such as setup steps or FAQs",
11
+ "whenNotToUse": "- A single block that hides and shows: use a collapsible\n- Switching between views of the same content: use tabs\n- Styling: Accordion takes no `className` and no `style`",
12
+ "example": "<Accordion>\n <AccordionItem value=\"shipping\">\n <AccordionTrigger>Shipping</AccordionTrigger>\n <AccordionContent>Ships in two days.</AccordionContent>\n </AccordionItem>\n</Accordion>"
13
+ },
14
+ "parts": [
15
+ {
16
+ "name": "AccordionItem",
17
+ "import": "import { AccordionItem } from '@ratio-ui/core'",
18
+ "description": "One section: a trigger and its content, separated from the next by a hairline.",
19
+ "props": []
20
+ },
21
+ {
22
+ "name": "AccordionTrigger",
23
+ "import": "import { AccordionTrigger } from '@ratio-ui/core'",
24
+ "description": "The heading row of a section, with a chevron that turns when it is open.",
25
+ "props": []
26
+ },
27
+ {
28
+ "name": "AccordionContent",
29
+ "import": "import { AccordionContent } from '@ratio-ui/core'",
30
+ "description": "The body of a section. It appears and disappears at once, with no height animation.",
31
+ "props": []
32
+ }
33
+ ]
34
+ },
4
35
  {
5
36
  "name": "Alert",
6
37
  "import": "import { Alert } from '@ratio-ui/core'",
@@ -234,6 +265,36 @@
234
265
  "example": "<Avatar name=\"My Store\" shape=\"square\" />\n<Avatar name=\"Kavya Singh\" />"
235
266
  }
236
267
  },
268
+ {
269
+ "name": "Breadcrumb",
270
+ "import": "import { Breadcrumb } from '@ratio-ui/core'",
271
+ "description": "The trail to the current page: links, chevron separators, the current page as text and an overflow ellipsis.",
272
+ "props": [
273
+ {
274
+ "name": "items",
275
+ "type": "BreadcrumbItem[]",
276
+ "required": true,
277
+ "description": "The trail, from the root to the current page."
278
+ },
279
+ {
280
+ "name": "maxItems",
281
+ "type": "number",
282
+ "required": false,
283
+ "description": "Past this many items the middle ones collapse behind an ellipsis, keeping the first and the last two."
284
+ },
285
+ {
286
+ "name": "ref",
287
+ "type": "Ref<HTMLElement>",
288
+ "required": false,
289
+ "description": "Receives the rendered nav."
290
+ }
291
+ ],
292
+ "usage": {
293
+ "whenToUse": "- Show where a page sits in a hierarchy, with a way back up",
294
+ "whenNotToUse": "- Switching between sibling views: use Tabs",
295
+ "example": "<Breadcrumb items={[{ label: 'Products', href: '/products' }, { label: 'Runner' }]} maxItems={4} />"
296
+ }
297
+ },
237
298
  {
238
299
  "name": "Button",
239
300
  "import": "import { Button } from '@ratio-ui/core'",
@@ -340,6 +401,198 @@
340
401
  }
341
402
  ]
342
403
  },
404
+ {
405
+ "name": "Chart",
406
+ "import": "import { Chart } from '@ratio-ui/core'",
407
+ "description": "A chart on Recharts, drawn with the chart tokens. It holds the config the tooltip and legend read, styles Recharts' ticks, grid and cursor with the tokens, and is a figure named by its label and summarised by a description, a visually hidden data table, or both. Motion is colour fades only, so every series passes isAnimationActive false.",
408
+ "props": [
409
+ {
410
+ "name": "description",
411
+ "type": "string",
412
+ "required": false,
413
+ "description": "One sentence on what the chart shows, read after its name, such as \"Sales peak on Tuesday\"."
414
+ },
415
+ {
416
+ "name": "table",
417
+ "type": "ChartTable",
418
+ "required": false,
419
+ "description": "The chart's rows as a visually hidden table, for assistive technology."
420
+ },
421
+ {
422
+ "name": "config",
423
+ "type": "ChartConfig",
424
+ "required": true,
425
+ "description": "The series and categories, by data key: their labels, icons and chart tokens."
426
+ },
427
+ {
428
+ "name": "label",
429
+ "type": "string",
430
+ "required": true,
431
+ "description": "The chart's accessible name, such as \"Sales by day\"."
432
+ },
433
+ {
434
+ "name": "aspect",
435
+ "type": "\"fill\" | \"square\" | \"video\"",
436
+ "required": false,
437
+ "default": "video",
438
+ "description": "The size: video (16:9, the default) or square at the full width, or fill to take the parent's size."
439
+ },
440
+ {
441
+ "name": "initialDimension",
442
+ "type": "{ width: number; height: number; }",
443
+ "required": false,
444
+ "default": "{ width: 320, height: 200 } as const",
445
+ "description": "The size the chart is drawn at before it measures its box. Defaults to 320 by 200."
446
+ },
447
+ {
448
+ "name": "children",
449
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>>",
450
+ "required": true,
451
+ "description": "One Recharts chart, such as a BarChart or a LineChart, whose series pass isAnimationActive false."
452
+ }
453
+ ],
454
+ "usage": {
455
+ "whenToUse": "- Analytics: a trend or a comparison over days or categories, as bars or lines\n- The page loader's bar mark\n- With a label and a one-sentence description, a data table, or both, so assistive technology gets the same summary\n- Every series passes isAnimationActive false: motion is colour fades only, and the tooltip never moves",
456
+ "whenNotToUse": "- A single number or a few numbers side by side: use MetricStrip\n- Progress towards an end: use Progress\n- Colours outside chart-1 to chart-5, or chart-4 and chart-5 alone on white: they sit below 3:1, so draw them beside a darker series",
457
+ "example": "import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts'\n\nconst config = { sales: { label: 'Sales', color: 'chart-1' } } satisfies ChartConfig\n\n<Chart config={config} label=\"Sales by day\" description=\"Sales peak on Friday\">\n <BarChart data={week} accessibilityLayer>\n <CartesianGrid vertical={false} />\n <XAxis dataKey=\"day\" tickLine={false} axisLine={false} />\n <ChartTooltip content={<ChartTooltipContent formatValue={formatMoney} />} />\n <Bar dataKey=\"sales\" fill={chartColor('chart-1')} isAnimationActive={false} />\n </BarChart>\n</Chart>"
458
+ },
459
+ "parts": [
460
+ {
461
+ "name": "ChartTooltip",
462
+ "import": "import { ChartTooltip } from '@ratio-ui/core'",
463
+ "description": "The chart's tooltip: Recharts' Tooltip, still (no movement), drawing ChartTooltipContent unless given other content.",
464
+ "props": [
465
+ {
466
+ "name": "cursor",
467
+ "type": "boolean",
468
+ "required": false,
469
+ "description": "Shows the cursor line or band under the pointer. On or off: Recharts' SVG props would carry a class name and a style."
470
+ }
471
+ ]
472
+ },
473
+ {
474
+ "name": "ChartTooltipContent",
475
+ "import": "import { ChartTooltipContent } from '@ratio-ui/core'",
476
+ "description": "The box a chart tooltip draws: the active point's label, then a row per series with its swatch, its config label and its value, a count unless formatValue writes it. A 1px border on white, no shadow.",
477
+ "props": [
478
+ {
479
+ "name": "active",
480
+ "type": "boolean",
481
+ "required": false,
482
+ "description": "Set by the tooltip: whether a point is active."
483
+ },
484
+ {
485
+ "name": "payload",
486
+ "type": "readonly Payload<ValueType, TooltipNameType>[]",
487
+ "required": false,
488
+ "description": "Set by the tooltip: the values of the series at the active point."
489
+ },
490
+ {
491
+ "name": "label",
492
+ "type": "ReactNode",
493
+ "required": false,
494
+ "description": "Set by the tooltip: the active point's label, such as its x-axis category."
495
+ },
496
+ {
497
+ "name": "labelFormatter",
498
+ "type": "((label: ReactNode, payload: readonly Payload<ValueType, TooltipNameType>[]) => ReactNode)",
499
+ "required": false,
500
+ "description": "Writes the label from it and the payload."
501
+ },
502
+ {
503
+ "name": "formatter",
504
+ "type": "Formatter<ValueType, TooltipNameType>",
505
+ "required": false,
506
+ "description": "Draws a whole row, in place of the swatch, name and value, from the value, name, item, index and payload."
507
+ },
508
+ {
509
+ "name": "indicator",
510
+ "type": "\"dashed\" | \"dot\" | \"line\"",
511
+ "required": false,
512
+ "default": "dot",
513
+ "description": "The swatch: a dot (the default), a line or a dashed line."
514
+ },
515
+ {
516
+ "name": "hideLabel",
517
+ "type": "boolean",
518
+ "required": false,
519
+ "default": "false",
520
+ "description": "Leaves out the label."
521
+ },
522
+ {
523
+ "name": "hideIndicator",
524
+ "type": "boolean",
525
+ "required": false,
526
+ "default": "false",
527
+ "description": "Leaves out the swatches."
528
+ },
529
+ {
530
+ "name": "nameKey",
531
+ "type": "string",
532
+ "required": false,
533
+ "description": "The key of the item, or its data row, whose value names the config entry of a row."
534
+ },
535
+ {
536
+ "name": "labelKey",
537
+ "type": "string",
538
+ "required": false,
539
+ "description": "The key whose config entry labels the tooltip, in place of the active point's label."
540
+ },
541
+ {
542
+ "name": "color",
543
+ "type": "\"chart-1\" | \"chart-2\" | \"chart-3\" | \"chart-4\" | \"chart-5\"",
544
+ "required": false,
545
+ "description": "One chart token for every swatch, in place of each series' own."
546
+ },
547
+ {
548
+ "name": "formatValue",
549
+ "type": "((value: number) => string)",
550
+ "required": false,
551
+ "description": "Writes a number for its series, such as formatMoney for sales. Defaults to a count."
552
+ }
553
+ ]
554
+ },
555
+ {
556
+ "name": "ChartLegend",
557
+ "import": "import { ChartLegend } from '@ratio-ui/core'",
558
+ "description": "The chart's legend: Recharts' Legend, drawing ChartLegendContent unless given other content.",
559
+ "props": []
560
+ },
561
+ {
562
+ "name": "ChartLegendContent",
563
+ "import": "import { ChartLegendContent } from '@ratio-ui/core'",
564
+ "description": "The row a chart legend draws: a swatch in each series' chart token, or its icon, and its config label.",
565
+ "props": [
566
+ {
567
+ "name": "payload",
568
+ "type": "readonly LegendPayload[]",
569
+ "required": false,
570
+ "description": "Set by the legend: its series."
571
+ },
572
+ {
573
+ "name": "verticalAlign",
574
+ "type": "\"bottom\" | \"middle\" | \"top\"",
575
+ "required": false,
576
+ "default": "bottom",
577
+ "description": "Set by the legend: whether it sits above or below the chart, which sets the side of its gap."
578
+ },
579
+ {
580
+ "name": "hideIcon",
581
+ "type": "boolean",
582
+ "required": false,
583
+ "default": "false",
584
+ "description": "Draws the swatch even where the config gives an icon."
585
+ },
586
+ {
587
+ "name": "nameKey",
588
+ "type": "string",
589
+ "required": false,
590
+ "description": "The key of the item whose value names the config entry of a series."
591
+ }
592
+ ]
593
+ }
594
+ ]
595
+ },
343
596
  {
344
597
  "name": "Checkbox",
345
598
  "import": "import { Checkbox } from '@ratio-ui/core'",
@@ -474,6 +727,124 @@
474
727
  "example": "```tsx\n<Collapsible title=\"More pricing\" summary={[{ label: 'Cost price', value: '₹450.00' }, { label: 'HSN code', value: '6109' }]}>\n ...fields...\n</Collapsible>\n```\n\nThe panel appears and disappears at once; the library has no height animation."
475
728
  }
476
729
  },
730
+ {
731
+ "name": "Combobox",
732
+ "import": "import { Combobox } from '@ratio-ui/core'",
733
+ "description": "A field to choose one option from a list, with a search: type to narrow the list, pick with the mouse or Enter. The field looks like an Input and the list like a Popover panel. For several choices shown as chips, use TagInput. Mark it invalid with aria-invalid; give the list an empty text with emptyText. Inside a Field, the label, hint, error and required mark reach the input.",
734
+ "props": [
735
+ {
736
+ "name": "items",
737
+ "type": "readonly ComboboxItem[]",
738
+ "required": true,
739
+ "description": "The options. Strings or { value, label } objects."
740
+ },
741
+ {
742
+ "name": "value",
743
+ "type": "string | null",
744
+ "required": false,
745
+ "description": "The chosen value for a controlled combobox, or null for none."
746
+ },
747
+ {
748
+ "name": "defaultValue",
749
+ "type": "string | null",
750
+ "required": false,
751
+ "description": "The value to start with when the combobox keeps its own state."
752
+ },
753
+ {
754
+ "name": "onValueChange",
755
+ "type": "((value: string | null, eventDetails: ChangeEventDetails) => void)",
756
+ "required": false,
757
+ "description": "Called with the new value, or null when the choice is cleared."
758
+ },
759
+ {
760
+ "name": "placeholder",
761
+ "type": "string",
762
+ "required": false,
763
+ "description": "The text shown while nothing is chosen."
764
+ },
765
+ {
766
+ "name": "emptyText",
767
+ "type": "string",
768
+ "required": false,
769
+ "default": "No results found",
770
+ "description": "The text shown when the search matches no option. Defaults to \"No results found\"."
771
+ },
772
+ {
773
+ "name": "aria-invalid",
774
+ "type": "boolean | \"true\" | \"false\"",
775
+ "required": false,
776
+ "description": "Marks the field invalid and draws the error ring, as on every form control. Field sets it when it shows an error."
777
+ },
778
+ {
779
+ "name": "invalid",
780
+ "type": "boolean",
781
+ "required": false,
782
+ "default": "false",
783
+ "description": "The same as aria-invalid, kept as its alias."
784
+ },
785
+ {
786
+ "name": "clearable",
787
+ "type": "boolean",
788
+ "required": false,
789
+ "default": "true",
790
+ "description": "Shows a button that clears the choice while one is made. Defaults to true."
791
+ },
792
+ {
793
+ "name": "disabled",
794
+ "type": "boolean",
795
+ "required": false,
796
+ "default": "false",
797
+ "description": "Stops people from changing the choice."
798
+ },
799
+ {
800
+ "name": "name",
801
+ "type": "string",
802
+ "required": false,
803
+ "description": "The name of the field in a form."
804
+ },
805
+ {
806
+ "name": "aria-label",
807
+ "type": "string",
808
+ "required": false,
809
+ "description": "The accessible name when no label is wired to the field."
810
+ },
811
+ {
812
+ "name": "aria-labelledby",
813
+ "type": "string",
814
+ "required": false,
815
+ "description": "The ids of the elements that name the field. Field sets it to its label."
816
+ },
817
+ {
818
+ "name": "aria-describedby",
819
+ "type": "string",
820
+ "required": false,
821
+ "description": "The ids of the elements that describe the field, such as its hint or error. Field sets it."
822
+ },
823
+ {
824
+ "name": "aria-required",
825
+ "type": "boolean | \"true\" | \"false\"",
826
+ "required": false,
827
+ "description": "Tells assistive technology that the field must be filled. Field sets it for a required field."
828
+ },
829
+ {
830
+ "name": "id",
831
+ "type": "string",
832
+ "required": false,
833
+ "description": "The id of the input, for a label's htmlFor."
834
+ },
835
+ {
836
+ "name": "ref",
837
+ "type": "Ref<HTMLInputElement>",
838
+ "required": false,
839
+ "description": "Receives the input element."
840
+ }
841
+ ],
842
+ "usage": {
843
+ "whenToUse": "- Choosing one option from a long list that people search, such as a vendor or a product type\n- A field where the list may be empty or the search may match nothing",
844
+ "whenNotToUse": "- A short list of a few options: use a radio group or tabs\n- Several choices shown as chips: use TagInput\n- A list of actions: use DropdownMenu",
845
+ "example": "<Combobox aria-label=\"Vendor\" items={['Acme', 'Northwind']} placeholder=\"Choose a vendor\" onValueChange={setVendor} />"
846
+ }
847
+ },
477
848
  {
478
849
  "name": "DataTable",
479
850
  "import": "import { DataTable } from '@ratio-ui/core'",
@@ -580,7 +951,7 @@
580
951
  {
581
952
  "name": "DatePicker",
582
953
  "import": "import { DatePicker } from '@ratio-ui/core'",
583
- "description": "A field that shows a day as DD/MM/YYYY, reads a typed date through the format module and opens a calendar in a popover from the button at its end. Text that is not a date is marked invalid when the field loses focus, and reports nothing. Under a name, a form submits the day as YYYY-MM-DD (empty when there is none), never the text on screen.",
954
+ "description": "A field that shows a day as DD/MM/YYYY, reads a typed date through the format module and opens a calendar in a popover from the button at its end. Text that is not a date reports null (as soon as it is typed over a day, and when the field loses focus) and is marked invalid, aria-invalid, when the field loses focus; the app shows Field's error from the null. Under a name, a form submits the day as YYYY-MM-DD (empty when there is none), never the text on screen.",
584
955
  "props": [
585
956
  {
586
957
  "name": "value",
@@ -599,7 +970,7 @@
599
970
  "name": "onValueChange",
600
971
  "type": "((value: Date | null) => void)",
601
972
  "required": false,
602
- "description": "Called with the day when a typed date is complete or a day is picked, and with null when the text is cleared."
973
+ "description": "Called with the day when a typed date is complete or a day is picked, and with null when the text is cleared or is not a date."
603
974
  },
604
975
  {
605
976
  "name": "disabledDays",
@@ -636,13 +1007,13 @@
636
1007
  "usage": {
637
1008
  "whenToUse": "- One day a person types or picks, such as a \"Best before\" custom field: `value` and `onValueChange` take a `Date` (or null when empty)\n- A date inside a table: `size` 32\n- Limiting the days: `disabledDays` (for example `{ before: new Date() }`), `startMonth` and `endMonth`, which limit the calendar (a typed date is not checked against them)",
638
1009
  "whenNotToUse": "- A period of days, such as an analytics range: use DateRangePicker\n- A calendar shown on the page, not behind a field: use Calendar\n- Showing a date as text: write it with the format module (`formatDate`)\n- Custom classes or inline styles: DatePicker takes neither `className` nor `style`",
639
- "example": "```tsx\nconst [bestBefore, setBestBefore] = useState<Date | null>(null)\n\n<DatePicker aria-label=\"Best before\" value={bestBefore} onValueChange={setBestBefore} />\n<DatePicker aria-label=\"Expiry\" size={32} aria-invalid aria-describedby=\"expiry-error\" />\n```\n\nThe field shows DD/MM/YYYY and reads typed dates through the format module (\"3/1/2026\" is 3 January). Text that is not a date turns the field invalid when it loses focus and is never reported.\n\nUnder a `name`, a form submits the committed day as `YYYY-MM-DD` (an empty string when the field is empty), not the text on screen: `<DatePicker name=\"bestBefore\" />` posts `bestBefore=2026-10-28`. A disabled field posts nothing."
1010
+ "example": "```tsx\nconst [bestBefore, setBestBefore] = useState<Date | null>(null)\n\n<DatePicker aria-label=\"Best before\" value={bestBefore} onValueChange={setBestBefore} />\n<DatePicker aria-label=\"Expiry\" size={32} aria-invalid aria-describedby=\"expiry-error\" />\n```\n\nThe field shows DD/MM/YYYY and reads typed dates through the format module (\"3/1/2026\" is 3 January). Text that is not a date is never reported as a day: `onValueChange` gets `null` (as soon as a held day is typed over, and again when the field loses focus), and the field turns invalid (`aria-invalid`) when it loses focus."
640
1011
  }
641
1012
  },
642
1013
  {
643
1014
  "name": "DateRangePicker",
644
1015
  "import": "import { DateRangePicker } from '@ratio-ui/core'",
645
- "description": "A field that shows a range as two DD/MM/YYYY days, reads a typed range through the format module and opens a calendar in a popover from the button at its end, for analytics periods. Pick the first day, then the last. Text that is not a range in order is marked invalid when the field loses focus, and reports nothing. Under a name, a form submits two fields, `name.from` and `name.to`, each as YYYY-MM-DD (empty when there is no range), never the text on screen.",
1016
+ "description": "A field that shows a range as two DD/MM/YYYY days, reads a typed range through the format module and opens a calendar in a popover from the button at its end, for analytics periods. Pick the first day, then the last. Text that is not a range in order reports null (as soon as it is typed over a range, and when the field loses focus) and is marked invalid, aria-invalid, when the field loses focus. Under a name, a form submits two fields, `name.from` and `name.to`, each as YYYY-MM-DD (empty when there is no range), never the text on screen.",
646
1017
  "props": [
647
1018
  {
648
1019
  "name": "value",
@@ -661,7 +1032,7 @@
661
1032
  "name": "onValueChange",
662
1033
  "type": "((value: DateRangeValue | null) => void)",
663
1034
  "required": false,
664
- "description": "Called with the range when a typed range is complete or two days are picked, and with null when the text is cleared."
1035
+ "description": "Called with the range when a typed range is complete or two days are picked, and with null when the text is cleared or is not a range."
665
1036
  },
666
1037
  {
667
1038
  "name": "disabledDays",
@@ -698,7 +1069,7 @@
698
1069
  "usage": {
699
1070
  "whenToUse": "- A period of days a person types or picks, such as an analytics range: `value` and `onValueChange` take `{ from, to }` (or null when empty)\n- Limiting the days: `disabledDays` (for example `{ after: new Date() }` for reports), `startMonth` and `endMonth`, which limit the calendar (a typed range is not checked against them)\n- A range inside a table: `size` 32",
700
1071
  "whenNotToUse": "- One day: use DatePicker\n- A calendar shown on the page, not behind a field: use Calendar\n- Showing a range as text: write both days with the format module (`formatDate`)\n- Custom classes or inline styles: DateRangePicker takes neither `className` nor `style`",
701
- "example": "```tsx\nconst [period, setPeriod] = useState<DateRangeValue | null>(null)\n\n<DateRangePicker aria-label=\"Period\" value={period} onValueChange={setPeriod} disabledDays={{ after: new Date() }} />\n```\n\nThe field shows `28/10/2026 – 03/11/2026`. Pick the first day, then the last (an earlier second day swaps them). A typed range with the end before the start, or one day only, turns the field invalid when it loses focus and is never reported.\n\nUnder a `name`, a form submits two fields, `name.from` and `name.to`, each the committed day as `YYYY-MM-DD` (empty strings when the field is empty), not the text on screen: `<DateRangePicker name=\"period\" />` posts `period.from=2026-10-28` and `period.to=2026-11-03`. A disabled field posts nothing."
1072
+ "example": "```tsx\nconst [period, setPeriod] = useState<DateRangeValue | null>(null)\n\n<DateRangePicker aria-label=\"Period\" value={period} onValueChange={setPeriod} disabledDays={{ after: new Date() }} />\n```\n\nThe field shows `28/10/2026 – 03/11/2026`. Pick the first day, then the last (an earlier second day swaps them). A typed range with the end before the start, or one day only, is never reported as a range: `onValueChange` gets `null` (as soon as a held range is typed over, and again on blur), and the field turns invalid (`aria-invalid`) when it loses focus. Show the error with Field as in DatePicker: `error={touched && range === null ? 'Enter a period as DD/MM/YYYY – DD/MM/YYYY' : undefined}`, `touched` set from `onBlur`.\n\nUnder a `name`, a form submits two fields, `name.from` and `name.to`, each the committed day as `YYYY-MM-DD` (empty strings when the field is empty), not the text on screen: `<DateRangePicker name=\"period\" />` posts `period.from=2026-10-28` and `period.to=2026-11-03`. A disabled field posts nothing."
702
1073
  }
703
1074
  },
704
1075
  {
@@ -830,19 +1201,131 @@
830
1201
  ]
831
1202
  },
832
1203
  {
833
- "name": "DropdownMenu",
834
- "import": "import { DropdownMenu } from '@ratio-ui/core'",
835
- "description": "The root of a menu of actions that opens from a trigger. Compose it with DropdownMenuTrigger and DropdownMenuContent.",
836
- "props": [],
1204
+ "name": "Drawer",
1205
+ "import": "import { Drawer } from '@ratio-ui/core'",
1206
+ "description": "The root of a drawer: a panel people swipe away on a phone. It holds the open state and renders nothing itself. `swipeDirection` picks the edge it leaves by (down, the default, makes a bottom drawer). Pass `open` and `onOpenChange` to control it, or `defaultOpen`. The page behind is inert while it is open, with no scrim over it.",
1207
+ "props": [
1208
+ {
1209
+ "name": "showSwipeHandle",
1210
+ "type": "boolean",
1211
+ "required": false,
1212
+ "default": "false",
1213
+ "description": "Whether a short bar shows on the edge the drawer is swiped from, as a hint on phones. Defaults to false."
1214
+ }
1215
+ ],
837
1216
  "usage": {
838
- "whenToUse": "- A short list of actions behind one control: \"More actions\", a store menu, a \"…\" button\n- Actions that belong in groups, with a label per group, a hairline between groups, or a submenu\n- Items that show a key hint, a check (on or off) or one choice out of a few",
839
- "whenNotToUse": "- Choosing a value for a form field: use a select\n- Content that is not a list of actions, such as notifications or a form: use Popover\n- A destructive action without a confirm step: the destructive item only colours it\n- Styling: the parts take no `className` and no `style`, and the menu has no shadow",
840
- "example": "<DropdownMenu>\n <DropdownMenuTrigger>More actions</DropdownMenuTrigger>\n <DropdownMenuContent>\n <DropdownMenuGroup>\n <DropdownMenuLabel>Product</DropdownMenuLabel>\n <DropdownMenuItem>\n Edit\n <DropdownMenuShortcut>⌘E</DropdownMenuShortcut>\n </DropdownMenuItem>\n </DropdownMenuGroup>\n <DropdownMenuSeparator />\n <DropdownMenuItem variant=\"destructive\">Delete</DropdownMenuItem>\n </DropdownMenuContent>\n</DropdownMenu>"
1217
+ "whenToUse": "- A panel on a phone that people swipe away, such as filters or a short form under the list\n- A bottom drawer by default; a side drawer (swipeDirection left or right) for a phone menu\n- Show the swipe handle on phones as a hint; the corner close button stays for people who do not swipe",
1218
+ "whenNotToUse": "- A large surface on a wide screen, such as Settings: use a Sheet\n- A short task or a confirm step: use a Dialog\n- Help text on a control: use a Tooltip or Popover",
1219
+ "example": "<Drawer showSwipeHandle>\n <DrawerTrigger>Filters</DrawerTrigger>\n <DrawerContent>\n <DrawerHeader>\n <DrawerTitle>Filters</DrawerTitle>\n <DrawerDescription>Narrow the product list</DrawerDescription>\n </DrawerHeader>\n <DrawerFooter>\n <DrawerClose>Done</DrawerClose>\n </DrawerFooter>\n </DrawerContent>\n</Drawer>"
841
1220
  },
842
1221
  "parts": [
843
1222
  {
844
- "name": "DropdownMenuCheckboxItem",
845
- "import": "import { DropdownMenuCheckboxItem } from '@ratio-ui/core'",
1223
+ "name": "DrawerClose",
1224
+ "import": "import { DrawerClose } from '@ratio-ui/core'",
1225
+ "description": "A button that closes the drawer, such as Done in the footer.",
1226
+ "props": [
1227
+ {
1228
+ "name": "render",
1229
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DrawerCloseState>",
1230
+ "required": false,
1231
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
1232
+ }
1233
+ ]
1234
+ },
1235
+ {
1236
+ "name": "DrawerContent",
1237
+ "import": "import { DrawerContent } from '@ratio-ui/core'",
1238
+ "description": "The panel, white with a hairline and 24px corners on its inner edge. A bottom drawer spans the screen and sizes to its content; a side drawer is full height and three quarters of a phone screen, 384px from the small breakpoint up. It follows the finger while it is swiped and otherwise does not move: no slide in or out, no scrim, blur or shadow. Put DrawerHeader, the content and DrawerFooter inside.",
1239
+ "props": [
1240
+ {
1241
+ "name": "showCloseButton",
1242
+ "type": "boolean",
1243
+ "required": false,
1244
+ "default": "true",
1245
+ "description": "Whether the corner close button shows. It is the way to close without swiping. Defaults to true."
1246
+ },
1247
+ {
1248
+ "name": "closeLabel",
1249
+ "type": "string",
1250
+ "required": false,
1251
+ "default": "Close",
1252
+ "description": "The accessible name of the corner close button. Defaults to Close."
1253
+ },
1254
+ {
1255
+ "name": "render",
1256
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DrawerPopupState>",
1257
+ "required": false,
1258
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
1259
+ }
1260
+ ]
1261
+ },
1262
+ {
1263
+ "name": "DrawerDescription",
1264
+ "import": "import { DrawerDescription } from '@ratio-ui/core'",
1265
+ "description": "One line that says what the drawer is for, in subdued body text.",
1266
+ "props": [
1267
+ {
1268
+ "name": "render",
1269
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DrawerDescriptionState>",
1270
+ "required": false,
1271
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
1272
+ }
1273
+ ]
1274
+ },
1275
+ {
1276
+ "name": "DrawerFooter",
1277
+ "import": "import { DrawerFooter } from '@ratio-ui/core'",
1278
+ "description": "The actions at the bottom of the drawer, stacked full width for a thumb.",
1279
+ "props": []
1280
+ },
1281
+ {
1282
+ "name": "DrawerHeader",
1283
+ "import": "import { DrawerHeader } from '@ratio-ui/core'",
1284
+ "description": "The title and description, stacked, with room on the right for the close button.",
1285
+ "props": []
1286
+ },
1287
+ {
1288
+ "name": "DrawerTitle",
1289
+ "import": "import { DrawerTitle } from '@ratio-ui/core'",
1290
+ "description": "The drawer's name, in the b4 style. It names the drawer for assistive technology.",
1291
+ "props": [
1292
+ {
1293
+ "name": "render",
1294
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DrawerTitleState>",
1295
+ "required": false,
1296
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
1297
+ }
1298
+ ]
1299
+ },
1300
+ {
1301
+ "name": "DrawerTrigger",
1302
+ "import": "import { DrawerTrigger } from '@ratio-ui/core'",
1303
+ "description": "The button that opens the drawer.",
1304
+ "props": [
1305
+ {
1306
+ "name": "render",
1307
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DrawerTriggerState>",
1308
+ "required": false,
1309
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
1310
+ }
1311
+ ]
1312
+ }
1313
+ ]
1314
+ },
1315
+ {
1316
+ "name": "DropdownMenu",
1317
+ "import": "import { DropdownMenu } from '@ratio-ui/core'",
1318
+ "description": "The root of a menu of actions that opens from a trigger. Compose it with DropdownMenuTrigger and DropdownMenuContent.",
1319
+ "props": [],
1320
+ "usage": {
1321
+ "whenToUse": "- A short list of actions behind one control: \"More actions\", a store menu, a \"…\" button\n- Actions that belong in groups, with a label per group, a hairline between groups, or a submenu\n- Items that show a key hint, a check (on or off) or one choice out of a few",
1322
+ "whenNotToUse": "- Choosing a value for a form field: use a select\n- Content that is not a list of actions, such as notifications or a form: use Popover\n- A destructive action without a confirm step: the destructive item only colours it\n- Styling: the parts take no `className` and no `style`, and the menu has no shadow",
1323
+ "example": "<DropdownMenu>\n <DropdownMenuTrigger>More actions</DropdownMenuTrigger>\n <DropdownMenuContent>\n <DropdownMenuGroup>\n <DropdownMenuLabel>Product</DropdownMenuLabel>\n <DropdownMenuItem>\n Edit\n <DropdownMenuShortcut>⌘E</DropdownMenuShortcut>\n </DropdownMenuItem>\n </DropdownMenuGroup>\n <DropdownMenuSeparator />\n <DropdownMenuItem variant=\"destructive\">Delete</DropdownMenuItem>\n </DropdownMenuContent>\n</DropdownMenu>"
1324
+ },
1325
+ "parts": [
1326
+ {
1327
+ "name": "DropdownMenuCheckboxItem",
1328
+ "import": "import { DropdownMenuCheckboxItem } from '@ratio-ui/core'",
846
1329
  "description": "An item that toggles an option. A check shows at its start while it is on and keeps the menu open, so several options can be changed in one visit.",
847
1330
  "props": [
848
1331
  {
@@ -1099,7 +1582,7 @@
1099
1582
  {
1100
1583
  "name": "Field",
1101
1584
  "import": "import { Field } from '@ratio-ui/core'",
1102
- "description": "A form row: label, control, hint and error. It wires the label to the control by id, links the hint and error with aria-describedby, and sets aria-invalid when there is an error. The control keeps an id it already has.",
1585
+ "description": "A form row: label, control, hint and error. It wires the label to the control by id and aria-labelledby (which also names a group such as RadioGroup), links the hint and error with aria-describedby, and sets aria-invalid when there is an error. The control keeps an id it already has.",
1103
1586
  "props": [
1104
1587
  {
1105
1588
  "name": "label",
@@ -1141,13 +1624,13 @@
1141
1624
  "name": "children",
1142
1625
  "type": "ReactElement<Record<string, unknown>, string | JSXElementConstructor<any>> | ((props: FieldControlProps) => ReactNode)",
1143
1626
  "required": true,
1144
- "description": "The control: one element, such as Input, which gets an id, aria-describedby, aria-invalid, aria-required and disabled. When the control sits inside a wrapper such as InputGroup, pass a function instead and spread the props it receives onto the input itself."
1627
+ "description": "The control: one element, such as Input, which gets an id, aria-labelledby, aria-describedby, aria-invalid, aria-required and disabled. When the control sits inside a wrapper such as InputGroup, pass a function instead and spread the props it receives onto the input itself."
1145
1628
  }
1146
1629
  ],
1147
1630
  "usage": {
1148
- "whenToUse": "- Every form row: a label, one control, and optional hint, error, info tooltip and required mark\n- Wiring: Field sets the control's id, aria-describedby, aria-invalid, aria-required and disabled, so you do not",
1631
+ "whenToUse": "- Every form row: a label, one control, and optional hint, error, info tooltip and required mark\n- Wiring: Field sets the control's id, aria-labelledby, aria-describedby, aria-invalid, aria-required and disabled, so you do not",
1149
1632
  "whenNotToUse": "- A heading for a group of controls: use a fieldset with a legend\n- A label with no control beside it: use Label alone, with `htmlFor`\n- Long help that must be read to finish the task: put it on the screen as the hint",
1150
- "example": "```tsx\n<Field label=\"Title\" hint=\"Shown in search results\" error={errors.title} required>\n <Input />\n</Field>\n<Field label=\"MRP\" info=\"Maximum retail price, with tax\">\n <Input inputMode=\"decimal\" />\n</Field>\n```\n\nFor an input inside an InputGroup, pass a function child and spread its props onto the input: `<Field label=\"MRP\">{(props) => <InputGroup>…<InputGroupInput {...props} /></InputGroup>}</Field>`.\n\nFor a Select, which renders no element of its own, spread the props onto its trigger: `<Field label=\"GST rate\">{(props) => <Select items={rates}><SelectTrigger {...props}><SelectValue /></SelectTrigger><SelectContent>…</SelectContent></Select>}</Field>`.\n\nThe error shows beside the alert glyph in the error ink (question 1) and replaces the hint. Label is the same label as a separate part."
1633
+ "example": "```tsx\n<Field label=\"Title\" hint=\"Shown in search results\" error={errors.title} required>\n <Input />\n</Field>\n<Field label=\"MRP\" info=\"Maximum retail price, with tax\">\n <Input inputMode=\"decimal\" />\n</Field>\n```\n\nFor an input inside an InputGroup, pass a function child and spread its props onto the input: `<Field label=\"MRP\">{(props) => <InputGroup>…<InputGroupInput {...props} /></InputGroup>}</Field>`.\n\nA Combobox or a RadioGroup goes straight inside, like an Input: the label names a radio group through `aria-labelledby`, because a label cannot point at a group.\n\nFor a Select, which renders no element of its own, spread the props onto its trigger: `<Field label=\"GST rate\">{(props) => <Select items={rates}><SelectTrigger {...props}><SelectValue /></SelectTrigger><SelectContent>…</SelectContent></Select>}</Field>`.\n\nThe error shows beside the alert glyph in the error ink (question 1) and replaces the hint. Label is the same label as a separate part."
1151
1634
  },
1152
1635
  "parts": [
1153
1636
  {
@@ -1457,6 +1940,109 @@
1457
1940
  }
1458
1941
  ]
1459
1942
  },
1943
+ {
1944
+ "name": "InputOtp",
1945
+ "import": "import { InputOtp } from '@ratio-ui/core'",
1946
+ "description": "A one-time-code field: six 44px digit slots (set `length` for another count) over one hidden input, so the browser offers the SMS code (autocomplete one-time-code), a pasted code fills every slot, and the numeric keypad opens on phones. Digits only.",
1947
+ "props": [
1948
+ {
1949
+ "name": "length",
1950
+ "type": "number",
1951
+ "required": false,
1952
+ "default": "6",
1953
+ "description": "The number of digits. Defaults to 6."
1954
+ },
1955
+ {
1956
+ "name": "value",
1957
+ "type": "string",
1958
+ "required": false,
1959
+ "description": "The code typed so far, for a controlled field."
1960
+ },
1961
+ {
1962
+ "name": "defaultValue",
1963
+ "type": "string",
1964
+ "required": false,
1965
+ "description": "The code typed so far, for an uncontrolled field."
1966
+ },
1967
+ {
1968
+ "name": "onValueChange",
1969
+ "type": "((value: string) => void)",
1970
+ "required": false,
1971
+ "description": "Called with the whole code on every change, including a paste."
1972
+ },
1973
+ {
1974
+ "name": "onComplete",
1975
+ "type": "((value: string) => void)",
1976
+ "required": false,
1977
+ "description": "Called once with the code when every slot is filled."
1978
+ },
1979
+ {
1980
+ "name": "disabled",
1981
+ "type": "boolean",
1982
+ "required": false,
1983
+ "description": "Stops editing and halves the opacity."
1984
+ },
1985
+ {
1986
+ "name": "aria-invalid",
1987
+ "type": "boolean | \"true\" | \"false\"",
1988
+ "required": false,
1989
+ "description": "Draws the error ring on every slot."
1990
+ },
1991
+ {
1992
+ "name": "aria-label",
1993
+ "type": "string",
1994
+ "required": false,
1995
+ "description": "The accessible name, for example \"6-digit code\"."
1996
+ },
1997
+ {
1998
+ "name": "aria-labelledby",
1999
+ "type": "string",
2000
+ "required": false,
2001
+ "description": "The id of an element that names the field."
2002
+ },
2003
+ {
2004
+ "name": "aria-describedby",
2005
+ "type": "string",
2006
+ "required": false,
2007
+ "description": "The ids of the elements that describe the field, such as its hint or error. Field sets it."
2008
+ },
2009
+ {
2010
+ "name": "aria-required",
2011
+ "type": "boolean | \"true\" | \"false\"",
2012
+ "required": false,
2013
+ "description": "Tells assistive technology that the code must be filled. Field sets it for a required field."
2014
+ },
2015
+ {
2016
+ "name": "id",
2017
+ "type": "string",
2018
+ "required": false,
2019
+ "description": "The id of the hidden input, for a label's htmlFor. Field sets it."
2020
+ },
2021
+ {
2022
+ "name": "name",
2023
+ "type": "string",
2024
+ "required": false,
2025
+ "description": "The form field name."
2026
+ },
2027
+ {
2028
+ "name": "autoFocus",
2029
+ "type": "boolean",
2030
+ "required": false,
2031
+ "description": "Focuses the field on mount."
2032
+ },
2033
+ {
2034
+ "name": "ref",
2035
+ "type": "Ref<HTMLInputElement>",
2036
+ "required": false,
2037
+ "description": "Receives the hidden input element."
2038
+ }
2039
+ ],
2040
+ "usage": {
2041
+ "whenToUse": "- The code step of a sign-in: the 6-digit code sent by SMS or email (\"Verify and sign in\")\n- Any short numeric code: set `length` for another count\n- A wrong code: set `aria-invalid` and every slot draws the error ring, and put the message beside the field in text",
2042
+ "whenNotToUse": "- A code with letters: InputOtp takes digits only\n- A long or free-form value: use Input\n- Custom classes or inline styles: InputOtp takes no `className` and no `style`",
2043
+ "example": "```tsx\n<InputOtp aria-label=\"6-digit code\" value={code} onValueChange={setCode} onComplete={(code) => verify(code)} />\n<InputOtp aria-label=\"PIN\" length={4} aria-invalid />\n```\n\nThe browser offers the SMS code through `autocomplete=\"one-time-code\"`, and a pasted code fills every slot."
2044
+ }
2045
+ },
1460
2046
  {
1461
2047
  "name": "Kbd",
1462
2048
  "import": "import { Kbd } from '@ratio-ui/core'",
@@ -1483,6 +2069,31 @@
1483
2069
  }
1484
2070
  ]
1485
2071
  },
2072
+ {
2073
+ "name": "KeyValueList",
2074
+ "import": "import { KeyValueList } from '@ratio-ui/core'",
2075
+ "description": "Settings rows in a hairline bordered panel: the label on the left, the value on the right and an optional action. It is a description list, so a screen reader announces each label with its value.",
2076
+ "props": [
2077
+ {
2078
+ "name": "items",
2079
+ "type": "KeyValueItem[]",
2080
+ "required": true,
2081
+ "description": "The rows, in order."
2082
+ },
2083
+ {
2084
+ "name": "emptyValue",
2085
+ "type": "string",
2086
+ "required": false,
2087
+ "default": "Not set",
2088
+ "description": "What an empty value shows. Defaults to \"Not set\"."
2089
+ }
2090
+ ],
2091
+ "usage": {
2092
+ "whenToUse": "- Settings and detail rows: a label on the left, its value on the right, an optional action such as Edit\n- A value that may be empty: it shows a placeholder, \"Not set\" by default",
2093
+ "whenNotToUse": "- Rows of many columns to scan or sort: use Table or DataTable\n- Editing a value in place: use a form with Input",
2094
+ "example": "```tsx\n<KeyValueList\n items={[\n { label: 'Store name', value: 'Maison Yara', action: { label: 'Edit', onClick: edit } },\n { label: 'Phone' },\n ]}\n/>\n```\n\nMoney and dates arrive already formatted; use the format module."
2095
+ }
2096
+ },
1486
2097
  {
1487
2098
  "name": "Link",
1488
2099
  "import": "import { Link } from '@ratio-ui/core'",
@@ -1522,7 +2133,7 @@
1522
2133
  }
1523
2134
  ],
1524
2135
  "usage": {
1525
- "whenToUse": "- A text link to another page: \"← Products\", \"View all variants →\"\n- A back link in a detail page's body (the page header has its own back link)",
2136
+ "whenToUse": "- A text link to another page: \"← Products\", \"View all variants →\"\n- A back link in a detail page's body (the page header draws its back link with Link too, so the two look alike)",
1526
2137
  "whenNotToUse": "- An action that changes something: use Button\n- A whole table row: use DataTable's `rowHref`\n- A navigation row in the side navigation: use SideNav",
1527
2138
  "example": "<Link href=\"/products\" back>Products</Link>\n<Link href=\"/products/blue-shirt/variants\" forward>View all variants</Link>"
1528
2139
  }
@@ -1870,6 +2481,12 @@
1870
2481
  "import": "import { PopoverTrigger } from '@ratio-ui/core'",
1871
2482
  "description": "The element that opens the popover on click or Enter. It renders a button unless render says otherwise.",
1872
2483
  "props": [
2484
+ {
2485
+ "name": "render",
2486
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTriggerState>",
2487
+ "required": false,
2488
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2489
+ },
1873
2490
  {
1874
2491
  "name": "handle",
1875
2492
  "type": "PopoverHandle<unknown>",
@@ -1882,12 +2499,6 @@
1882
2499
  "required": false,
1883
2500
  "description": "A payload to pass to the popover when it is opened."
1884
2501
  },
1885
- {
1886
- "name": "render",
1887
- "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTriggerState>",
1888
- "required": false,
1889
- "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
1890
- },
1891
2502
  {
1892
2503
  "name": "delay",
1893
2504
  "type": "number",
@@ -1913,6 +2524,75 @@
1913
2524
  }
1914
2525
  ]
1915
2526
  },
2527
+ {
2528
+ "name": "Progress",
2529
+ "import": "import { Progress } from '@ratio-ui/core'",
2530
+ "description": "The thin progress bar of question 5, 2px high, for page loads and refetches. Determinate with a value, indeterminate without one (a partial bar that moves only when the loading animation token names a motion, question 5). Base UI gives it the progressbar role, its label and its value text.",
2531
+ "props": [
2532
+ {
2533
+ "name": "value",
2534
+ "type": "number | null",
2535
+ "required": false,
2536
+ "default": "null",
2537
+ "description": "The current value; leave it out or pass null for an indeterminate bar."
2538
+ },
2539
+ {
2540
+ "name": "max",
2541
+ "type": "number",
2542
+ "required": false,
2543
+ "default": "100",
2544
+ "description": "The value at which the bar is full. Defaults to 100; for steps, the number of steps."
2545
+ },
2546
+ {
2547
+ "name": "label",
2548
+ "type": "string",
2549
+ "required": false,
2550
+ "description": "A visible label, which also names the bar for screen readers."
2551
+ },
2552
+ {
2553
+ "name": "showValue",
2554
+ "type": "boolean",
2555
+ "required": false,
2556
+ "description": "Shows the value on the right: a percent, or \"value/max\" with steps."
2557
+ },
2558
+ {
2559
+ "name": "steps",
2560
+ "type": "boolean",
2561
+ "required": false,
2562
+ "description": "The step variant: counts completed steps out of max, such as 0/5 for the setup guide. Implies showValue."
2563
+ },
2564
+ {
2565
+ "name": "aria-label",
2566
+ "type": "string",
2567
+ "required": false,
2568
+ "description": "The accessible name when there is no visible label."
2569
+ }
2570
+ ],
2571
+ "usage": {
2572
+ "whenToUse": "- A page load or a refetch of content already on screen (the thin 2px bar, question 5)\n- The setup guide's step count, with the steps variant (\"0/5\")",
2573
+ "whenNotToUse": "- A first load of a list or table: use Skeleton rows\n- A short wait on one control: use Spinner",
2574
+ "example": "```tsx\n<Progress label=\"Loading orders\" value={60} showValue />\n<Progress label=\"Setup guide\" value={0} max={5} steps />\n<Progress aria-label=\"Refreshing\" />\n```\n\nWithout a value the bar is indeterminate: a static partial bar, since the loading animation token is none."
2575
+ }
2576
+ },
2577
+ {
2578
+ "name": "RadioGroup",
2579
+ "import": "import { RadioGroup } from '@ratio-ui/core'",
2580
+ "description": "A column of radio buttons where exactly one can be on. The arrow keys move the choice. aria-invalid on the group, as Field sets it, draws the error ring on every item.",
2581
+ "props": [],
2582
+ "usage": {
2583
+ "whenToUse": "- One choice among a few options that all deserve to be seen at once, such as a plan or a delivery method\n- A form field where the answer is exactly one of two to five values",
2584
+ "whenNotToUse": "- Several answers can be on: use checkboxes\n- A setting that applies at once: use a switch\n- A long list of options: use a select\n- Styling: RadioGroup and RadioGroupItem take no `className` and no `style`",
2585
+ "example": "```tsx\n<RadioGroup aria-label=\"Plan\" value={plan} onValueChange={setPlan}>\n <Inline gap={8} align=\"center\">\n <RadioGroupItem id=\"plan-free\" value=\"free\" />\n <Label htmlFor=\"plan-free\">Free</Label>\n </Inline>\n <Inline gap={8} align=\"center\">\n <RadioGroupItem id=\"plan-pro\" value=\"pro\" />\n <Label htmlFor=\"plan-pro\">Pro</Label>\n </Inline>\n</RadioGroup>\n```\n\nGive every item an `id` and a visible `Label` that points at it, so the text people read is the radio's name."
2586
+ },
2587
+ "parts": [
2588
+ {
2589
+ "name": "RadioGroupItem",
2590
+ "import": "import { RadioGroupItem } from '@ratio-ui/core'",
2591
+ "description": "One choice in a RadioGroup: a 14px round control like the checkbox, drawn with a hairline ring and filled in the primary colour with a white dot when chosen. An invisible 24px area around it takes the tap.",
2592
+ "props": []
2593
+ }
2594
+ ]
2595
+ },
1916
2596
  {
1917
2597
  "name": "SaveBar",
1918
2598
  "import": "import { SaveBar } from '@ratio-ui/core'",
@@ -1984,6 +2664,37 @@
1984
2664
  "example": "```tsx\n<SaveBar dirty={form.isDirty} saving={form.isSaving} onSave={form.save} onDiscard={form.reset} />\n```\n\nIt renders in flow and nothing when not dirty; the app shell decides where it sits. While saving, Save is disabled."
1985
2665
  }
1986
2666
  },
2667
+ {
2668
+ "name": "ScrollArea",
2669
+ "import": "import { ScrollArea } from '@ratio-ui/core'",
2670
+ "description": "A scrolling region that fills its parent, which sets the height or width, with a thin scrollbar in the design system greys.",
2671
+ "props": [
2672
+ {
2673
+ "name": "children",
2674
+ "type": "ReactNode",
2675
+ "required": true,
2676
+ "description": "The scrolling content."
2677
+ },
2678
+ {
2679
+ "name": "orientation",
2680
+ "type": "\"both\" | \"horizontal\" | \"vertical\"",
2681
+ "required": false,
2682
+ "default": "vertical",
2683
+ "description": "Which scrollbars to offer. Defaults to vertical."
2684
+ },
2685
+ {
2686
+ "name": "ref",
2687
+ "type": "Ref<HTMLDivElement>",
2688
+ "required": false,
2689
+ "description": "Receives the rendered element."
2690
+ }
2691
+ ],
2692
+ "usage": {
2693
+ "whenToUse": "- A bounded region (a list, a panel body) that scrolls inside a fixed height or width",
2694
+ "whenNotToUse": "- The page itself: let the page scroll",
2695
+ "example": "<div style={{ height: 240 }}>\n <ScrollArea>{rows}</ScrollArea>\n</div>"
2696
+ }
2697
+ },
1987
2698
  {
1988
2699
  "name": "SearchField",
1989
2700
  "import": "import { SearchField } from '@ratio-ui/core'",
@@ -2145,6 +2856,155 @@
2145
2856
  }
2146
2857
  ]
2147
2858
  },
2859
+ {
2860
+ "name": "Separator",
2861
+ "import": "import { Separator } from '@ratio-ui/core'",
2862
+ "description": "A 1px hairline in the divider grey, horizontal or vertical. A horizontal line fills its row and shrinks to share it, so two lines and a word between them in an Inline make the \"or\" divider; a vertical line keeps its 1px.",
2863
+ "props": [
2864
+ {
2865
+ "name": "orientation",
2866
+ "type": "\"horizontal\" | \"vertical\"",
2867
+ "required": false,
2868
+ "default": "horizontal",
2869
+ "description": "The direction of the line. Defaults to horizontal."
2870
+ },
2871
+ {
2872
+ "name": "decorative",
2873
+ "type": "boolean",
2874
+ "required": false,
2875
+ "default": "true",
2876
+ "description": "True, the default, hides the line from assistive technology; false makes it a semantic separator."
2877
+ },
2878
+ {
2879
+ "name": "ref",
2880
+ "type": "Ref<HTMLDivElement>",
2881
+ "required": false,
2882
+ "description": "Receives the rendered element."
2883
+ }
2884
+ ],
2885
+ "usage": {
2886
+ "whenToUse": "- A hairline between groups of content, or the \"or\" divider on the login screen\n- A vertical hairline between inline items",
2887
+ "whenNotToUse": "- A card or table edge: those own their border",
2888
+ "example": "```tsx\n<Separator />\n<Inline gap={8}>\n <Separator />\n <Text variant=\"c1\" tone=\"muted\">or</Text>\n <Separator />\n</Inline>\n<Separator orientation=\"vertical\" />\n<Separator decorative={false} />\n```\n\nIn the \"or\" divider the two lines share the row and the word keeps its width. Keep the word short: a longer phrase wraps before the lines give way."
2889
+ }
2890
+ },
2891
+ {
2892
+ "name": "Sheet",
2893
+ "import": "import { Sheet } from '@ratio-ui/core'",
2894
+ "description": "The root of a sheet. It holds the open state and renders nothing itself. Pass `open` and `onOpenChange` to control it, or `defaultOpen` to leave it uncontrolled. The page behind is inert while it is open, with no scrim over it.",
2895
+ "props": [],
2896
+ "usage": {
2897
+ "whenToUse": "- A bottom sheet for a large surface that sits over the page, such as Settings: it spans the screen and has 24px top corners\n- A side sheet for a panel that keeps the page in view, such as filters or the phone navigation\n- Work that needs more room than a 480px dialog",
2898
+ "whenNotToUse": "- A short task or a confirm step: use a Dialog\n- A panel people swipe away on a phone: use a Drawer\n- Help text on a control: use a Tooltip or Popover",
2899
+ "example": "<Sheet>\n <SheetTrigger>Settings</SheetTrigger>\n <SheetContent side=\"bottom\" closeLabel=\"Close settings\">\n <SheetHeader>\n <SheetTitle>General</SheetTitle>\n <SheetDescription>Store name, contact and address</SheetDescription>\n </SheetHeader>\n <SheetBody>Store details</SheetBody>\n <SheetFooter>\n <SheetClose>Done</SheetClose>\n </SheetFooter>\n </SheetContent>\n</Sheet>"
2900
+ },
2901
+ "parts": [
2902
+ {
2903
+ "name": "SheetBody",
2904
+ "import": "import { SheetBody } from '@ratio-ui/core'",
2905
+ "description": "The content between header and footer. It takes the free height and scrolls when the content is taller.",
2906
+ "props": []
2907
+ },
2908
+ {
2909
+ "name": "SheetClose",
2910
+ "import": "import { SheetClose } from '@ratio-ui/core'",
2911
+ "description": "A button that closes the sheet, such as Done in the footer.",
2912
+ "props": [
2913
+ {
2914
+ "name": "render",
2915
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogCloseState>",
2916
+ "required": false,
2917
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2918
+ }
2919
+ ]
2920
+ },
2921
+ {
2922
+ "name": "SheetContent",
2923
+ "import": "import { SheetContent } from '@ratio-ui/core'",
2924
+ "description": "The panel, white with a hairline on its inner edge. A side sheet is full height and three quarters of a phone screen, 384px from the small breakpoint up; a bottom sheet spans the screen, sizes to its content and has 24px top corners. It appears without sliding and with no scrim, blur or shadow. Put SheetHeader, SheetBody and SheetFooter inside.",
2925
+ "props": [
2926
+ {
2927
+ "name": "side",
2928
+ "type": "\"bottom\" | \"left\" | \"right\"",
2929
+ "required": false,
2930
+ "default": "right",
2931
+ "description": "The edge the sheet sits on: a full-height side sheet, or a bottom sheet with 24px top corners. Defaults to right."
2932
+ },
2933
+ {
2934
+ "name": "showCloseButton",
2935
+ "type": "boolean",
2936
+ "required": false,
2937
+ "default": "true",
2938
+ "description": "Whether the corner close button shows. Defaults to true."
2939
+ },
2940
+ {
2941
+ "name": "closeLabel",
2942
+ "type": "string",
2943
+ "required": false,
2944
+ "default": "Close",
2945
+ "description": "The accessible name of the corner close button. Defaults to Close."
2946
+ },
2947
+ {
2948
+ "name": "render",
2949
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogPopupState>",
2950
+ "required": false,
2951
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2952
+ }
2953
+ ]
2954
+ },
2955
+ {
2956
+ "name": "SheetDescription",
2957
+ "import": "import { SheetDescription } from '@ratio-ui/core'",
2958
+ "description": "One line that says what the sheet is for, in subdued body text.",
2959
+ "props": [
2960
+ {
2961
+ "name": "render",
2962
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogDescriptionState>",
2963
+ "required": false,
2964
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2965
+ }
2966
+ ]
2967
+ },
2968
+ {
2969
+ "name": "SheetFooter",
2970
+ "import": "import { SheetFooter } from '@ratio-ui/core'",
2971
+ "description": "The row of actions at the bottom of the sheet, aligned to the end.",
2972
+ "props": []
2973
+ },
2974
+ {
2975
+ "name": "SheetHeader",
2976
+ "import": "import { SheetHeader } from '@ratio-ui/core'",
2977
+ "description": "The title and description, stacked, with room on the right for the close button.",
2978
+ "props": []
2979
+ },
2980
+ {
2981
+ "name": "SheetTitle",
2982
+ "import": "import { SheetTitle } from '@ratio-ui/core'",
2983
+ "description": "The sheet's name, in the b4 style. It names the sheet for assistive technology.",
2984
+ "props": [
2985
+ {
2986
+ "name": "render",
2987
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogTitleState>",
2988
+ "required": false,
2989
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
2990
+ }
2991
+ ]
2992
+ },
2993
+ {
2994
+ "name": "SheetTrigger",
2995
+ "import": "import { SheetTrigger } from '@ratio-ui/core'",
2996
+ "description": "The button that opens the sheet.",
2997
+ "props": [
2998
+ {
2999
+ "name": "render",
3000
+ "type": "ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogTriggerState>",
3001
+ "required": false,
3002
+ "description": "Allows you to replace the component's HTML element with a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
3003
+ }
3004
+ ]
3005
+ }
3006
+ ]
3007
+ },
2148
3008
  {
2149
3009
  "name": "SideNav",
2150
3010
  "import": "import { SideNav } from '@ratio-ui/core'",