gridsmith-ui 0.29.0 → 0.30.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 (103) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +5 -1
  3. package/dist/DropdownMenu-DqT7ZQ0D.d.ts +39 -0
  4. package/dist/charts.d.ts +5 -0
  5. package/dist/charts.js +5 -3
  6. package/dist/charts.js.map +1 -1
  7. package/dist/chat.js +5 -5
  8. package/dist/{chunk-EIWXVICU.js → chunk-6A5RHW2W.js} +3 -3
  9. package/dist/{chunk-EIWXVICU.js.map → chunk-6A5RHW2W.js.map} +1 -1
  10. package/dist/{chunk-XVB55M3N.js → chunk-72SDAX54.js} +2 -2
  11. package/dist/chunk-72SDAX54.js.map +1 -0
  12. package/dist/{chunk-OYNUU346.js → chunk-7QLXYRPD.js} +3 -12
  13. package/dist/chunk-7QLXYRPD.js.map +1 -0
  14. package/dist/chunk-F2BNJSON.js +1348 -0
  15. package/dist/chunk-F2BNJSON.js.map +1 -0
  16. package/dist/{chunk-GXMTS3YD.js → chunk-GQVBZ4GW.js} +54 -16
  17. package/dist/chunk-GQVBZ4GW.js.map +1 -0
  18. package/dist/{chunk-2ROVDVLZ.js → chunk-INGF2CPW.js} +4 -23
  19. package/dist/chunk-INGF2CPW.js.map +1 -0
  20. package/dist/{chunk-66JJCQU3.js → chunk-JTU7OBYJ.js} +5 -3
  21. package/dist/chunk-JTU7OBYJ.js.map +1 -0
  22. package/dist/{chunk-DQCHCLEJ.js → chunk-KTPGMJUK.js} +6 -6
  23. package/dist/{chunk-DQCHCLEJ.js.map → chunk-KTPGMJUK.js.map} +1 -1
  24. package/dist/{chunk-TJEV7YAY.js → chunk-L6HWACK7.js} +7 -7
  25. package/dist/chunk-L6HWACK7.js.map +1 -0
  26. package/dist/chunk-LJYSVZMI.js +25 -0
  27. package/dist/chunk-LJYSVZMI.js.map +1 -0
  28. package/dist/{chunk-YUTF2X6B.js → chunk-NI5542ZM.js} +42 -38
  29. package/dist/chunk-NI5542ZM.js.map +1 -0
  30. package/dist/chunk-NKUCS67T.js +13 -0
  31. package/dist/chunk-NKUCS67T.js.map +1 -0
  32. package/dist/{chunk-TRDXWHD2.js → chunk-NLXQ323J.js} +6 -6
  33. package/dist/chunk-NLXQ323J.js.map +1 -0
  34. package/dist/{chunk-ZI3OQDET.js → chunk-OIOII5L6.js} +93 -4
  35. package/dist/chunk-OIOII5L6.js.map +1 -0
  36. package/dist/{chunk-R4GXKH4U.js → chunk-Q36JVJX4.js} +52 -93
  37. package/dist/chunk-Q36JVJX4.js.map +1 -0
  38. package/dist/{chunk-FD4YED7O.js → chunk-RHWTMTU5.js} +3 -3
  39. package/dist/{chunk-FD4YED7O.js.map → chunk-RHWTMTU5.js.map} +1 -1
  40. package/dist/chunk-TYNDXMBV.js +105 -0
  41. package/dist/chunk-TYNDXMBV.js.map +1 -0
  42. package/dist/{chunk-OPFJEL3Z.js → chunk-UNS4TGGK.js} +110 -20
  43. package/dist/chunk-UNS4TGGK.js.map +1 -0
  44. package/dist/chunk-Z4MCD73B.js +316 -0
  45. package/dist/chunk-Z4MCD73B.js.map +1 -0
  46. package/dist/{chunk-CRQ6KCRD.js → chunk-ZHUVLBCC.js} +7 -4
  47. package/dist/chunk-ZHUVLBCC.js.map +1 -0
  48. package/dist/data-grid.d.ts +26 -7
  49. package/dist/data-grid.js +179 -57
  50. package/dist/data-grid.js.map +1 -1
  51. package/dist/editor-mentions.js +3 -3
  52. package/dist/editor.js +5 -5
  53. package/dist/{feather-FNBQ74RR.js → feather-XFFEFLTV.js} +5 -3
  54. package/dist/feather-XFFEFLTV.js.map +1 -0
  55. package/dist/{heroicons-DODCSYWD.js → heroicons-L6DOHBEA.js} +5 -3
  56. package/dist/heroicons-L6DOHBEA.js.map +1 -0
  57. package/dist/icons.d.ts +10 -1
  58. package/dist/icons.js +1 -1
  59. package/dist/index.d.ts +174 -50
  60. package/dist/index.js +810 -1731
  61. package/dist/index.js.map +1 -1
  62. package/dist/kanban.d.ts +26 -8
  63. package/dist/kanban.js +203 -79
  64. package/dist/kanban.js.map +1 -1
  65. package/dist/{lucide-ZWE3EPGP.js → lucide-JOORDTO6.js} +5 -3
  66. package/dist/lucide-JOORDTO6.js.map +1 -0
  67. package/dist/{phosphor-DIHPA4AJ.js → phosphor-TKGDYNGR.js} +5 -3
  68. package/dist/phosphor-TKGDYNGR.js.map +1 -0
  69. package/dist/resizable.js +12 -11
  70. package/dist/resizable.js.map +1 -1
  71. package/dist/roots.css +14 -8
  72. package/dist/{table-utils-BkjNEWts.d.ts → table-utils-BXkP-2Hx.d.ts} +8 -5
  73. package/dist/{tabler-D3VYTSGI.js → tabler-YGETLYRU.js} +5 -3
  74. package/dist/tabler-YGETLYRU.js.map +1 -0
  75. package/dist/tokens.css +14 -0
  76. package/dist/{use-pagination-BxcRlNQG.d.ts → use-pagination-QykGSREv.d.ts} +7 -0
  77. package/dist/utils.js.map +1 -1
  78. package/dist/virtualized.d.ts +1 -1
  79. package/dist/virtualized.js +10 -9
  80. package/dist/virtualized.js.map +1 -1
  81. package/dist-static/tokens.css +14 -0
  82. package/package.json +1 -1
  83. package/dist/chunk-2ROVDVLZ.js.map +0 -1
  84. package/dist/chunk-66JJCQU3.js.map +0 -1
  85. package/dist/chunk-BRAXJEEP.js +0 -117
  86. package/dist/chunk-BRAXJEEP.js.map +0 -1
  87. package/dist/chunk-CRQ6KCRD.js.map +0 -1
  88. package/dist/chunk-GXMTS3YD.js.map +0 -1
  89. package/dist/chunk-OPFJEL3Z.js.map +0 -1
  90. package/dist/chunk-OYNUU346.js.map +0 -1
  91. package/dist/chunk-QM4SJKWV.js +0 -51
  92. package/dist/chunk-QM4SJKWV.js.map +0 -1
  93. package/dist/chunk-R4GXKH4U.js.map +0 -1
  94. package/dist/chunk-TJEV7YAY.js.map +0 -1
  95. package/dist/chunk-TRDXWHD2.js.map +0 -1
  96. package/dist/chunk-XVB55M3N.js.map +0 -1
  97. package/dist/chunk-YUTF2X6B.js.map +0 -1
  98. package/dist/chunk-ZI3OQDET.js.map +0 -1
  99. package/dist/feather-FNBQ74RR.js.map +0 -1
  100. package/dist/heroicons-DODCSYWD.js.map +0 -1
  101. package/dist/lucide-ZWE3EPGP.js.map +0 -1
  102. package/dist/phosphor-DIHPA4AJ.js.map +0 -1
  103. package/dist/tabler-D3VYTSGI.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,84 @@
