@sableui/react 0.14.0 → 0.16.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 (163) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/DESIGN.md +3 -1
  3. package/bin/sable-lint.mjs +5 -1
  4. package/dist/{_contract-CkqXDVww.d.ts → _contract-CDa9-NdB.d.ts} +47 -1
  5. package/dist/blocks/index.d.ts +12 -1
  6. package/dist/blocks/index.js +4 -0
  7. package/dist/blocks/message-reactions-block.js +87 -0
  8. package/dist/blocks/message-reactions-block.meta.js +23 -0
  9. package/dist/blocks/wallet-block.js +2 -2
  10. package/dist/components/app-shell/app-shell-frame.js +16 -0
  11. package/dist/components/app-shell/app-shell-main.js +12 -0
  12. package/dist/components/app-shell/app-shell.js +28 -4
  13. package/dist/components/app-shell/app-shell.meta.js +88 -4
  14. package/dist/components/area-chart/area-chart.meta.js +6 -1
  15. package/dist/components/attachment/attachment.meta.js +2 -1
  16. package/dist/components/attachment/attachment.variants.js +1 -1
  17. package/dist/components/badge/badge.meta.js +8 -1
  18. package/dist/components/badge/badge.variants.js +7 -7
  19. package/dist/components/banner/banner.meta.js +6 -1
  20. package/dist/components/banner/banner.variants.js +5 -5
  21. package/dist/components/bar-chart/bar-chart.meta.js +6 -1
  22. package/dist/components/bubble/bubble.js +105 -5
  23. package/dist/components/bubble/bubble.meta.js +42 -6
  24. package/dist/components/bubble/bubble.variants.js +1 -1
  25. package/dist/components/bubble/index.js +2 -0
  26. package/dist/components/caret-menu/caret-menu.js +5 -6
  27. package/dist/components/chat-composer/chat-composer.meta.js +2 -2
  28. package/dist/components/chat-composer/chat-composer.variants.js +1 -1
  29. package/dist/components/color-field/color-field.js +6 -8
  30. package/dist/components/combobox/combobox.js +27 -5
  31. package/dist/components/combobox/combobox.meta.js +10 -2
  32. package/dist/components/command-menu/command-menu.js +14 -3
  33. package/dist/components/command-menu/command-menu.meta.js +9 -1
  34. package/dist/components/date-picker/date-picker.meta.js +2 -2
  35. package/dist/components/feedback/feedback.js +9 -2
  36. package/dist/components/feedback/feedback.meta.js +8 -1
  37. package/dist/components/gradient-art/gradient-art.js +8 -5
  38. package/dist/components/gradient-art/gradient-art.meta.js +35 -2
  39. package/dist/components/input/input.meta.js +11 -3
  40. package/dist/components/input-group/input-group.js +1 -1
  41. package/dist/components/input-group/input-group.meta.js +1 -1
  42. package/dist/components/like-button/index.js +7 -0
  43. package/dist/components/like-button/like-button.js +89 -0
  44. package/dist/components/like-button/like-button.meta.js +132 -0
  45. package/dist/components/line-chart/line-chart.meta.js +6 -1
  46. package/dist/components/menu/menu.js +1 -1
  47. package/dist/components/meter/meter.js +53 -7
  48. package/dist/components/meter/meter.meta.js +51 -4
  49. package/dist/components/meter/meter.variants.js +43 -7
  50. package/dist/components/note/note.meta.js +5 -1
  51. package/dist/components/note/note.variants.js +4 -4
  52. package/dist/components/number-field/number-field.meta.js +2 -2
  53. package/dist/components/otp-field/otp-field.meta.js +2 -2
  54. package/dist/components/panel-group/panel-group.js +5 -8
  55. package/dist/components/pie-chart/pie-chart.meta.js +6 -1
  56. package/dist/components/radar-chart/radar-chart.meta.js +6 -1
  57. package/dist/components/radial-chart/radial-chart.meta.js +6 -1
  58. package/dist/components/reaction-picker/index.js +13 -0
  59. package/dist/components/reaction-picker/reaction-picker.js +104 -0
  60. package/dist/components/reaction-picker/reaction-picker.meta.js +156 -0
  61. package/dist/components/search-input/search-input.meta.js +11 -3
  62. package/dist/components/segmented-bar/segmented-bar.meta.js +8 -1
  63. package/dist/components/segmented-bar/segmented-bar.variants.js +7 -7
  64. package/dist/components/select/select.js +1 -1
  65. package/dist/components/select/select.meta.js +2 -2
  66. package/dist/components/sheet/sheet.js +5 -4
  67. package/dist/components/sidebar/sidebar.js +16 -1
  68. package/dist/components/sidebar/sidebar.meta.js +7 -2
  69. package/dist/components/sparkline/sparkline.meta.js +8 -1
  70. package/dist/components/status-dot/status-dot.meta.js +8 -1
  71. package/dist/components/status-dot/status-dot.variants.js +7 -7
  72. package/dist/components/stepper/stepper.meta.js +2 -1
  73. package/dist/components/stepper/stepper.variants.js +1 -1
  74. package/dist/components/table/table.meta.js +4 -4
  75. package/dist/components/textarea/textarea.js +5 -0
  76. package/dist/components/textarea/textarea.meta.js +3 -3
  77. package/dist/components/toast/toast.js +25 -13
  78. package/dist/components/toast/toast.meta.js +7 -1
  79. package/dist/file-hashes.json +112 -100
  80. package/dist/foundations/accessibility.js +5 -0
  81. package/dist/foundations/motion.js +21 -1
  82. package/dist/icon-sets/reicon.js +11 -1
  83. package/dist/index.d.ts +425 -84
  84. package/dist/index.js +11 -1
  85. package/dist/lib/chart/types.js +8 -8
  86. package/dist/lib/chart/use-arrival.js +5 -4
  87. package/dist/lib/field-shell.js +1 -1
  88. package/dist/lib/highlight-glide.js +6 -6
  89. package/dist/lib/scrub-gesture.js +6 -6
  90. package/dist/lib/taxonomy.js +2 -0
  91. package/dist/lib/use-caret-trigger.js +11 -18
  92. package/dist/lib/use-match-highlight.js +69 -0
  93. package/dist/lib/use-smooth-wheel.js +10 -8
  94. package/dist/motion/Burst.js +79 -0
  95. package/dist/motion/index.js +2 -0
  96. package/dist/presets/index.d.ts +13 -2
  97. package/dist/presets/index.js +17 -1
  98. package/dist/styles.css +241 -0
  99. package/dist/{theme-provider-CdSicAad.d.ts → theme-provider-BydfM5Z0.d.ts} +1 -1
  100. package/dist/themes/_contract.js +14 -0
  101. package/dist/themes/atelier/atelier.css +18 -0
  102. package/dist/themes/atelier/index.d.ts +1 -1
  103. package/dist/themes/contrast.js +48 -1
  104. package/dist/themes/ember/ember.css +18 -0
  105. package/dist/themes/ember/index.d.ts +1 -1
  106. package/dist/themes/index.d.ts +2 -2
  107. package/dist/themes/meridian/index.d.ts +1 -1
  108. package/dist/themes/meridian/meridian.css +18 -0
  109. package/dist/themes/poppy/index.d.ts +1 -1
  110. package/dist/themes/poppy/poppy.css +18 -0
  111. package/dist/themes/prism/index.d.ts +1 -1
  112. package/dist/themes/prism/prism.css +18 -0
  113. package/dist/themes/sable/index.d.ts +1 -1
  114. package/dist/themes/sable/sable.css +18 -0
  115. package/dist/themes/verdant/index.d.ts +1 -1
  116. package/dist/themes/verdant/verdant.css +18 -0
  117. package/llms/blocks/message-reactions-block.md +116 -0
  118. package/llms/blocks/wallet-block.md +5 -4
  119. package/llms/components/app-shell.md +72 -3
  120. package/llms/components/area-chart.md +1 -1
  121. package/llms/components/attachment.md +1 -1
  122. package/llms/components/badge.md +1 -1
  123. package/llms/components/banner.md +1 -1
  124. package/llms/components/bar-chart.md +1 -1
  125. package/llms/components/bubble.md +17 -7
  126. package/llms/components/chat-composer.md +1 -1
  127. package/llms/components/combobox.md +2 -1
  128. package/llms/components/command-menu.md +3 -2
  129. package/llms/components/date-picker.md +1 -1
  130. package/llms/components/feedback.md +2 -1
  131. package/llms/components/gradient-art.md +28 -1
  132. package/llms/components/input-group.md +1 -1
  133. package/llms/components/input.md +4 -2
  134. package/llms/components/like-button.md +91 -0
  135. package/llms/components/line-chart.md +1 -1
  136. package/llms/components/meter.md +33 -4
  137. package/llms/components/note.md +1 -1
  138. package/llms/components/number-field.md +1 -1
  139. package/llms/components/otp-field.md +1 -1
  140. package/llms/components/pie-chart.md +1 -1
  141. package/llms/components/radar-chart.md +1 -1
  142. package/llms/components/radial-chart.md +1 -1
  143. package/llms/components/reaction-picker.md +115 -0
  144. package/llms/components/search-input.md +20 -2
  145. package/llms/components/segmented-bar.md +1 -1
  146. package/llms/components/select.md +1 -1
  147. package/llms/components/sidebar.md +3 -2
  148. package/llms/components/sparkline.md +1 -1
  149. package/llms/components/status-dot.md +1 -1
  150. package/llms/components/stepper.md +1 -1
  151. package/llms/components/table.md +2 -2
  152. package/llms/components/textarea.md +2 -2
  153. package/llms/components/toast.md +2 -1
  154. package/llms/foundations/accessibility.md +1 -0
  155. package/llms/foundations/motion.md +18 -2
  156. package/llms/reference/token-intent.md +22 -2
  157. package/llms/reference/utilities.md +9 -3
  158. package/llms-full.txt +596 -61
  159. package/llms.txt +10 -7
  160. package/package.json +1 -1
  161. package/registry.json +968 -68
  162. package/scripts/lint-rules.mjs +6 -3
  163. package/targets/ios.json +18 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,136 @@
1
1
  # What's new
2
2
 
3
+ ## 0.16.0
4
+
5
+ Five pieces of the calm, product-chrome look that AI and B2B tools share:
6
+ tick-bar budgets, pastel art, filled fields, an inset frame and a dither
7
+ texture. Each is told through the theme, so every preset can wear it.
8
+
9
+ - **New: segmented meters.** `<Meter segments={40} />` draws the bar as a row
10
+ of ticks — a spend limit, credits left, a lead score, months of data — read
11
+ at a glance. Any reading above `min` shows at least one tick, and when the
12
+ value changes the fill runs through the ticks from the edge it is leaving.
13
+ Size the count to the track (about one tick per 8px) so a tick stays taller
14
+ than it is wide. `shape="ring"` ignores it.
15
+ - **New: `GradientArt intensity="soft"`.** The same art as a pastel of itself:
16
+ lifted and quieted, bright in dark mode too. It is a stage for floating
17
+ chips, logo tiles and card header bands, not a surface for text.
18
+ - **New: the field tokens, `field` and `field-border`.** Every text field —
19
+ Input, Textarea, Select, Combobox, SearchInput, NumberField, OTPField,
20
+ DatePicker, InputGroup, ChatComposer and CommandMenuTrigger — now paints
21
+ this pair, outlined on the card by default exactly as before. A theme fills
22
+ them all at once with `--theme-field: var(--muted)` and
23
+ `--theme-field-border: transparent`. Canvas has a matching **Fields**
24
+ control (Preset / Outline / Filled), and presets may set both hooks.
25
+ A hand-built field wearing `border-field-border` is caught by
26
+ `lookalike-control` like one wearing `border-input`.
27
+ - **New: `AppShell frame="inset"`.** From `lg`, the rail and a small gap sit on
28
+ one ground and main becomes a rounded, bordered panel that scrolls inside
29
+ itself while the rail stays put. The Sidebar in the slot drops its border
30
+ and fills the row, and its footer rule spans the content column rather than
31
+ running out into the gap; below `lg` nothing changes. The ground is a new derived
32
+ token, `shell-ground` — one step off the rail, so the panel reads as lifted
33
+ even in themes whose rail is the page colour; tune it with
34
+ `--theme-shell-ground`. Inset moves the desktop scroll from `window` to
35
+ `#main-content`, so scroll listeners (restoration, scroll-to-top, scrollspy)
36
+ must target `main`.
37
+ - **New utility: `texture-dither`.** A faint field of tiny squares that
38
+ dissolves, painted in `border-strong` so it follows every theme — decoration
39
+ for behind an empty state or a page header. `--dither-fade`,
40
+ `--dither-size` and `--dither-color` tune it.
41
+ - **Status and tone colours at graphic strength.** Meters, segmented bars,
42
+ status dots, chart series and the soft tints behind badges, banners and
43
+ notes were painted with the label fills — dark enough to carry white text,
44
+ so in light mode they read brown, forest green and plum. They now read new
45
+ `-mark` tokens (`success-mark`, `tone-warning-mark`, `destructive-mark`,
46
+ `tone-info-mark`, `tone-1/2/3-mark`): the same hue lifted and saturated,
47
+ held to a 2:1 visibility floor on the page instead of a text ratio. Every
48
+ bundled theme tunes its own in both modes, and keeps its character —
49
+ Ember's olive, Poppy's pastels and Atelier's grey tone stay theirs; a
50
+ preset sets `--theme-*-mark` or gets one derived from its fill. Labels are
51
+ unchanged: solid badges, banners and buttons keep their fills, and text
52
+ keeps `-text`. `markFor` (from `@sableui/react/presets`) derives a mark the
53
+ same way, and Canvas moves the marks with any tone it generates.
54
+ - **Canvas: a brand colour no longer flattens the tones.** Info is treated
55
+ as the status it is: it leans toward the brand within the blues instead of
56
+ being repainted (Brand only made it the brand, so a green brand's info read
57
+ as success; the schemes spun it anywhere). A status also keeps 25° off the
58
+ brand's own hue when its band allows, so a green brand gets a teal success
59
+ rather than a second copy of itself. And Brand only is a family you can tell
60
+ apart — a deep brand, a lighter neighbour and a muted sibling — instead of
61
+ one hue in four near-identical depths.
62
+ - **Table:** `numeric` on TableHead and TableCell is now documented.
63
+ - **New: search matches light up.** Combobox and CommandMenu paint the typed
64
+ text in each row's label, on a tint of the theme's `highlight`. It uses the CSS
65
+ Custom Highlight API, so rows get no `<mark>` wrappers and their accessible
66
+ names stay whole; browsers without it simply show no paint. Combobox lights
67
+ only what was typed, not a label a pick filled in. `highlightMatches={false}`
68
+ turns it off on either.
69
+ - **New hook: `useMatchHighlight(query, { selector })`.** The same paint for a
70
+ results list you filter yourself, such as a SearchInput over a table: it
71
+ returns a callback ref for the results container.
72
+ - **Textarea:** text you are editing no longer rewraps above the caret when
73
+ the field sits inside a `text-pretty` or `text-balance` block
74
+ (`text-wrap-style: stable`). ChatComposer gets the same fix.
75
+
76
+ ## 0.15.0
77
+
78
+ Motion with some life in it: small things that turn on now land like events,
79
+ told through the theme rather than one-off styles, and chat reactions that
80
+ answer you the way a messaging app does.
81
+
82
+ - **New: `LikeButton`.** A pressed-or-not toggle for a like, a star or a
83
+ save, with its count. Pressing it on fills the glyph, which blooms past
84
+ its size and settles while a ring opens and dots spray out in the theme's
85
+ palette families, and the count rolls up a digit; pressing it off is quiet.
86
+ It shows the viewer's like at once and goes back to `count` exactly
87
+ whenever `count` changes, so pass the server's total. `icon`, `tone` (a
88
+ palette family) and `label` make it a star or a save; `motion="subtle"`
89
+ keeps the fill and drops the flourish.
90
+ - **New recipe: `motion-bloom`.** A small glyph landing in its on state grows
91
+ from nothing to 1.2 and settles. Remount the glyph (a changing `key`) to
92
+ replay it; `--motion-bloom-delay` lets a ring lead. Expressive.
93
+ - **New: `<Burst>`.** The follow-through behind a bloom — a disc that hollows
94
+ into a ring, then dots thrown outward — coloured by palette families, so it
95
+ re-themes. Plays when its `trigger` changes, never on mount, and renders
96
+ nothing below full motion.
97
+ - **Toast: a status glyph instead of an accent bar.** A typed toast now leads
98
+ with the same glyph and ink as Note and InlineStatus (success, info,
99
+ warning, and the destructive glyph for errors), and it blooms in once the
100
+ card has landed — and again in the spinner's place when a loading toast
101
+ settles. The coloured bar down the leading edge told the type by colour
102
+ alone. A stacked deck now springs as cards step back and fan out; leaving
103
+ toasts still take the standard curve.
104
+ - **Feedback:** the chosen rating mark blooms as it is picked.
105
+ - **Icons: `replay` and `motion`.** A single circular arrow for watching
106
+ something again from its start (`refresh` reads as fetching again), and
107
+ a play mark in a dashed ring for a motion or playback setting (`sparkles`
108
+ reads as AI).
109
+ - **New: `ReactionPicker` and `MenuReactions`.** The quick row of emoji you
110
+ react to a message with. `ReactionPicker` is a toolbar for a popover or a
111
+ hover bar; `MenuReactions` is the same row as checkbox menu items, for the
112
+ top of the menu a message opens on right click or long press. The emoji
113
+ bloom in one after another as it opens and magnify under the pointer.
114
+ - **Bubble reactions celebrate.** When a `BubbleReaction` becomes the
115
+ reader's own — pressed, or added already on to a row that has rendered —
116
+ the emoji blooms and copies of it spray out, and the count rolls. Taking
117
+ a reaction back is quiet, and the reactions a page loads with stay still.
118
+ New `label` names a pill ("Party, 3"); new `BubbleReactionAdd` is the
119
+ smiley pill that opens a picker. `BubbleReactions` now hides itself while
120
+ empty, so render it for every message: staying mounted is how the first
121
+ reaction still celebrates. A Bubble shows a focus ring when it is made
122
+ focusable as a menu trigger.
123
+ - **New block: `MessageReactionsBlock`.** A thread you can react to from a
124
+ right-click / long-press menu or the add-reaction pill.
125
+ - **`<Burst>`: `particle` and `playOnMount`.** Throw copies of a glyph
126
+ instead of dots (no ring, travelling upright), and play once on mount for
127
+ a glyph that exists because of the action.
128
+ - **Icon: `emoji`.** A smiley, for an add-reaction control.
129
+ - **Motion guidance:** amplitude scales with smallness (a glyph travels far, a
130
+ surface barely), a moment is staged (main object first, follow-through
131
+ after), and only a person's own choice is celebrated. In `sable docs
132
+ motion` and DESIGN.md.
133
+
3
134
  ## 0.14.0
4
135
 
5
136
  Motion that is crafted by default and calm on request, and the first pieces for agent products.
package/DESIGN.md CHANGED
@@ -481,6 +481,8 @@ Motion is a first-class token layer, not an afterthought. **Crafted by default,
481
481
 
482
482
  **The principle behind the craft: one thing travels; two things do not blink.** When a selection, a value or a mark changes in place, move the element that represents it. And finish the story — a refused answer shakes *and* says why, a success confirms where it happened, an armed action asks again before it acts.
483
483
 
484
+ **Amplitude scales with smallness.** Motion felt lifeless when one amplitude served everything: the 4% a dialog needs is invisible on a 16px icon, so a heart turning on read as a colour swap. Small glyphs that a person turns on now travel far — grow from nothing past their size and settle — while surfaces keep their 0.96 → 1 and presses their 0.97. A moment is staged rather than flashed: the main object lands, then its follow-through (LikeButton's ring opens, the heart blooms 90ms later, dots are thrown after it). And only a person's own choice is celebrated, only at full motion: a burst on a system event or on load is a notification wearing confetti. The look of all of it still comes from the theme — palette families, tone glyphs, the radius scale — never from one-off values.
485
+
484
486
  **Two levels.** *Essential* motion answers the user — colour and focus feedback, press, overlay arrival, disclosure height, loaders, chart hover. *Expressive* motion narrates — glides, rolling numbers, drawn ticks, shakes, staggers, chart arrival and morph, scroll reveals, spring overshoot. Expressive durations are multiplied by a second factor, `--motion-expressive`, which `data-motion="subtle"` sets to 0 (and which takes the overshoot out of `ease-spring`). So the policy ladder is `full` (default: both) → `subtle` (essential only) → `off` (nothing moves; every state still changes). Set it app-wide on `<MotionProvider policy>`, for a region with `<MotionScope level>`, or per component with the `motion` prop that every component owning a flourish takes. Scopes and props can only turn motion **down** — `full` on a component means "inherit", so nothing below the provider can override a reader's reduce-motion setting.
485
487
 
486
488
  **Three parts:**
@@ -492,7 +494,7 @@ Motion is a first-class token layer, not an afterthought. **Crafted by default,
492
494
 
493
495
  2. **Recipes** (`styles.css`) — named, reusable behaviours a component opts into with one class instead of a bespoke transition string:
494
496
  - `motion-interactive` (hover/focus colour + transform transitions, including `rotate`) · `motion-press` (tactile press settle) · `motion-fade` (scrims) · `motion-pop` (anchored/centred surfaces — gentle spring on open) · `motion-slide-{up,down,left,right}` (directional fade-slide) · `motion-disclosure` (height) · `motion-draw` (SVG line-art draw-on — a `pathLength="1"` path inside a `<Reveal>` draws in when scrolled into view; stagger siblings via `--motion-draw-delay`) · loaders `motion-spin`/`motion-pulse`/`motion-shimmer`/`motion-dot-bounce`. Enter/exit recipes drive off Base UI's `data-[starting-style]`/`data-[ending-style]`.
495
- - Expressive: `motion-glide` (one indicator travelling to the selection) · `motion-shake` (a refused answer, driven by `useShake()`) · `motion-tick` (a mark that draws itself in) · `motion-appear` (a small control mounting via `@starting-style`) · `motion-arrive` (items a user action adds to a list, staggered) · `motion-size` (easing between intrinsic sizes via `interpolate-size`). `NumberTicker` is the rolling-number primitive.
497
+ - Expressive: `motion-glide` (one indicator travelling to the selection) · `motion-shake` (a refused answer, driven by `useShake()`) · `motion-tick` (a mark that draws itself in) · `motion-appear` (a small control mounting via `@starting-style`) · `motion-arrive` (items a user action adds to a list, staggered) · `motion-size` (easing between intrinsic sizes via `interpolate-size`) · `motion-bloom` (a small glyph landing in its on state, 0 → 1.2 → 1). `NumberTicker` is the rolling-number primitive; `<Burst>` is the follow-through behind a bloom (a ring and thrown dots in the theme's palette families), used by `LikeButton`.
496
498
 
497
499
  3. **Policy** — `prefers-reduced-motion` and `<MotionProvider>` collapse `--motion-scale` to 0 in one place: transitions resolve instantly, loaders hold their first frame. `subtle` zeroes `--motion-expressive` instead, so essential motion keeps playing. In-app, `useMotion()` reads and sets the policy; `useMotionLevel(motion)` resolves a component's own prop against everything around it, and JS-driven flourishes gate on its `expressive`.
498
500
 
@@ -44,7 +44,11 @@ if (!existsSync(registryPath)) {
44
44
  }
45
45
  const REGISTRY = JSON.parse(readFileSync(registryPath, "utf8"))
46
46
 
47
- const semanticTokens = new Set((REGISTRY.tokens?.semantic ?? []).map((t) => t.name))
47
+ // Derived colours (`tone-warning-mark`, `field`, `shell-ground`…) are real tokens
48
+ // too: every theme gets them, so a utility naming one is not a typo.
49
+ const semanticTokens = new Set(
50
+ [...(REGISTRY.tokens?.semantic ?? []), ...(REGISTRY.tokens?.derived ?? [])].map((t) => (typeof t === "string" ? t : t.name)),
51
+ )
48
52
  // Motion token NAMES are reassembled from the registry's durations/easings lists.
49
53
  // A test in the package asserts this reconstruction equals _contract.ts exactly,
50
54
  // so the consumer linter can never validate against a drifted vocabulary.
@@ -147,6 +147,52 @@ declare const ART_TOKENS: readonly ["art-1", "art-2", "art-3", "art-4", "art-5",
147
147
  * 2 doubles it, for a theme that wants every colour to read as its own.
148
148
  */
149
149
  declare const PALETTE_TOKENS: readonly ["palette-harmony"];
150
+ /**
151
+ * The text field's surface — derived like the edges, with a `--theme-*` hook.
152
+ *
153
+ * - `field` is the fill of every text-entry field (Input, Textarea, Select,
154
+ * Combobox, SearchInput, InputGroup, ChatComposer…). It defaults to `card`.
155
+ * - `field-border` is that field's outline. It defaults to `input`.
156
+ *
157
+ * Together they are the one choice a theme makes about how a field reads:
158
+ * outlined on the card (the default), or filled — `--theme-field: var(--muted)`
159
+ * with `--theme-field-border: transparent` — the borderless grey well some
160
+ * product UIs use for every input. Every field wears the pair through
161
+ * lib/field-shell, so the choice reaches them all at once instead of being
162
+ * restyled per control.
163
+ */
164
+ declare const FIELD_TOKENS: readonly ["field", "field-border"];
165
+ /**
166
+ * The ground of an inset app frame — derived, with a `--theme-*` hook.
167
+ *
168
+ * `<AppShell frame="inset">` lifts main into a rounded panel and paints the
169
+ * rail and the gap around it as one surface. That surface cannot simply be
170
+ * `sidebar`: most themes paint the rail the page's own colour, so the panel
171
+ * sat on a ground of the same colour and only its hairline told them apart.
172
+ * `shell-ground` is one small step off the rail toward its ink, so the panel
173
+ * reads as lifted in every theme, and a theme sets `--theme-shell-ground` for
174
+ * a stronger or tinted ground.
175
+ */
176
+ declare const APP_SHELL_TOKENS: readonly ["shell-ground"];
177
+ /**
178
+ * Status and tone colours at GRAPHIC strength — derived, with `--theme-*` hooks.
179
+ *
180
+ * Each status colour already comes in two: the fill, which carries a label
181
+ * (dark enough for white ink at 4.5:1 in light mode), and `-text`, the colour
182
+ * as ink on a surface. Neither is right for a mark that carries no text — a
183
+ * meter's fill, a segmented bar, a status dot, a chart series, the soft tint
184
+ * behind a badge. Those were painted with the label fill, so in light mode
185
+ * every one of them was a deep brown, forest green or plum: muddy, and 5–9:1
186
+ * against the page when a graphic needs far less.
187
+ *
188
+ * A mark is the same hue lifted and saturated. It is held to a VISIBILITY floor
189
+ * of 2:1 against `background` and `card` (NON_TEXT_HARD_FLOOR, the `graphic`
190
+ * tier in contrast.ts), not to a text ratio: every mark that uses it sits
191
+ * beside text that states the same thing (a meter's reading, a dot's label).
192
+ * A graphic that is the ONLY carrier of its meaning still owes WCAG 1.4.11's
193
+ * 3:1 — use the fill, or add the text.
194
+ */
195
+ declare const MARK_TOKENS: readonly ["destructive-mark", "success-mark", "tone-warning-mark", "tone-info-mark", "tone-1-mark", "tone-2-mark", "tone-3-mark"];
150
196
  /**
151
197
  * UI type tiers (mode-independent) — the chrome typography register.
152
198
  *
@@ -193,4 +239,4 @@ interface ThemeManifest {
193
239
  };
194
240
  }
195
241
 
196
- export { ART_TOKENS as A, DENSITY_TOKENS as D, MOTION_TOKENS as M, PALETTE_TOKENS as P, RADIUS_TOKENS as R, SEMANTIC_TOKENS as S, type ThemeManifest as T, WORKSPACE_SURFACE_TOKENS as W, type ThemeMode as a, type DensityToken as b, type MotionToken as c, RHYTHM_TOKENS as d, type RadiusToken as e, type RhythmToken as f, type SemanticToken as g, TYPE_OVERLINE_TOKENS as h, TYPE_REFINEMENT_TOKENS as i, TYPE_SCALE_TOKENS as j, TYPE_UI_TOKENS as k, type TypeOverlineToken as l, type TypeRefinementToken as m, type TypeScaleToken as n, type TypeUiToken as o, type WorkspaceSurfaceToken as p };
242
+ export { APP_SHELL_TOKENS as A, DENSITY_TOKENS as D, FIELD_TOKENS as F, MARK_TOKENS as M, PALETTE_TOKENS as P, RADIUS_TOKENS as R, SEMANTIC_TOKENS as S, type ThemeManifest as T, WORKSPACE_SURFACE_TOKENS as W, type ThemeMode as a, ART_TOKENS as b, type DensityToken as c, MOTION_TOKENS as d, type MotionToken as e, RHYTHM_TOKENS as f, type RadiusToken as g, type RhythmToken as h, type SemanticToken as i, TYPE_OVERLINE_TOKENS as j, TYPE_REFINEMENT_TOKENS as k, TYPE_SCALE_TOKENS as l, TYPE_UI_TOKENS as m, type TypeOverlineToken as n, type TypeRefinementToken as o, type TypeScaleToken as p, type TypeUiToken as q, type WorkspaceSurfaceToken as r };
@@ -170,6 +170,17 @@ declare function MediaPlayerBlock({ className, framed, onPlayingChange, onPrevio
170
170
 
171
171
  declare const mediaPlayerBlockMeta: BlockMeta;
172
172
 
173
+ interface MessageReactionsBlockProps {
174
+ className?: string;
175
+ /** false drops the card's surface and border. Keep it framed on a muted page: incoming bubbles are the muted surface too. */
176
+ framed?: boolean;
177
+ /** Called when the reader adds or removes a reaction, with the message, the emoji and whether it is now on. */
178
+ onReact?: (messageId: string, emoji: string, on: boolean) => void;
179
+ }
180
+ declare function MessageReactionsBlock({ className, framed, onReact }: MessageReactionsBlockProps): react.JSX.Element;
181
+
182
+ declare const messageReactionsBlockMeta: BlockMeta;
183
+
173
184
  interface NewsletterBlockProps {
174
185
  className?: string;
175
186
  /** false drops the card's surface and border — for a footer or muted section that already frames it. */
@@ -408,4 +419,4 @@ declare function WalletBlock({ className, framed, onSend, onReceive }: WalletBlo
408
419
 
409
420
  declare const walletBlockMeta: BlockMeta;
410
421
 
411
- export { ApiKeysBlock, ApprovalBlock, ChatThreadBlock, CommandPaletteBlock, CtaBandBlock, DangerZoneBlock, DeviceComparisonBlock, EventCardBlock, FaqBlock, FeatureGridBlock, FeedbackFormBlock, GoalProgressBlock, HeroBlock, InviteTeamBlock, InvoiceHistoryBlock, KpiSparklineBlock, MediaPlayerBlock, NewsletterBlock, NotFoundBlock, NotificationListBlock, NotificationSettingsBlock, OrderTrackingBlock, PricingCardBlock, PricingTableBlock, ProductCardBlock, ProfileCardBlock, ProfileFormBlock, RevenueBreakdownBlock, SignInBlock, SignUpBlock, SiteFooterBlock, StatRowBlock, StepsBlock, TaskListBlock, TeamMembersBlock, TestimonialBlock, TrafficSourcesBlock, TrafficTrendBlock, UploadListBlock, VerifyCodeBlock, WalletBlock, apiKeysBlockMeta, approvalBlockMeta, chatThreadBlockMeta, commandPaletteBlockMeta, ctaBandBlockMeta, dangerZoneBlockMeta, deviceComparisonBlockMeta, eventCardBlockMeta, faqBlockMeta, featureGridBlockMeta, feedbackFormBlockMeta, goalProgressBlockMeta, heroBlockMeta, inviteTeamBlockMeta, invoiceHistoryBlockMeta, kpiSparklineBlockMeta, mediaPlayerBlockMeta, newsletterBlockMeta, notFoundBlockMeta, notificationListBlockMeta, notificationSettingsBlockMeta, orderTrackingBlockMeta, pricingCardBlockMeta, pricingTableBlockMeta, productCardBlockMeta, profileCardBlockMeta, profileFormBlockMeta, revenueBreakdownBlockMeta, signInBlockMeta, signUpBlockMeta, siteFooterBlockMeta, statRowBlockMeta, stepsBlockMeta, taskListBlockMeta, teamMembersBlockMeta, testimonialBlockMeta, trafficSourcesBlockMeta, trafficTrendBlockMeta, uploadListBlockMeta, verifyCodeBlockMeta, walletBlockMeta };
422
+ export { ApiKeysBlock, ApprovalBlock, ChatThreadBlock, CommandPaletteBlock, CtaBandBlock, DangerZoneBlock, DeviceComparisonBlock, EventCardBlock, FaqBlock, FeatureGridBlock, FeedbackFormBlock, GoalProgressBlock, HeroBlock, InviteTeamBlock, InvoiceHistoryBlock, KpiSparklineBlock, MediaPlayerBlock, MessageReactionsBlock, NewsletterBlock, NotFoundBlock, NotificationListBlock, NotificationSettingsBlock, OrderTrackingBlock, PricingCardBlock, PricingTableBlock, ProductCardBlock, ProfileCardBlock, ProfileFormBlock, RevenueBreakdownBlock, SignInBlock, SignUpBlock, SiteFooterBlock, StatRowBlock, StepsBlock, TaskListBlock, TeamMembersBlock, TestimonialBlock, TrafficSourcesBlock, TrafficTrendBlock, UploadListBlock, VerifyCodeBlock, WalletBlock, apiKeysBlockMeta, approvalBlockMeta, chatThreadBlockMeta, commandPaletteBlockMeta, ctaBandBlockMeta, dangerZoneBlockMeta, deviceComparisonBlockMeta, eventCardBlockMeta, faqBlockMeta, featureGridBlockMeta, feedbackFormBlockMeta, goalProgressBlockMeta, heroBlockMeta, inviteTeamBlockMeta, invoiceHistoryBlockMeta, kpiSparklineBlockMeta, mediaPlayerBlockMeta, messageReactionsBlockMeta, newsletterBlockMeta, notFoundBlockMeta, notificationListBlockMeta, notificationSettingsBlockMeta, orderTrackingBlockMeta, pricingCardBlockMeta, pricingTableBlockMeta, productCardBlockMeta, profileCardBlockMeta, profileFormBlockMeta, revenueBreakdownBlockMeta, signInBlockMeta, signUpBlockMeta, siteFooterBlockMeta, statRowBlockMeta, stepsBlockMeta, taskListBlockMeta, teamMembersBlockMeta, testimonialBlockMeta, trafficSourcesBlockMeta, trafficTrendBlockMeta, uploadListBlockMeta, verifyCodeBlockMeta, walletBlockMeta };
@@ -33,6 +33,8 @@ import { KpiSparklineBlock } from "./kpi-sparkline-block.js";
33
33
  import { kpiSparklineBlockMeta } from "./kpi-sparkline-block.meta.js";
34
34
  import { MediaPlayerBlock } from "./media-player-block.js";
35
35
  import { mediaPlayerBlockMeta } from "./media-player-block.meta.js";
36
+ import { MessageReactionsBlock } from "./message-reactions-block.js";
37
+ import { messageReactionsBlockMeta } from "./message-reactions-block.meta.js";
36
38
  import { NewsletterBlock } from "./newsletter-block.js";
37
39
  import { newsletterBlockMeta } from "./newsletter-block.meta.js";
38
40
  import { NotFoundBlock } from "./not-found-block.js";
@@ -99,6 +101,7 @@ export {
99
101
  InvoiceHistoryBlock,
100
102
  KpiSparklineBlock,
101
103
  MediaPlayerBlock,
104
+ MessageReactionsBlock,
102
105
  NewsletterBlock,
103
106
  NotFoundBlock,
104
107
  NotificationListBlock,
@@ -140,6 +143,7 @@ export {
140
143
  invoiceHistoryBlockMeta,
141
144
  kpiSparklineBlockMeta,
142
145
  mediaPlayerBlockMeta,
146
+ messageReactionsBlockMeta,
143
147
  newsletterBlockMeta,
144
148
  notFoundBlockMeta,
145
149
  notificationListBlockMeta,
@@ -0,0 +1,87 @@
1
+ "use client";
2
+
3
+ // src/blocks/message-reactions-block.tsx
4
+ import { useState } from "react";
5
+ import { Bubble, BubbleReaction, BubbleReactionAdd, BubbleReactions } from "../components/bubble/index.js";
6
+ import { Card, CardContent, CardFooter } from "../components/card/index.js";
7
+ import {
8
+ ContextMenu,
9
+ ContextMenuContent,
10
+ ContextMenuItem,
11
+ ContextMenuSeparator,
12
+ ContextMenuTrigger
13
+ } from "../components/context-menu/index.js";
14
+ import { Message } from "../components/message/index.js";
15
+ import { DEFAULT_REACTIONS, MenuReactions } from "../components/reaction-picker/index.js";
16
+ import { cn } from "../lib/cn.js";
17
+ import { jsx, jsxs } from "react/jsx-runtime";
18
+ var messages = [
19
+ { id: "m1", text: "The new onboarding flow is live on staging \u{1F680}", mine: false, reactions: { "\u{1F389}": 2, "\u{1F440}": 1 } },
20
+ { id: "m2", text: "Clicked through it twice. The progress bar is so smooth.", mine: true, reactions: {} },
21
+ { id: "m3", text: "Ship it Friday?", mine: false, reactions: { "\u{1F44D}": 1 } }
22
+ ];
23
+ var hint = "Right-click or long-press a message to react.";
24
+ var labels = {
25
+ ...Object.fromEntries(DEFAULT_REACTIONS.map((r) => [r.emoji, r.label])),
26
+ "\u{1F440}": "Eyes"
27
+ };
28
+ function MessageReactionsBlock({ className, framed = true, onReact }) {
29
+ const [mine, setMine] = useState({ m1: ["\u{1F389}"] });
30
+ const toggle = (messageId, emoji) => {
31
+ const on = !(mine[messageId] ?? []).includes(emoji);
32
+ setMine((prev) => {
33
+ const current = prev[messageId] ?? [];
34
+ const next = current.includes(emoji) ? current.filter((e) => e !== emoji) : [...current, emoji];
35
+ return { ...prev, [messageId]: next };
36
+ });
37
+ onReact?.(messageId, emoji, on);
38
+ };
39
+ return /* @__PURE__ */ jsxs(Card, { variant: framed ? "default" : "flat", className: cn("w-full max-w-sm", className), children: [
40
+ /* @__PURE__ */ jsx(CardContent, { className: "flex flex-col gap-3", children: messages.map((m) => {
41
+ const align = m.mine ? "end" : "start";
42
+ const chosen = mine[m.id] ?? [];
43
+ const counts = { ...m.reactions };
44
+ for (const emoji of chosen) counts[emoji] = counts[emoji] ?? 0;
45
+ const pills = Object.entries(counts).map(([emoji, others]) => ({
46
+ emoji,
47
+ active: chosen.includes(emoji),
48
+ count: others + (chosen.includes(emoji) ? 1 : 0)
49
+ }));
50
+ return /* @__PURE__ */ jsxs(Message, { messageId: m.id, align: m.mine ? "end" : void 0, children: [
51
+ /* @__PURE__ */ jsxs(ContextMenu, { children: [
52
+ /* @__PURE__ */ jsx(
53
+ ContextMenuTrigger,
54
+ {
55
+ render: /* @__PURE__ */ jsx(Bubble, { tabIndex: 0, variant: m.mine ? "primary" : void 0, align }),
56
+ children: m.text
57
+ }
58
+ ),
59
+ /* @__PURE__ */ jsxs(ContextMenuContent, { children: [
60
+ /* @__PURE__ */ jsx(MenuReactions, { selected: chosen, onSelect: (emoji) => toggle(m.id, emoji) }),
61
+ /* @__PURE__ */ jsx(ContextMenuSeparator, {}),
62
+ /* @__PURE__ */ jsx(ContextMenuItem, { children: "Reply" }),
63
+ /* @__PURE__ */ jsx(ContextMenuItem, { children: "Copy text" })
64
+ ] })
65
+ ] }),
66
+ /* @__PURE__ */ jsxs(BubbleReactions, { align, children: [
67
+ pills.map((p) => /* @__PURE__ */ jsx(
68
+ BubbleReaction,
69
+ {
70
+ emoji: p.emoji,
71
+ label: labels[p.emoji],
72
+ count: p.count,
73
+ active: p.active,
74
+ onClick: () => toggle(m.id, p.emoji)
75
+ },
76
+ p.emoji
77
+ )),
78
+ pills.length > 0 ? /* @__PURE__ */ jsx(BubbleReactionAdd, { selected: chosen, onSelect: (emoji) => toggle(m.id, emoji) }) : null
79
+ ] })
80
+ ] }, m.id);
81
+ }) }),
82
+ /* @__PURE__ */ jsx(CardFooter, { className: "text-caption text-muted-foreground", children: hint })
83
+ ] });
84
+ }
85
+ export {
86
+ MessageReactionsBlock
87
+ };
@@ -0,0 +1,23 @@
1
+ // src/blocks/message-reactions-block.meta.ts
2
+ var messageReactionsBlockMeta = {
3
+ name: "MessageReactionsBlock",
4
+ family: "Social & messaging",
5
+ kind: "card",
6
+ width: { min: "18rem", max: "24rem" },
7
+ summary: "A short thread you can react to: right-click or long-press a bubble for a menu with a row of quick reactions above Reply and Copy, or use the add-reaction pill under a message. A new reaction blooms in and sprays copies of its emoji.",
8
+ intent: "Reach for this when a conversation excerpt should be reactable \u2014 a team thread, a comment card, a support transcript. A read-only excerpt is ChatThreadBlock; a working chat with a composer and history is the ChatPanel page shell.",
9
+ notes: [
10
+ "Two ways in, on purpose: the right-click / long-press menu is fast once you know it is there, and the add-reaction pill is how a first-time reader finds reactions at all.",
11
+ "The quick row in the menu is MenuReactions (checkbox menu items) and the one in the pill's popover is ReactionPicker (a toolbar), so each is reachable by the keyboard model of the surface it sits in.",
12
+ "Only the reader's own reaction celebrates. Counts from other people roll quietly, and the reactions a page loads with never play.",
13
+ "The add-reaction pill appears only on messages that already have reactions; a message with none is reacted to from its menu, which keeps quiet rows quiet."
14
+ ],
15
+ a11y: [
16
+ "Each bubble is focusable, and its menu opens on right click, long press, Shift+F10 or the Menu key; its reactions are checkbox items reporting whether the reader has used each one.",
17
+ 'Each reaction pill is a toggle named by its meaning and count ("Party, 3") with `aria-pressed`; the emoji and the burst are hidden from assistive tech.',
18
+ "Under reduced motion the reaction appears and the count changes at once; nothing else is lost."
19
+ ]
20
+ };
21
+ export {
22
+ messageReactionsBlockMeta
23
+ };
@@ -11,6 +11,7 @@ var activity = [
11
11
  { title: "Refund", detail: "Order 1042 \xB7 Yesterday", amount: "-24.00", tone: "2" },
12
12
  { title: "Payout", detail: "To checking \xB7 Mon", amount: "-850.00", tone: "3" }
13
13
  ];
14
+ var TONE_MARK = { "1": "bg-tone-1-mark", "2": "bg-tone-2-mark", "3": "bg-tone-3-mark" };
14
15
  function WalletBlock({ className, framed = true, onSend, onReceive }) {
15
16
  return /* @__PURE__ */ jsxs(Card, { variant: framed ? "default" : "flat", className: cn("w-full max-w-xs", className), children: [
16
17
  /* @__PURE__ */ jsxs(CardHeader, { children: [
@@ -30,8 +31,7 @@ function WalletBlock({ className, framed = true, onSend, onReceive }) {
30
31
  "span",
31
32
  {
32
33
  "aria-hidden": true,
33
- className: "block size-8 rounded-full",
34
- style: { background: `oklch(from var(--tone-${item.tone}) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h)` }
34
+ className: cn("block size-8 rounded-full", TONE_MARK[item.tone])
35
35
  }
36
36
  ),
37
37
  title: item.title,
@@ -0,0 +1,16 @@
1
+ "use client";
2
+
3
+ // src/components/app-shell/app-shell-frame.tsx
4
+ import { createContext, useContext } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ var AppShellFrameContext = createContext(null);
7
+ function AppShellFrameProvider({ frame, children }) {
8
+ return /* @__PURE__ */ jsx(AppShellFrameContext.Provider, { value: frame, children });
9
+ }
10
+ function useAppShellFrame() {
11
+ return useContext(AppShellFrameContext);
12
+ }
13
+ export {
14
+ AppShellFrameProvider,
15
+ useAppShellFrame
16
+ };
@@ -0,0 +1,12 @@
1
+ "use client";
2
+
3
+ // src/components/app-shell/app-shell-main.tsx
4
+ import { useScrollActivity } from "../../lib/use-scroll-activity.js";
5
+ import { jsx } from "react/jsx-runtime";
6
+ function AppShellMain({ scrolls, ...props }) {
7
+ const trackScroll = useScrollActivity();
8
+ return /* @__PURE__ */ jsx("main", { ref: scrolls ? trackScroll : void 0, ...props });
9
+ }
10
+ export {
11
+ AppShellMain
12
+ };
@@ -1,6 +1,8 @@
1
1
  // src/components/app-shell/app-shell.tsx
2
2
  import { cn } from "../../lib/cn.js";
3
3
  import { AppShellDock } from "./app-shell-dock.js";
4
+ import { AppShellFrameProvider } from "./app-shell-frame.js";
5
+ import { AppShellMain } from "./app-shell-main.js";
4
6
  import { SkipLink } from "./skip-link.js";
5
7
  import { jsx, jsxs } from "react/jsx-runtime";
6
8
  function AppShell({
@@ -10,18 +12,27 @@ function AppShell({
10
12
  bottomNav,
11
13
  dock,
12
14
  overlays,
15
+ frame = "flush",
13
16
  className,
14
17
  children,
15
18
  ...rest
16
19
  }) {
20
+ const inset = frame === "inset";
17
21
  return /* @__PURE__ */ jsxs(
18
22
  "div",
19
23
  {
24
+ "data-frame": inset ? "inset" : void 0,
20
25
  className: cn(
21
26
  // `overflow-x-clip` (not `-hidden`) contains horizontal bleed WITHOUT
22
27
  // establishing a scroll container — `-hidden` forces `overflow-y: auto`,
23
28
  // which breaks `position: sticky` on a descendant Sidebar.
24
29
  "flex min-h-screen flex-col overflow-x-clip bg-background text-foreground",
30
+ // Inset, from lg: the shell IS the viewport and never scrolls — main
31
+ // does. The ground is the rail's surface, so the rail and the gap
32
+ // around the panel read as one plane. A page-surface rail (Sidebar
33
+ // surface="page") is page-coloured, so the ground follows it and the
34
+ // panel's border carries the lift.
35
+ inset && "lg:h-dvh lg:min-h-0 lg:bg-shell-ground lg:[&:has(>[data-app-shell-body]>[data-surface=page])]:bg-background",
25
36
  className
26
37
  ),
27
38
  ...rest,
@@ -29,16 +40,29 @@ function AppShell({
29
40
  /* @__PURE__ */ jsx(SkipLink, {}),
30
41
  banners ? /* @__PURE__ */ jsx("div", { className: "relative z-(--z-nav)", children: banners }) : null,
31
42
  header,
32
- /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1", children: [
33
- sidebar,
43
+ /* @__PURE__ */ jsxs("div", { "data-app-shell-body": inset ? "" : void 0, className: "flex min-h-0 flex-1", children: [
44
+ inset ? /* @__PURE__ */ jsx(AppShellFrameProvider, { frame: "inset", children: sidebar }) : sidebar,
34
45
  /* @__PURE__ */ jsx(
35
- "main",
46
+ AppShellMain,
36
47
  {
37
48
  id: "main-content",
49
+ scrolls: inset,
50
+ tabIndex: inset ? -1 : void 0,
38
51
  style: { containerType: "inline-size" },
39
52
  className: cn(
40
53
  "min-w-0 flex-1",
41
- bottomNav && "pb-[calc(var(--nav-height)+env(safe-area-inset-bottom,0px))] lg:pb-0"
54
+ bottomNav && "pb-[calc(var(--nav-height)+env(safe-area-inset-bottom,0px))] lg:pb-0",
55
+ // The inset panel. A spacing-step gap on the open sides (the rail
56
+ // is the leading one). Banners and the dock are in flow in the
57
+ // shell's column, so the bottom gap already sits above a dock.
58
+ // Sticky children stick to the panel's top, and the overflow
59
+ // clips them to its rounded corners. A resting surface: a hairline
60
+ // edge and the subtlest raise, never a floating shadow.
61
+ inset && cn(
62
+ "outline-none lg:m-2 lg:overflow-y-auto lg:overscroll-contain scrollbar-quiet",
63
+ "lg:rounded-lg lg:border lg:border-border lg:bg-background lg:shadow-sm",
64
+ sidebar && "lg:ms-0"
65
+ )
42
66
  ),
43
67
  children
44
68
  }