1
1
  # gridsmith-ui changelog
2
2
 
3
+ ## Unreleased (0.30.0)
4
+
5
+ **Added.**
6
+ - **Days a picker can't choose, beyond `min` and `max`.** DatePicker and DateRangePicker take `disabledDates`: a function of the day, or weekdays as a shorthand (`[0, 6]` for weekends; the `DisabledDates` type). An excluded day can't be chosen by a click, the keyboard or typing, a range's ends are never excluded days though the range may span them, and DatePicker's Today is unavailable when today can't be chosen.
7
+ - **DateRangePicker's range can be typed.** Its field holds the start's and the end's day, month and year, typed as DatePicker's are; the chevron opens the calendar. A range is taken once both ends are whole, valid and in order. At phone width its panel lines up with its field, the field's width, one month filling it with days a comfortable tap, its quick ranges one row of chips that scrolls sideways, and its footer with Clear always on the screen.
8
+ - **A consent notice on Alert.** `consent` gives an Alert two choices of equal weight (accept and reject analytics cookies, say), which stay two buttons at phone width; the notice is a region named by its title, never an alert, and is never pinned over the page. `confirmation` replaces the choices with what the person chose, announced and focused. Equal weight is the default and the better practice; `primary` on one choice draws it as the primary button for a page that insists (the `AlertConsentChoice` type).
9
+ - **A card moves without dragging.** Each KanbanBoard card has a ⋯ button in its top right, outside the card's own button, whose menu lists the columns it can go to ("Move to Done") and its place in its own ("Move up", "Move down"); it shows without hover on a touch screen. A move from the menu goes through `onCardMove` as a drop does, so a page that refuses it puts the card back.
10
+ - **A card's own actions.** `cardActions` gives a card's ⋯ menu the page's actions (open, edit, copy, delete), with the board's Move among them as a submenu where the page puts it, or at their end; on a read-only board the menu holds the actions alone.
11
+ - **Submenus in DropdownMenu.** An entry with `items` (the `DropdownMenuSubmenu` type) opens a submenu, one level deep: beside the menu on a desktop, where → or Enter opens it, its first item focused, and ← or Escape closes it, focus back on its entry, and a hover opens it too; on a phone its items take the menu's place under a Back item. Home and End now go to the menu's first and last items.
12
+ - **A column's work-in-progress limit.** `KanbanColumn.limit` draws the column's count against it ("4/3"), and a column past it is marked by a warning icon and by its count's words for a screen reader ("4 cards, over the limit of 3"), never by colour alone. A limit informs and never refuses a drop.
13
+ - **Tabs that activate by Enter.** `activation="manual"`: the arrows move focus among the tabs, and Enter or Space selects one, for tabs whose panels load or navigate. The default still selects as focus moves.
14
+ - **The Gantt shades days.** `shadeDays` (a non-working day, a holiday) passes to its TimeViewport, at the zooms that draw days.
15
+ - **A selected Tag.** `selected` draws a Tag chosen, as a filter chip, and reports it (`aria-pressed`).
16
+ - **A range of rows in one go.** In a selectable Table, Shift with a click on a row's checkbox, or with Space on it, selects every row between the last one chosen and this one, in the order the rows are drawn; a plain click still picks one. Checkbox's `onChange` passes on whether Shift was held (`onChange(checked, { shiftKey })`).
17
+ - **An editable DataGrid shows which cells edit, and each kind edits with its own control.** A pencil shows on an editable cell while it is hovered or focused; a calculated or locked cell (`meta.editable: false`) shows none and is marked read-only. A number edits in a NumberInput, a date in a DatePicker, a choice in a Dropdown of the column's `meta.options` (`meta.kind: "choice"`; each option a `DataGridOption`, from `gridsmith-ui/data-grid`), a yes or no in a Checkbox (the `boolean` kind, found from the values), and text in a text Input; each saves once (Enter, a pick, or leaving the editor), and Escape leaves the cell as it was. A picker's popup is part of its editor, so opening it doesn't end the edit, and while a cell is edited its keys are its editor's. The editor sits inside its cell, so no column moves and the row keeps its height while it is open: an editable column of numbers, dates or choices is at least as wide as its editor.
18
+ - **The carousel's stop and play button.** An auto-playing Carousel has a quiet button to stop and start it in its top-left corner, first in its Tab order: a 24 px target, its icon on a faint see-through backdrop that reads on a photo too. Keyboard focus coming to any part of it stops auto-play, which stays stopped until the person presses play; the pointer over it, a drag or focus in a slide pause it; and it never starts by itself for a person who has asked for reduced motion. It takes an `aria-label`, each slide is named by its place ("3 of 10"), only the slide in view can be reached, and a move the person makes is announced. Auto-play goes on from the last slide to the first.
19
+ - **Icons `play` and `pause`,** in every pack.
20
+
21
+ **Fixed.**
22
+ - **A person's reduced motion is kept in generated apps.** The library zeroes its motion tokens when the person asks for reduced motion, but a theme's stylesheet declared them again after it, so the theme's speeds won. The generated stylesheet now ends with its own reduced-motion block, and the library's block outranks a stylesheet from an earlier version and a token written on the page's root.
23
+ - **Typing a date or a time.** A part (day, month, year; hour, minute) keeps focus until the person moves on, with Tab, an arrow or a separator (`/`, `-`, `.` or a space for a date; `:`, `.` or a space for a time), and a digit the part can't take starts the next. A part come back to by Tab, an arrow or a click is typed afresh. Typing 07, Tab, 12 had given the day 07 and a blank month, and 09, Tab, 20 a blank minute: the Tab after a full part skipped the next one. Typing the digits alone still works.
24
+ - **NotificationCenter groups its rows by their date,** not the words in their time: "2 days ago" sat under Today. A row's time is a RelativeTime; a row whose `timestamp` is text alone can't be dated, so it goes under Earlier.
25
+ - **NumberInput is a spin button with its value and limits,** and Home and End go to its limits where it has them (they stay the caret's keys where it hasn't).
26
+ - **A pinned page header no longer covers the control in focus.** A collapsed PageHeader pinned over the page took no room from the scroller, so a control reached with Tab, or brought into view by a link to it, could stop under the bar. The scroller's top scroll padding now follows the bar, inside the shell and on the window outside one, and the shell's outer box can no longer be scrolled by focus.
27
+ - **Alert's urgency matches its message.** An error or a warning announces at once (`role="alert"`), a success or information politely (`role="status"`). Its Dismiss is the library's icon Button, with its tooltip and a 24 px target.
28
+ - **A control that removes itself hands focus on.** After an Alert's Dismiss or a Tag's remove, focus moves to the same control in the item before, else to the item before, else after, else to the group, never to the page.
29
+ - **A clickable, removable Tag has two controls side by side.** Its own button and its remove button were one inside the other; now they are siblings, the whole chip still answers a press, and the chip draws the focus outline.
30
+ - **A label-less control keeps the name the page gives it.** An `aria-label` now reaches the control of a Checkbox, RadioGroup, Rating, PinInput, NumberInput, DatePicker, TimePicker, DateRangePicker (its name, then the range), ColorPicker (its group), FileUpload (its input) and TreeView (with `aria-labelledby` too); it had dropped on the way, leaving the control unnamed.
31
+ - **TimePicker's columns say what they are.** Each is a listbox (Hours, Minutes, AM or PM) whose options report which is chosen; the choice had been marked by its look alone.
32
+ - **A picker in a sheet or a dialog gives focus back.** A press outside a popup that leaves focus on nothing, inside a modal (the phone's date sheet, a Drawer), now gives focus back to the field the popup opened from.
33
+ - **Table's first load draws skeleton rows,** at phone and desktop width, as many as a page shows, where it drew a spinner; a refresh keeps its rows, marks the table busy and draws a thin moving line along its top edge (still, under reduced motion).
34
+ - **Every sortable column shows that it sorts,** in Table and DataGrid alike: a double chevron in the muted text colour on each sortable header, and on the sorted one a single chevron in the accent pointing its way, its label in full colour. In a right-aligned column the indicator sits before the label, so the label ends where the column's values end. Table showed nothing until a column was sorted, and DataGrid's chevrons were too faint to read (1.6:1); they now meet the 3:1 an icon marking a control needs.
35
+ - **Cut text has a keyboard and a tap route to its full text.** Text cut to fit (TextOverflow, a table's cut cell, a long email address) is a Tab stop that draws the focus outline, its full text shown on keyboard focus and at once on a tap as well as on hover; inside a control (a button, a link, a row that opens on a click, a grid's cell) it adds no stop. A table's cut email address had only a native title, which no keyboard or tap reaches.
36
+ - **CodeBlock's copy button shows the copy icon,** where it showed a folder.
37
+ - **A flow open in two tabs no longer has them undo each other.** `useFlowSteps` keeps the step on screen, so "Save and continue later" and a return resume there, but it also wrote it whenever the stored progress changed, another tab's change included. Two tabs on different steps each wrote their own step back over the other's for as long as both stayed open, and "Save and continue later" in one could resume at the other's step. A tab now keeps its step only when it shows one: on arriving at a step and on each move.
38
+
39
+ ### Upgrade note for agents (0.29.1 → 0.30.0)
40
+
41
+ Upgrade `gridsmith-ui` to `^0.30.0` (an app pinned to `^0.29.0` never reaches it), then report the new version as `ui_version`: the rules and guidance that need 0.30.0 are served from then on. An app's code needs no change and its theme's stylesheet no regenerating; what to check:
42
+ - **A test that finds a success or information Alert with `getByRole('alert')`.** Those are `role="status"` now; only an error or a warning is an alert. Find them with `getByRole('status')`, or by their title's text.
43
+ - **A test that clicks a time in TimePicker's panel as a button.** Its times are options in listboxes: `getByRole('option', { name: '09' })`.
44
+ - **A test that finds a clickable Tag's chip as the button.** The button is the Tag's label, a `<button>` inside the chip: `getByRole('button', { name: 'Design' })`.
45
+ - **A test that types into a NumberInput as a textbox.** It is a spin button: `getByRole('spinbutton', { name })`.
46
+ - **A test that waits for Table's spinner.** A first load draws skeleton rows and says "Loading table data" to a screen reader; a refresh marks the table `aria-busy`.
47
+ - **A test that finds a Carousel's slide by "Slide 3 of 10".** A slide is a group named "3 of 10", and only the slide in view is reachable.
48
+ - **A test that finds a table's long email address by its title.** It has none: its full address is the cell's tooltip, reached by hover, Tab or a tap.
49
+ - **An editable DataGrid.** A column of numbers, dates, or yeses and noes now edits with its kind's control; a column with a fixed set of values edits with a Dropdown once its `meta` gives `kind: "choice"` and the `options`, and edits as text until then (a development build says so). A test that typed into a text Input in every cell finds the kind's control instead.
50
+ - **NotificationCenter rows with text timestamps.** A row whose `timestamp` is text ("2 days ago") can't be dated, so it goes under Earlier: give it the time itself (an ISO string, a Date or milliseconds), and the row shows it as a RelativeTime under its day's group.
51
+ - **An auto-playing Carousel.** Give it an `aria-label` naming the set. It now goes on from its last slide to its first, looping or not.
52
+ - **Code that reads a menu's entries.** `DropdownMenuEntry` now includes `DropdownMenuSubmenu`, an entry with `items` and no `onClick`: code that calls `onClick` on every entry that isn't a divider narrows with `"items" in entry` first. Code that only builds menus needs nothing.
53
+ - **A test that looks for a sort indicator only on the sorted column.** Every sortable header now has one; the sorted one is marked `data-sort="asc"` or `"desc"`, the others `"none"`, and the header's `aria-sort` still says the order.
54
+ - **An editable DataGrid's column widths.** An editable column of numbers, dates or choices is at least as wide as its editor (9 to 11.5 rem), so a grid laid out to the pixel may need a little more room or a `meta.width`.
55
+ - **Types that grew.** A `Notification`'s `timestamp` is a `TimeValue` (a string, a Date or milliseconds), `ColumnKind` adds `"choice"` and `"boolean"`, and a Checkbox's `onChange` passes a second argument, `{ shiftKey }`. Code that reads a timestamp as text, switches over every kind, or calls a Checkbox's `onChange` itself is the code to check.
56
+
57
+ ## 0.29.1 — 9 October 2026
58
+
59
+ **Fixed.**
60
+ - **A ribbon Launcher no longer sits under the toasts** (Sprint 21.6 session B, A7 and its review). A ribbon at the end of a side edge, or on the bottom edge, now claims the corner it sits by, as a bubble or pill does, so the ToastProvider's stack lands above it. A mid-edge bottom ribbon holds the whole bottom edge (both corners and the centre), and a mid-edge side ribbon holds its side, so a tall stack passes beside it. Measured in Chrome before the fix, a bottom-right toast covered 90 × 13 px of a bottom ribbon and 13 × 44 px of a side one. The claim follows the ribbon when its `container` changes, and every launcher's claim now counts the safe area it sits inside. Nothing changes in an app's code.
61
+ - **Bottom-centre toasts move out of the way too.** The `bottom-center` position read no inset, so a mid-edge bottom ribbon sat under it; it now reads `--ds-toast-inset-bottom-center` (one spacing step by default), as the corners read theirs.
62
+ - **Toasts keep their gutter beside a claimed side.** Beside a docked column or an open side panel, a corner stack keeps a gutter at the window's other edge: below about 870 px with the assistant's panel open, it had reached the edge. A centred toast now centres in the room the two sides leave, where it could reach under an open side panel, and never leaves the screen.
63
+ - **An open non-modal Drawer keeps the toasts off it.** A side panel (`modal={false}`, such as the shell's assistant) now holds its side while open, so toasts slide past it as they do past a docked AsidePanel; a bottom one lifts them above itself. Every side claim (this one, AsidePanel's and a side-anchored CornerPanel's) stops where a toast at its narrowest would leave the window, so on a narrow window the stack overlaps the panel's outer part instead of leaving the screen.
64
+ - **A non-modal left Drawer becomes the bottom sheet on a phone.** It became a full-width side panel there, with nothing to keep focus in it, while a right or bottom one became the modal sheet. Now every non-modal drawer does, whatever its side, and so does an `AsidePanel side="left"` collapsed on a phone; a modal left drawer (the navigation convention) is still a side panel, and `mobile` still chooses.
65
+ - **One tooltip per control, never two.** Since round 15 an icon Button stepped its own tooltip aside inside a Tooltip; now every tooltip a component draws on its own does: cut-off text in a Badge, Tag, Tabs or TextOverflow, a RelativeTime's date, an avatar's name, a panel's own buttons. It steps aside only where a Tooltip that can show covers it, so a Tooltip around a component that passes no props on still leaves the component's own. A `title` never becomes the browser's second tooltip beside ours: a Tooltip drops the title of the element or Button it wraps, and an icon Button with an aria-label shows its title's text in its one tooltip. Nothing changes in an app's code.
66
+ - **The page's padding follows the Launcher however the slot wraps it.** AppShell read the corner to clear from the `launcher` slot's element, so a Fragment or an app's own component around the Launcher read as bottom-right. The Launcher now tells the shell its corner itself (`registerLauncher` on the shell's context; a ribbon still takes none).
67
+ - **The Calendar's corners keep their keyboard outline whole** (Sprint 25.27). The month's day in a bottom corner, week view's last 23:00 and the year view's December now round their corner as the card does, so their outline follows the curve instead of being cut off at it. Nothing changes at rest. The Calendar's events keep their look, flush with the card's and week strips' edges, so their outline can be cut where they meet an edge; the rest of it shows, as WCAG AA asks.
68
+ - **Week view's bars from the week's first day end on their last day.** A bar starting in the first column ran a divider too far for each extra day, and a week-long bar ran 6–12 px past the strip.
69
+ - **Narrow week view brings the keyboard's event into view whole.** Where the week scrolls sideways, an event or hour reached with Tab partly past the edge now scrolls fully in, clear of the strip's fade; the hours strip draws its edge fade again, which a style of its own had replaced.
70
+ - **The compact Calendar's month buttons keep room for their outline at any density.** At the Compact density the header's padding was 3.5 px; it is never under 4 px now. Nothing moves at the default density.
71
+ - **The library's code and its styles switch at the same width** (Sprint 25.28). `useBreakpoint(key)` was true at or below a breakpoint, in pixels, while Tailwind's `sm:`, `md:` and `lg:` start at it, in rem. So at exactly 640, 768, 1024 and 1280 px both claimed the width: at 640 an app with its assistant on a launcher showed no assistant button at all. And at a larger browser text size the styles' breakpoints moved while the code's didn't: at Large text, from 641 to 799 px, it showed two. The hook now asks Tailwind's own question, below the breakpoint, `(width < 40rem)` for `sm`, so each layout change happens at one width at any text size. What changes at exactly each width: at 640 the tablet layout (the menu drawer, side drawers, dialogs not full-screen, the month's bars, the Launcher); at 768 the sidebar's rail, tables as tables and responsive Tabs as their list, so an upright iPad matches the newer ones; at 1024 the full sidebar and a docked AsidePanel. `BreakpointWidthProvider`'s width counts the same way (below the breakpoint), so the builder's tablet preview at 768 shows the rail.
72
+ - **An app that calls `useBreakpoint` or `BreakpointWidthProvider` itself shifts at each breakpoint's exact width too,** and a test stub of `matchMedia` that reads `(max-width: …px)` no longer matches the hook's query, which is now `(width < Nrem)` (N the breakpoint's px ÷ 16).
73
+
74
+ ### Upgrade note for agents (0.29.0 → 0.29.1)
75
+
76
+ `^0.29.0` covers 0.29.1, so `npm update gridsmith-ui` brings it in. Nothing in an app's code changes, and the theme's tokens need no regenerating: the one new token, `--ds-toast-inset-bottom-center`, comes from `roots.css`. Four cases need a look:
77
+ - **Your own `useBreakpoint` calls.** `useBreakpoint(key)` is true below the breakpoint, where it was true at or below it, and counts in rem: at exactly 640, 768, 1024 or 1280 px (the default breakpoints, at the default text size) it is now false, as Tailwind's `sm:`, `md:`, `lg:` and `xl:` start there, and it moves with the browser's text size as they do. Code that pairs it with those classes now agrees with them and needs nothing; code that read it as "at or below" no longer includes the breakpoint's own width. `BreakpointWidthProvider`'s `width` counts the same way.
78
+ - **A test's `matchMedia` stub.** One that parses `(max-width: …px)` no longer matches the hook's query, `(width < Nrem)` with N the breakpoint's px ÷ 16 (`(width < 48rem)` for `md`): parse that form, and match when the stubbed width is under N × 16.
79
+ - **A test that finds a control by its title.** An icon-size Button with an `aria-label`, and an element or Button that a `Tooltip` wraps, no longer carry a `title` attribute (the Button's own tooltip shows the title's text instead), so `getByTitle` doesn't find them: find them by role and name (`getByRole('button', { name })`).
80
+ - **A hand-built `AppShellContextValue`.** The type gains `registerLauncher`, which is required: a test that mocks `useAppShell` with a whole value adds `registerLauncher: () => () => {}`. The context itself isn't exported, so nothing else builds one.
81
+
3
82
  ## 0.29.0 — 6 October 2026
4
83
 
5
84
  The deprecated props are removed before beta (Sprint 28.2, B8). Each was an alias kept for one version after its replacement shipped, and code that still writes one fails the type-check, as any unknown prop does: a 0.x minor release, 0.29.0, says so, and an app pinned to `^0.28.0` doesn't reach it by itself. The upgrade note at the end says what to write instead.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # gridsmith-ui
2
2
 
3
+ <!-- Last checked against the code on 13 October 2026 (gridsmith-ui 0.29.1). -->
4
+
3
5
  React components and charts with a CSS custom property theming engine, and 5 icon packs.
4
6
 
5
7
  ## Installation
@@ -86,6 +88,8 @@ import { preloadPack } from "gridsmith-ui/icons";
86
88
  preloadPack("lucide");
87
89
  ```
88
90
 
91
+ **Server-rendered apps (Next.js, Remix).** Wrap the app's top in `LocaleProvider`, from `gridsmith-ui`, with the page's language and the request's `Accept-Language` header: `<LocaleProvider lang={LANG} acceptLanguage={acceptLanguage}>`, the same `LANG` on `<html lang>`. The server then writes dates and numbers in the visitor's form, as the browser does, so the page hydrates without a mismatch. In Next.js, read the header in the root layout (`(await headers()).get("accept-language")`). A Vite app doesn't need it.
92
+
89
93
  ## Entry Points
90
94
 
91
95
  Every entry but `gridsmith-ui/utils` is client code, each file opening with `"use client"`. In Next.js (15 or later), a server component (a page with no `'use client'` of its own) renders the components with plain data: text, numbers, dates, elements. A function (`onClick`, `onChange`, a column's `render`) needs a client component of yours, and so does calling the library's hooks; a server page hands the library's constants and contexts on as they are, without reading them, and renders a compound part by its own name (`TabsList` for `Tabs.List`, `CollapsibleTrigger` for `Collapsible.Trigger`). On the server (a page, a route handler, a server action), call the helpers from `gridsmith-ui/utils`, passing them `requestLocale(LANG, acceptLanguage)` so their words read as the components' do; a Date is read there in the server's time zone, so hand `RelativeTime` and the Calendar the data for what the visitor reads in their own. Vite and Remix ignore the marker.
@@ -102,7 +106,7 @@ Every entry but `gridsmith-ui/utils` is client code, each file opening with `"us
102
106
  | `gridsmith-ui/virtualized` | `VirtualizedList` (requires `@tanstack/react-virtual`) |
103
107
  | `gridsmith-ui/resizable` | `ResizablePanelGroup`, `ResizablePanel`, `ResizableHandle`, `SplitView` (requires `react-resizable-panels`) |
104
108
  | `gridsmith-ui/icons` | `Icon`, `IconProvider`, `preloadPack`, `registerIconPack` — 5 icon packs with two-phase loading |
105
- | `gridsmith-ui/utils` | `cn()` — className merge utility (clsx + tailwind-merge) — and the helpers server code calls: the repeat rules, calendar days and date ranges, time zones, a chart's label kind, and `requestLocale()` for a request's locale. Nothing in it needs React or a browser |
109
+ | `gridsmith-ui/utils` | `cn()` — className merge utility (clsx + tailwind-merge) — the shared focus styles (`FOCUS_RING` and its kin) for an element of your own that takes focus, and the helpers server code calls: the repeat rules, calendar days and date ranges, time zones, a chart's label kind, and `requestLocale()` for a request's locale. Nothing in it needs React or a browser |
106
110
  | `gridsmith-ui/roots.css` | CSS custom property engine |
107
111
  | `gridsmith-ui/tokens.css` | Default preset token values |
108
112
  | `gridsmith-ui/tailwind.css` | Tailwind v4 `@theme` bindings for `--ds-*` tokens |
@@ -0,0 +1,39 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { IconName } from './icons.js';
4
+
5
+ type Side = "top" | "right" | "bottom" | "left";
6
+ type Align = "start" | "center" | "end";
7
+
8
+ interface DropdownMenuItem {
9
+ label: string;
10
+ icon?: IconName;
11
+ onClick: () => void;
12
+ variant?: "default" | "destructive";
13
+ disabled?: boolean;
14
+ }
15
+ interface DropdownMenuDivider {
16
+ type: "divider";
17
+ }
18
+ /**
19
+ * An entry that opens a submenu of its own (Sprint 25.8.7's look check: a card's Move among its other actions), one
20
+ * level deep. On a desktop it opens beside the menu: → or Enter opens it, its first item focused; ← or Escape closes it,
21
+ * focus back on this entry; a hover opens it too. On a phone its items take the menu's place, under a Back item.
22
+ */
23
+ interface DropdownMenuSubmenu {
24
+ label: string;
25
+ icon?: IconName;
26
+ items: (DropdownMenuItem | DropdownMenuDivider)[];
27
+ disabled?: boolean;
28
+ }
29
+ type DropdownMenuEntry = DropdownMenuItem | DropdownMenuDivider | DropdownMenuSubmenu;
30
+ interface DropdownMenuProps {
31
+ trigger: ReactNode;
32
+ items: DropdownMenuEntry[];
33
+ align?: Align;
34
+ className?: string;
35
+ container?: Element | DocumentFragment;
36
+ }
37
+ declare function DropdownMenu({ trigger, items, align, className, container }: DropdownMenuProps): react_jsx_runtime.JSX.Element;
38
+
39
+ export { type Align as A, type DropdownMenuSubmenu as D, type Side as S, type DropdownMenuEntry as a, DropdownMenu as b, type DropdownMenuDivider as c, type DropdownMenuItem as d };
package/dist/charts.d.ts CHANGED
@@ -397,6 +397,11 @@ interface ChartContainerProps {
397
397
  }
398
398
  declare function ChartContainer({ height, title, loading, emptyState, variant, children, className, }: ChartContainerProps): react_jsx_runtime.JSX.Element;
399
399
 
400
+ /**
401
+ * The theme's chart series colours, `count` of them in series order (repeating past the palette's length), read from the
402
+ * live tokens so they follow the theme and its mode; `soft` gives the lighter series. Before the theme's styles are
403
+ * committed, and on the server, it returns the fallback shades, then the theme's on the next layout.
404
+ */
400
405
  declare function useChartColors(count: number, soft?: boolean): string[];
401
406
 
402
407
  export { AreaChart, type AreaChartProps, type AreaDef, BarChart, type BarChartProps, type BarDef, type CategoryKind, ChartContainer, CompositeChart, type CompositeChartProps, FunnelChart, type FunnelChartProps, LineChart, type LineChartProps, type LineDef, PieChart, type PieChartProps, RadarChart, type RadarChartProps, type RadarDataKey, RadialBarChart, type RadialBarChartProps, SankeyChart, type SankeyChartProps, ScatterChart, type ScatterChartProps, type ScatterSeries, Sparkline, type SparklineProps, TreemapChart, type TreemapChartProps, type TreemapNode, type ValueFormat, categoryKind, useChartColors };
package/dist/charts.js CHANGED
@@ -1,8 +1,10 @@
1
1
  "use client";
2
- import { useFormatLocale, useHydrated, TITLE_TEXT, Skeleton } from './chunk-2ROVDVLZ.js';
3
- import { formatLocale, elementType } from './chunk-OYNUU346.js';
2
+ import { TITLE_TEXT, Skeleton } from './chunk-INGF2CPW.js';
3
+ import { useFormatLocale, useHydrated } from './chunk-LJYSVZMI.js';
4
+ import { formatLocale } from './chunk-7QLXYRPD.js';
5
+ import { elementType } from './chunk-NKUCS67T.js';
4
6
  import { mergeRefs } from './chunk-7Q46JXSK.js';
5
- import { cn, GLASS_SURFACE } from './chunk-66JJCQU3.js';
7
+ import { cn, GLASS_SURFACE } from './chunk-JTU7OBYJ.js';
6
8
  import { createContext, forwardRef, useContext, useRef, useState, useMemo, useLayoutEffect, useEffect, useId, useSyncExternalStore, Children, isValidElement } from 'react';
7
9
  import { ResponsiveContainer, BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Bar, LineChart as LineChart$1, Line, AreaChart as AreaChart$1, Area, PieChart as PieChart$1, Pie, Cell, ComposedChart, ScatterChart as ScatterChart$1, ZAxis, Scatter, RadarChart as RadarChart$1, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, RadialBarChart as RadialBarChart$1, RadialBar, FunnelChart as FunnelChart$1, Funnel, LabelList, Treemap, Sankey, Text, Legend, getNiceTickValues, Rectangle } from 'recharts';
8
10
  import { createPortal } from 'react-dom';