@sableui/react 0.17.0 → 0.19.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.
- package/AGENTS.md +10 -3
- package/CHANGELOG.md +235 -0
- package/README.md +11 -4
- package/bin/sable.mjs +266 -30
- package/dist/{_contract-CDa9-NdB.d.ts → _contract-DlklV0Im.d.ts} +2 -2
- package/dist/activity-DT8VK9fx.d.ts +25 -0
- package/dist/adapters/github.d.ts +41 -0
- package/dist/adapters/github.js +24 -0
- package/dist/blocks/index.d.ts +1 -1
- package/dist/components/activity-heatmap/activity-heatmap.js +420 -0
- package/dist/components/activity-heatmap/activity-heatmap.meta.js +142 -0
- package/dist/components/activity-heatmap/index.js +9 -0
- package/dist/components/avatar/avatar.js +16 -7
- package/dist/components/avatar/avatar.meta.js +43 -10
- package/dist/components/avatar/avatar.variants.js +26 -2
- package/dist/components/code-block/code-block.js +5 -3
- package/dist/components/code-block/code-block.meta.js +6 -0
- package/dist/components/collapsible/collapsible.js +6 -5
- package/dist/components/collapsible/collapsible.meta.js +14 -5
- package/dist/components/data-table/data-table.js +132 -45
- package/dist/components/data-table/data-table.meta.js +63 -4
- package/dist/components/date-picker/date-picker.js +13 -6
- package/dist/components/date-picker/date-picker.meta.js +22 -0
- package/dist/components/dialog/dialog.js +25 -5
- package/dist/components/dialog/dialog.meta.js +26 -5
- package/dist/components/media-image/media-image.js +3 -1
- package/dist/components/media-image/media-image.meta.js +15 -1
- package/dist/components/media-tile/media-tile.js +13 -1
- package/dist/components/media-tile/media-tile.meta.js +10 -0
- package/dist/components/menu/menu.js +2 -0
- package/dist/components/menu/menu.meta.js +2 -2
- package/dist/components/page-header/page-header.js +9 -4
- package/dist/components/page-header/page-header.meta.js +26 -1
- package/dist/components/panel-group/panel-group.js +17 -1
- package/dist/components/panel-group/panel-group.meta.js +43 -3
- package/dist/components/pill/pill.meta.js +15 -6
- package/dist/components/pill/pill.variants.js +1 -0
- package/dist/components/search-input/search-input.js +30 -5
- package/dist/components/search-input/search-input.meta.js +29 -4
- package/dist/components/section-header/section-header.js +27 -17
- package/dist/components/section-header/section-header.meta.js +27 -5
- package/dist/components/sheet/sheet.js +15 -3
- package/dist/components/sheet/sheet.meta.js +2 -2
- package/dist/components/side-nav/side-nav.variants.js +8 -3
- package/dist/components/sidebar/index.js +4 -0
- package/dist/components/sidebar/sidebar.js +90 -11
- package/dist/components/sidebar/sidebar.meta.js +38 -4
- package/dist/components/table/table.js +7 -3
- package/dist/components/tabs/tabs.js +10 -2
- package/dist/components/tabs/tabs.meta.js +4 -0
- package/dist/components/workspace/workspace.js +13 -3
- package/dist/components/workspace/workspace.meta.js +109 -1
- package/dist/file-hashes.json +94 -80
- package/dist/foundations/typography.js +13 -0
- package/dist/icon/Icon.js +21 -2
- package/dist/icon/index.d.ts +36 -1
- package/dist/icon/index.js +5 -0
- package/dist/icon/names.js +240 -0
- package/dist/icon-sets/reicon.js +41 -1
- package/dist/index.d.ts +540 -75
- package/dist/index.js +18 -1
- package/dist/layout/AspectBox.js +2 -5
- package/dist/layout/Bleed.js +10 -6
- package/dist/layout/Center.js +11 -3
- package/dist/layout/Columns.js +6 -3
- package/dist/layout/Grid.js +14 -6
- package/dist/layout/GridOverlay.js +7 -7
- package/dist/layout/Region.js +18 -3
- package/dist/layout/Row.js +27 -20
- package/dist/layout/Spacer.js +13 -2
- package/dist/layout/SplitPane.js +1 -1
- package/dist/layout/Stack.js +25 -18
- package/dist/layout/index.d.ts +57 -6
- package/dist/lib/chart/activity.js +32 -0
- package/dist/lib/chart/calendar.js +67 -0
- package/dist/lib/chart/chart-tooltip.js +2 -2
- package/dist/lib/chart/heat.js +55 -0
- package/dist/lib/cn.js +1 -0
- package/dist/lib/focus-return.js +53 -0
- package/dist/lib/taxonomy.js +1 -0
- package/dist/lib/use-media-query.js +41 -0
- package/dist/{meta-DX5r5wQL.d.ts → meta-Dfm9AExe.d.ts} +6 -0
- package/dist/patterns/dashboard-page.js +23 -17
- package/dist/patterns/index.d.ts +7 -2
- package/dist/patterns/index.js +5 -1
- package/dist/patterns/tracker-page.js +392 -0
- package/dist/patterns/tracker-page.meta.js +40 -0
- package/dist/presets/index.d.ts +1 -1
- package/dist/styles.css +301 -188
- package/dist/{theme-provider-BydfM5Z0.d.ts → theme-provider-BUBealk_.d.ts} +1 -1
- package/dist/themes/_contract.js +9 -1
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/ember/index.d.ts +1 -1
- package/dist/themes/index.d.ts +2 -2
- package/dist/themes/meridian/index.d.ts +1 -1
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/sable/index.d.ts +1 -1
- package/dist/themes/verdant/index.d.ts +1 -1
- package/llms/components/activity-heatmap.md +133 -0
- package/llms/components/avatar.md +32 -9
- package/llms/components/code-block.md +1 -0
- package/llms/components/collapsible.md +12 -4
- package/llms/components/data-table.md +40 -3
- package/llms/components/date-picker.md +15 -1
- package/llms/components/dialog.md +34 -3
- package/llms/components/media-image.md +12 -0
- package/llms/components/media-tile.md +21 -0
- package/llms/components/menu.md +1 -1
- package/llms/components/page-header.md +16 -1
- package/llms/components/panel-group.md +34 -2
- package/llms/components/pill.md +24 -5
- package/llms/components/search-input.md +37 -4
- package/llms/components/section-header.md +28 -4
- package/llms/components/sheet.md +2 -2
- package/llms/components/sidebar.md +17 -4
- package/llms/components/tabs.md +1 -0
- package/llms/components/workspace.md +81 -1
- package/llms/foundations/typography.md +4 -1
- package/llms/layout/aspect-box.md +1 -1
- package/llms/layout/bleed.md +1 -1
- package/llms/layout/center.md +1 -1
- package/llms/layout/columns.md +1 -1
- package/llms/layout/grid.md +1 -1
- package/llms/layout/region.md +1 -1
- package/llms/layout/row.md +9 -1
- package/llms/layout/spacer.md +1 -1
- package/llms/layout/split-pane.md +1 -1
- package/llms/layout/stack.md +1 -1
- package/llms/patterns/dashboard-page.md +18 -15
- package/llms/patterns/tracker-page.md +450 -0
- package/llms/reference/token-intent.md +3 -1
- package/llms/reference/utilities.md +4 -1
- package/llms/tasks/auditing-an-app.md +35 -1
- package/llms/tasks/building-a-page.md +3 -2
- package/llms/tasks/building-a-tool.md +101 -5
- package/llms/tasks/building-with-sable.md +11 -3
- package/llms/tasks/creating-a-preset.md +9 -0
- package/llms/tasks/getting-started.md +10 -3
- package/llms/tasks/using-presets.md +1 -2
- package/llms-full.txt +1215 -89
- package/llms.txt +11 -9
- package/package.json +7 -1
- package/registry.json +1021 -70
- package/scripts/adoption.mjs +252 -0
- package/scripts/agent-setup.mjs +110 -18
- package/scripts/app-css.mjs +129 -0
- package/scripts/grade.mjs +178 -2
- package/scripts/lint-rules.mjs +126 -0
- package/scripts/wiring.mjs +2 -1
- package/skills/entry/SKILL.md +1 -1
- package/skills/sable/SKILL.md +64 -6
- package/skills/sable/agents/openai.yaml +1 -1
- package/targets/ios.json +9 -0
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
useSyncExternalStore
|
|
8
8
|
} from "react";
|
|
9
9
|
import { cn } from "../../lib/cn.js";
|
|
10
|
-
import {
|
|
10
|
+
import { useBelowBreakpoint } from "../../lib/use-media-query.js";
|
|
11
|
+
import { Sheet, SheetContent, SheetTitle } from "../sheet/index.js";
|
|
12
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
13
|
var widthCache = /* @__PURE__ */ new Map();
|
|
12
14
|
var listeners = /* @__PURE__ */ new Set();
|
|
13
15
|
function clampWidth(value, min, max) {
|
|
@@ -64,10 +66,24 @@ function Panel({
|
|
|
64
66
|
label,
|
|
65
67
|
width,
|
|
66
68
|
scroll = true,
|
|
69
|
+
narrow = "dock",
|
|
70
|
+
open,
|
|
71
|
+
onOpenChange,
|
|
72
|
+
side = "right",
|
|
73
|
+
below = "lg",
|
|
67
74
|
className,
|
|
68
75
|
children,
|
|
69
76
|
...props
|
|
70
77
|
}) {
|
|
78
|
+
const isNarrow = useBelowBreakpoint(below);
|
|
79
|
+
if (narrow !== "dock" && isNarrow) {
|
|
80
|
+
if (narrow === "hidden") return null;
|
|
81
|
+
return /* @__PURE__ */ jsx(Sheet, { open: open ?? false, onOpenChange: (next) => onOpenChange?.(next), children: /* @__PURE__ */ jsxs(SheetContent, { side, className: "gap-0 p-0 [--sidebar-width:100%]", children: [
|
|
82
|
+
/* @__PURE__ */ jsx(SheetTitle, { className: "sr-only", children: label }),
|
|
83
|
+
children
|
|
84
|
+
] }) });
|
|
85
|
+
}
|
|
86
|
+
if (open === false) return null;
|
|
71
87
|
return /* @__PURE__ */ jsx(
|
|
72
88
|
"aside",
|
|
73
89
|
{
|
|
@@ -13,7 +13,13 @@ var panelGroupMeta = {
|
|
|
13
13
|
"tool",
|
|
14
14
|
"chrome",
|
|
15
15
|
"inspector",
|
|
16
|
-
"layout"
|
|
16
|
+
"layout",
|
|
17
|
+
"mobile",
|
|
18
|
+
"responsive",
|
|
19
|
+
"sheet",
|
|
20
|
+
"drawer",
|
|
21
|
+
"narrow",
|
|
22
|
+
"detail pane"
|
|
17
23
|
],
|
|
18
24
|
usage: {
|
|
19
25
|
description: "Use PanelGroup for the body of a tool workspace: fixed-width Panels docked left and/or right, a flexible Panel in the middle for the canvas, and a PanelResizer between them. Panels scroll internally and the group never scrolls. This is the workspace-register counterpart to SplitPane, which only switches between side-by-side and stacked at a breakpoint and cannot be resized.",
|
|
@@ -30,6 +36,14 @@ var panelGroupMeta = {
|
|
|
30
36
|
guidance: "do",
|
|
31
37
|
description: "Label every Panel \u2014 they are landmarks, and 'Layers' or 'Properties' is what a screen-reader user navigates by."
|
|
32
38
|
},
|
|
39
|
+
{
|
|
40
|
+
guidance: "do",
|
|
41
|
+
description: 'Decide what each docked Panel becomes on a phone with `narrow`: "sheet" for an inspector or a navigation rail the user opens on demand, "hidden" for chrome a phone does without. Drive a sheet\'s `open` from the same state that selects the record, so selecting a row opens the inspector at every width.'
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
guidance: "dont",
|
|
45
|
+
description: 'Render the same inspector twice, once in a Panel and once in a Sheet behind a hand-rolled viewport check. `narrow="sheet"` renders the content once, in whichever place applies.'
|
|
46
|
+
},
|
|
33
47
|
{
|
|
34
48
|
guidance: "dont",
|
|
35
49
|
description: "Let the group scroll. Only panels and the canvas scroll; a scrolling group means the shell lost its height constraint."
|
|
@@ -69,8 +83,8 @@ var panelGroupMeta = {
|
|
|
69
83
|
subcomponents: [
|
|
70
84
|
{
|
|
71
85
|
name: "Panel",
|
|
72
|
-
type:
|
|
73
|
-
description:
|
|
86
|
+
type: '{ label: string; width?: number; scroll?: boolean; narrow?: "dock" | "sheet" | "hidden"; open?: boolean; onOpenChange?: (open: boolean) => void; side?: "left" | "right"; below?: "sm" | "md" | "lg" | "xl" | "2xl" }',
|
|
87
|
+
description: 'A docked or flexible column. Below `below` (default lg, where Sidebar and BottomNav swap), `narrow` decides what it becomes: "dock" (default) stays docked, "sheet" renders the same content once inside a Sheet from `side` (default "right"; a panel before the canvas is "left"), titled by `label`, open while `open` is true and closed through `onOpenChange` (Esc, scrim, swipe), and "hidden" drops it. `open` defaults to true docked (false removes the column, so the canvas takes its width) and false as a sheet. Crossing the breakpoint remounts the content, so keep state that must survive it in the app. `label` names the landmark; omit `width` for the flexible centre. `scroll` defaults to true \u2014 set it false for a header + scrolling body + pinned footer, so the footer doesn\'t scroll away with the list. When you own the layout, give the header and footer shrink-0 and the middle min-h-0 flex-1 overflow-y-auto. overflow-y-auto only activates if every flex ancestor can shrink: a single missing min-h-0 anywhere up the chain makes the panel grow instead of scroll, and nothing reports it.'
|
|
74
88
|
},
|
|
75
89
|
{
|
|
76
90
|
name: "PanelResizer",
|
|
@@ -158,6 +172,32 @@ var panelGroupMeta = {
|
|
|
158
172
|
</Panel>
|
|
159
173
|
</PanelGroup>
|
|
160
174
|
)
|
|
175
|
+
}`
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
label: "Inspector that becomes a sheet on phones",
|
|
179
|
+
code: `function Body() {
|
|
180
|
+
const [selected, setSelected] = useState<string | null>(null)
|
|
181
|
+
return (
|
|
182
|
+
<PanelGroup>
|
|
183
|
+
<Panel label="Issues">
|
|
184
|
+
<div className="p-2">
|
|
185
|
+
<Button size="xs" variant="ghost" onClick={() => setSelected("AES-104")}>AES-104 Refine product page</Button>
|
|
186
|
+
</div>
|
|
187
|
+
</Panel>
|
|
188
|
+
<Panel
|
|
189
|
+
label="Issue details"
|
|
190
|
+
width={320}
|
|
191
|
+
narrow="sheet"
|
|
192
|
+
open={selected !== null}
|
|
193
|
+
onOpenChange={(open) => !open && setSelected(null)}
|
|
194
|
+
>
|
|
195
|
+
<PanelSection title={selected ?? ""}>
|
|
196
|
+
<PropertyRow label="Status"><Input defaultValue="In progress" /></PropertyRow>
|
|
197
|
+
</PanelSection>
|
|
198
|
+
</Panel>
|
|
199
|
+
</PanelGroup>
|
|
200
|
+
)
|
|
161
201
|
}`
|
|
162
202
|
},
|
|
163
203
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/components/pill/pill.meta.ts
|
|
2
2
|
var pillMeta = {
|
|
3
3
|
name: "Pill",
|
|
4
|
-
summary: 'A chip someone clicks: a filter, a tag choice, an option in a row. Always a button with aria-pressed. appearance="solid" (filters; selected lifts onto the primary surface) or "outline" (quiet chips that annotate content, like reactions); size md or sm. A static label is a Badge.',
|
|
4
|
+
summary: 'A chip someone clicks: a filter, a tag choice, an option in a row. Always a button with aria-pressed. appearance="solid" (filters; selected lifts onto the primary surface) or "outline" (quiet chips that annotate content, like reactions); size lg, md or sm. A static label is a Badge.',
|
|
5
5
|
status: "stable",
|
|
6
6
|
keywords: [
|
|
7
7
|
"pill",
|
|
@@ -22,6 +22,10 @@ var pillMeta = {
|
|
|
22
22
|
guidance: "do",
|
|
23
23
|
description: "Reflect the current choice with selected, so the active filter reads at a glance."
|
|
24
24
|
},
|
|
25
|
+
{
|
|
26
|
+
guidance: "do",
|
|
27
|
+
description: 'In a toolbar where filter chips share a row with a search field ("All 8 \xB7 Products 5 \xB7 Open source 2 \u2026 [Find a project]"), pair Pill size="lg" with SearchInput size="sm", and Button size="sm" for any action beside them. All three stand on --control-sm (32px, 28 compact), the rung the spacing doctrine gives toolbar controls, so the row is one height in either density. Pill md (28px) has no text field of its height in the page register, so md beside a field always reads as a mismatch.'
|
|
28
|
+
},
|
|
25
29
|
{
|
|
26
30
|
guidance: "dont",
|
|
27
31
|
description: "Do not use Pill as a static label. A pressable-looking chip that does nothing is the most common cause of dead clicks in a filter row \u2014 that is a Badge.",
|
|
@@ -33,14 +37,15 @@ var pillMeta = {
|
|
|
33
37
|
},
|
|
34
38
|
{
|
|
35
39
|
guidance: "dont",
|
|
36
|
-
description: 'Do not restyle a Pill with border or background classes to get a quieter chip. That is appearance="outline" (and size="sm" for a small one); an override is a look that exists nowhere else in the system.'
|
|
40
|
+
description: 'Do not restyle a Pill with border or background classes to get a quieter chip. That is appearance="outline" (and size="sm" for a small one); an override is a look that exists nowhere else in the system. Likewise never set h-* on a Pill to match a neighbour: pick the size that shares its rung.'
|
|
37
41
|
}
|
|
38
42
|
]
|
|
39
43
|
},
|
|
40
44
|
related: {
|
|
41
45
|
components: [
|
|
42
46
|
"Badge",
|
|
43
|
-
"ToggleGroup"
|
|
47
|
+
"ToggleGroup",
|
|
48
|
+
"SearchInput"
|
|
44
49
|
]
|
|
45
50
|
},
|
|
46
51
|
props: [
|
|
@@ -58,9 +63,9 @@ var pillMeta = {
|
|
|
58
63
|
},
|
|
59
64
|
{
|
|
60
65
|
name: "size",
|
|
61
|
-
type: '"md" | "sm"',
|
|
66
|
+
type: '"lg" | "md" | "sm"',
|
|
62
67
|
default: '"md"',
|
|
63
|
-
description: "
|
|
68
|
+
description: "lg sits on --control-sm (32px, 28 compact), the toolbar rung it shares with SearchInput sm and Button sm, for a filter row beside a search field. md sits on --control-xs (28px, 24 compact), the chip in a row of its own or among content. sm is a fixed 24px (the target floor, which density does not shrink), for chips under or inside other content."
|
|
64
69
|
},
|
|
65
70
|
{
|
|
66
71
|
name: "tone",
|
|
@@ -76,7 +81,7 @@ var pillMeta = {
|
|
|
76
81
|
],
|
|
77
82
|
variants: {
|
|
78
83
|
appearance: ["solid", "outline"],
|
|
79
|
-
size: ["md", "sm"],
|
|
84
|
+
size: ["lg", "md", "sm"],
|
|
80
85
|
selected: ["true", "false"],
|
|
81
86
|
tone: ["neutral", "red", "orange", "yellow", "green", "teal", "blue", "purple", "pink"]
|
|
82
87
|
},
|
|
@@ -143,6 +148,10 @@ var pillMeta = {
|
|
|
143
148
|
label: "Filter row",
|
|
144
149
|
code: "<Row gap={1}><Pill selected onClick={() => {}}>All</Pill><Pill onClick={() => {}}>Projects</Pill><Pill onClick={() => {}}>Reports</Pill></Row>"
|
|
145
150
|
},
|
|
151
|
+
{
|
|
152
|
+
label: "Filters with search",
|
|
153
|
+
code: 'const [filter, setFilter] = useState("all")\nconst [q, setQ] = useState("")\n\n<Row gap={2} align="center" justify="between" wrap>\n <Row gap={1}>\n <Pill size="lg" selected={filter === "all"} onClick={() => setFilter("all")}>All 8</Pill>\n <Pill size="lg" selected={filter === "products"} onClick={() => setFilter("products")}>Products 5</Pill>\n <Pill size="lg" selected={filter === "oss"} onClick={() => setFilter("oss")}>Open source 2</Pill>\n </Row>\n <SearchInput size="sm" value={q} onValueChange={setQ} placeholder="Find a project\u2026" aria-label="Find a project" className="max-w-64" />\n</Row>'
|
|
154
|
+
},
|
|
146
155
|
{
|
|
147
156
|
label: "Outline, small (reactions)",
|
|
148
157
|
code: '<Row gap={1}><Pill appearance="outline" size="sm" selected onClick={() => {}}>\u{1F389} 3</Pill><Pill appearance="outline" size="sm" onClick={() => {}}>\u{1F440}</Pill></Row>'
|
|
@@ -26,6 +26,7 @@ var pillVariants = cva(
|
|
|
26
26
|
outline: "border"
|
|
27
27
|
},
|
|
28
28
|
size: {
|
|
29
|
+
lg: "h-(--control-sm) px-4 py-0 text-label-lg [--sable-optical-pad:0px]",
|
|
29
30
|
md: "h-(--control-xs) px-3 py-0 [--sable-optical-pad:0px]",
|
|
30
31
|
sm: "h-6 py-0 text-label-sm [--sable-optical-pad:0px]"
|
|
31
32
|
},
|
|
@@ -2,10 +2,31 @@
|
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { Icon } from "../../icon/index.js";
|
|
4
4
|
import { cn } from "../../lib/cn.js";
|
|
5
|
-
import { FIELD_INVALID, FIELD_SHELL, MOBILE_FIELD_TIER } from "../../lib/field-shell.js";
|
|
5
|
+
import { FIELD_INVALID, FIELD_SHELL, FIELD_SHELL_XS, MOBILE_FIELD_TIER } from "../../lib/field-shell.js";
|
|
6
6
|
import { useDensity, useTypeTier } from "../../lib/density.js";
|
|
7
|
+
import { hitTargetMin } from "../../lib/hit-target.js";
|
|
7
8
|
import { Button } from "../button/button.js";
|
|
8
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
var SIZES = {
|
|
11
|
+
md: {
|
|
12
|
+
field: "h-(--control-md) ps-9 pe-9",
|
|
13
|
+
icon: "start-3 text-sm",
|
|
14
|
+
clear: "end-2",
|
|
15
|
+
button: void 0
|
|
16
|
+
},
|
|
17
|
+
sm: {
|
|
18
|
+
field: "h-(--control-sm) ps-8 pe-8",
|
|
19
|
+
icon: "start-2.5 text-sm",
|
|
20
|
+
clear: "end-1",
|
|
21
|
+
button: cn("size-[calc(var(--control-sm)-var(--spacing)*2)] [--button-icon:0.875rem]", hitTargetMin)
|
|
22
|
+
},
|
|
23
|
+
xs: {
|
|
24
|
+
field: cn("h-(--control-xs) ps-6 pe-7", FIELD_SHELL_XS),
|
|
25
|
+
icon: "start-1.5 text-xs",
|
|
26
|
+
clear: "end-0.5",
|
|
27
|
+
button: cn("size-[calc(var(--control-xs)-var(--spacing))] [--button-icon:0.75rem]", hitTargetMin)
|
|
28
|
+
}
|
|
29
|
+
};
|
|
9
30
|
function SearchInput({
|
|
10
31
|
value,
|
|
11
32
|
defaultValue,
|
|
@@ -14,10 +35,12 @@ function SearchInput({
|
|
|
14
35
|
placeholder = "Search",
|
|
15
36
|
className,
|
|
16
37
|
inputClassName,
|
|
38
|
+
size = "md",
|
|
17
39
|
ref,
|
|
18
40
|
type,
|
|
19
41
|
...props
|
|
20
42
|
}) {
|
|
43
|
+
const rung = SIZES[size];
|
|
21
44
|
const [uncontrolled, setUncontrolled] = useState(defaultValue ?? "");
|
|
22
45
|
const controlled = value !== void 0;
|
|
23
46
|
const current = controlled ? value : uncontrolled;
|
|
@@ -41,7 +64,7 @@ function SearchInput({
|
|
|
41
64
|
Icon,
|
|
42
65
|
{
|
|
43
66
|
name: "search",
|
|
44
|
-
className: "pointer-events-none absolute
|
|
67
|
+
className: cn("pointer-events-none absolute text-muted-foreground", rung.icon)
|
|
45
68
|
}
|
|
46
69
|
),
|
|
47
70
|
/* @__PURE__ */ jsx(
|
|
@@ -53,10 +76,12 @@ function SearchInput({
|
|
|
53
76
|
onChange: (event) => change(event.target.value),
|
|
54
77
|
placeholder,
|
|
55
78
|
className: cn(
|
|
56
|
-
"flex
|
|
79
|
+
"flex w-full text-foreground",
|
|
57
80
|
// The shell Input and CommandMenuTrigger wear too — lib/field-shell.
|
|
81
|
+
// xs layers the inset tool-panel surface over it, as Input xs does.
|
|
58
82
|
FIELD_SHELL,
|
|
59
83
|
FIELD_INVALID,
|
|
84
|
+
rung.field,
|
|
60
85
|
"[&::-webkit-search-cancel-button]:appearance-none",
|
|
61
86
|
tier.body,
|
|
62
87
|
density === "comfortable" && MOBILE_FIELD_TIER,
|
|
@@ -72,14 +97,14 @@ function SearchInput({
|
|
|
72
97
|
// transition-property slot — whichever sorted last would win, and
|
|
73
98
|
// either the hover ease or the arrival would silently go. Removal
|
|
74
99
|
// stays instant: an emptied field should not wait on its X.
|
|
75
|
-
/* @__PURE__ */ jsx("span", { className: "absolute
|
|
100
|
+
/* @__PURE__ */ jsx("span", { className: cn("absolute flex motion-appear", rung.clear), children: /* @__PURE__ */ jsx(
|
|
76
101
|
Button,
|
|
77
102
|
{
|
|
78
103
|
variant: "ghost",
|
|
79
104
|
size: "icon-xs",
|
|
80
105
|
onClick: handleClear,
|
|
81
106
|
"aria-label": "Clear search",
|
|
82
|
-
className: "text-muted-foreground hover:text-foreground",
|
|
107
|
+
className: cn("text-muted-foreground hover:text-foreground", rung.button),
|
|
83
108
|
children: /* @__PURE__ */ jsx(Icon, { name: "xmark" })
|
|
84
109
|
}
|
|
85
110
|
) })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/components/search-input/search-input.meta.ts
|
|
2
2
|
var searchInputMeta = {
|
|
3
3
|
name: "SearchInput",
|
|
4
|
-
summary: "Styled search field with a leading magnifier and a clearable X. Soft-square (rounded-sm), the theme's field surface, 2px focus ring; the clear button appears only when there is a value.",
|
|
4
|
+
summary: "Styled search field with a leading magnifier and a clearable X. Soft-square (rounded-sm), the theme's field surface, 2px focus ring; the clear button appears only when there is a value. size md (form), sm (toolbar, matching Pill lg and Button sm) or xs (inset tool-panel field).",
|
|
5
5
|
status: "stable",
|
|
6
6
|
keywords: [
|
|
7
7
|
"search",
|
|
@@ -28,6 +28,14 @@ var searchInputMeta = {
|
|
|
28
28
|
guidance: "do",
|
|
29
29
|
description: "Light the query in the results it filters with `useMatchHighlight(query)` \u2014 a callback ref for the results container, with `selector` to keep it to titles. It paints on the `highlight` pair without wrapping matches in `<mark>`, the same paint Combobox and CommandMenu use for their rows."
|
|
30
30
|
},
|
|
31
|
+
{
|
|
32
|
+
guidance: "do",
|
|
33
|
+
description: `In a toolbar beside filter chips, use size="sm" with Pill size="lg" (and Button size="sm" for actions). All three stand on --control-sm (32px, 28 compact), the rung the spacing doctrine gives toolbar controls, so the row is one height in either density. Keep md for a search field in a form or a page header beside md buttons, and xs for a filter inside a workspace panel, where it matches Input xs's inset surface.`
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
guidance: "dont",
|
|
37
|
+
description: "Do not match a neighbour's height with h-* on className or inputClassName. The magnifier, padding and clear button would stay sized for the old rung; pick the size that shares the neighbour's rung."
|
|
38
|
+
},
|
|
31
39
|
{
|
|
32
40
|
guidance: "dont",
|
|
33
41
|
description: "Do not use SearchInput as a command palette; use CommandMenu for global commands, opened from the header by CommandMenuTrigger. A SearchInput that opens a dialog on focus is a text box you cannot type in."
|
|
@@ -42,10 +50,17 @@ var searchInputMeta = {
|
|
|
42
50
|
components: [
|
|
43
51
|
"Input",
|
|
44
52
|
"CommandMenu",
|
|
45
|
-
"Combobox"
|
|
53
|
+
"Combobox",
|
|
54
|
+
"Pill"
|
|
46
55
|
]
|
|
47
56
|
},
|
|
48
57
|
props: [
|
|
58
|
+
{
|
|
59
|
+
name: "size",
|
|
60
|
+
type: '"md" | "sm" | "xs"',
|
|
61
|
+
default: '"md"',
|
|
62
|
+
description: "The rung of the control ladder, named as on Input, Select and Combobox. md is the form field (--control-md, 40px / 32 compact). sm is the toolbar field (--control-sm, 32px / 28 compact), the rung Pill lg and Button sm share, so filters + search + actions in one row are one height. xs is the tool-panel field (--control-xs, 28px / 24 compact) on Input xs's inset panel-field surface. The magnifier, inline padding and clear button scale with the rung and stay vertically centred."
|
|
63
|
+
},
|
|
49
64
|
{
|
|
50
65
|
name: "value",
|
|
51
66
|
type: "string",
|
|
@@ -91,7 +106,7 @@ var searchInputMeta = {
|
|
|
91
106
|
{
|
|
92
107
|
name: "...props",
|
|
93
108
|
type: 'ComponentPropsWithRef<"input">',
|
|
94
|
-
description: "All native input attributes (name, autoFocus, ref, aria-invalid, \u2026) pass through to the <input> except value/onChange, which are owned by value/onValueChange,
|
|
109
|
+
description: "All native input attributes (name, autoFocus, ref, aria-invalid, \u2026) pass through to the <input> except value/onChange, which are owned by value/onValueChange, className, which sizes the whole field, and the native numeric size attribute, which the size prop replaces."
|
|
95
110
|
}
|
|
96
111
|
],
|
|
97
112
|
tokensUsed: [
|
|
@@ -100,7 +115,9 @@ var searchInputMeta = {
|
|
|
100
115
|
"foreground",
|
|
101
116
|
"muted-foreground",
|
|
102
117
|
"ring",
|
|
103
|
-
"destructive"
|
|
118
|
+
"destructive",
|
|
119
|
+
"panel-field",
|
|
120
|
+
"panel-field-hover"
|
|
104
121
|
],
|
|
105
122
|
motion: [
|
|
106
123
|
{
|
|
@@ -152,6 +169,14 @@ var searchInputMeta = {
|
|
|
152
169
|
label: "Highlighting matches in the results",
|
|
153
170
|
code: 'const [q, setQ] = useState("")\nconst results = useMatchHighlight<HTMLUListElement>(q, { selector: "[data-title]" })\nconst shown = posts.filter((post) => post.title.toLowerCase().includes(q.trim().toLowerCase()))\n\n<SearchInput value={q} onValueChange={setQ} placeholder="Search posts" />\n<ul ref={results}>\n {shown.map((post) => (\n <li key={post.id}><span data-title>{post.title}</span></li>\n ))}\n</ul>'
|
|
154
171
|
},
|
|
172
|
+
{
|
|
173
|
+
label: "Filters with search",
|
|
174
|
+
code: 'const [filter, setFilter] = useState("all")\nconst [q, setQ] = useState("")\n\n<Row gap={2} align="center" justify="between" wrap>\n <Row gap={1}>\n <Pill size="lg" selected={filter === "all"} onClick={() => setFilter("all")}>All 8</Pill>\n <Pill size="lg" selected={filter === "products"} onClick={() => setFilter("products")}>Products 5</Pill>\n <Pill size="lg" selected={filter === "oss"} onClick={() => setFilter("oss")}>Open source 2</Pill>\n </Row>\n <SearchInput size="sm" value={q} onValueChange={setQ} placeholder="Find a project\u2026" aria-label="Find a project" className="max-w-64" />\n</Row>'
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
label: "Sizes",
|
|
178
|
+
code: '<Stack gap={1}>\n <SearchInput size="md" placeholder="Search posts" aria-label="Search posts" />\n <SearchInput size="sm" placeholder="Find a project\u2026" aria-label="Find a project" />\n <SearchInput size="xs" placeholder="Filter layers" aria-label="Filter layers" />\n</Stack>'
|
|
179
|
+
},
|
|
155
180
|
{
|
|
156
181
|
label: "Disabled",
|
|
157
182
|
code: '<SearchInput disabled placeholder="Search" />'
|
|
@@ -3,9 +3,10 @@ import { useId } from "react";
|
|
|
3
3
|
import { cn } from "../../lib/cn.js";
|
|
4
4
|
import { HeadingRow } from "../../lib/heading-row.js";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
var SIZE = {
|
|
7
|
+
lg: { root: "gap-3", title: "text-heading-2", description: "text-body-md" },
|
|
8
|
+
md: { root: "gap-2", title: "text-heading-3", description: "text-body-sm" },
|
|
9
|
+
sm: { root: "gap-2", title: "text-heading-4", description: "text-body-sm" }
|
|
9
10
|
};
|
|
10
11
|
function SectionHeader({
|
|
11
12
|
title,
|
|
@@ -18,20 +19,29 @@ function SectionHeader({
|
|
|
18
19
|
...props
|
|
19
20
|
}) {
|
|
20
21
|
const generatedId = useId();
|
|
21
|
-
return /* @__PURE__ */ jsxs(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
return /* @__PURE__ */ jsxs(
|
|
23
|
+
"div",
|
|
24
|
+
{
|
|
25
|
+
"data-slot": "section-header",
|
|
26
|
+
"data-size": size,
|
|
27
|
+
className: cn("flex flex-col", SIZE[size].root, className),
|
|
28
|
+
...props,
|
|
29
|
+
children: [
|
|
30
|
+
/* @__PURE__ */ jsx(
|
|
31
|
+
HeadingRow,
|
|
32
|
+
{
|
|
33
|
+
as: titleAs,
|
|
34
|
+
title,
|
|
35
|
+
titleId: titleId ?? generatedId,
|
|
36
|
+
titleClassName: SIZE[size].title,
|
|
37
|
+
band: "flex min-h-(--control-sm) items-center mt-[min(0px,calc((var(--control-sm)_-_1lh)/2))]",
|
|
38
|
+
actions
|
|
39
|
+
}
|
|
40
|
+
),
|
|
41
|
+
description ? /* @__PURE__ */ jsx("p", { className: cn("max-w-(--measure-prose) text-pretty text-muted-foreground", SIZE[size].description), children: description }) : null
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
);
|
|
35
45
|
}
|
|
36
46
|
SectionHeader.displayName = "SectionHeader";
|
|
37
47
|
export {
|
|
@@ -14,7 +14,9 @@ var sectionHeaderMeta = {
|
|
|
14
14
|
"h2",
|
|
15
15
|
"subsection",
|
|
16
16
|
"optical alignment",
|
|
17
|
-
"bleed"
|
|
17
|
+
"bleed",
|
|
18
|
+
"editorial section",
|
|
19
|
+
"large section title"
|
|
18
20
|
],
|
|
19
21
|
usage: {
|
|
20
22
|
description: "Use SectionHeader to head each section below the page title \u2014 a home-page shelf with View all, a settings group, a list with its own actions. PageHeader is the route's single h1; every section under it takes a SectionHeader, with titleId wired to the section's aria-labelledby.",
|
|
@@ -25,7 +27,7 @@ var sectionHeaderMeta = {
|
|
|
25
27
|
},
|
|
26
28
|
{
|
|
27
29
|
guidance: "do",
|
|
28
|
-
description:
|
|
30
|
+
description: `Pick titleAs by the document outline (h2 under the page h1, h3 inside an h2 section) and size by visual weight: lg for an editorial page's sections under a PageHeader size="lg", md for a page's sections and shelves, sm for denser groups like settings.`
|
|
29
31
|
},
|
|
30
32
|
{
|
|
31
33
|
guidance: "do",
|
|
@@ -87,13 +89,13 @@ var sectionHeaderMeta = {
|
|
|
87
89
|
},
|
|
88
90
|
{
|
|
89
91
|
name: "size",
|
|
90
|
-
type: '"md" | "sm"',
|
|
92
|
+
type: '"lg" | "md" | "sm"',
|
|
91
93
|
default: '"md"',
|
|
92
|
-
description:
|
|
94
|
+
description: `Visual weight, independent of the heading level: lg (text-heading-2 over a text-body-md description) for an editorial page's sections, under a PageHeader size="lg"; md (text-heading-3) for a page's sections and shelves; sm (text-heading-4) for denser sets \u2014 settings groups, subsections. lg stops at heading-2: heading-1 is the page title's, one per route. The actions stay centred on the title's first line at every size.`
|
|
93
95
|
}
|
|
94
96
|
],
|
|
95
97
|
variants: {
|
|
96
|
-
size: ["md", "sm"]
|
|
98
|
+
size: ["lg", "md", "sm"]
|
|
97
99
|
},
|
|
98
100
|
tokensUsed: ["foreground", "muted-foreground"],
|
|
99
101
|
a11y: [
|
|
@@ -161,6 +163,26 @@ var sectionHeaderMeta = {
|
|
|
161
163
|
<Entity className="[--entity-px:0]" title="Late drives" subtitle="42 tracks" />
|
|
162
164
|
<Entity className="[--entity-px:0]" title="Focus" subtitle="18 tracks" />
|
|
163
165
|
</Stack>
|
|
166
|
+
</section>`
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
label: "An editorial section (size lg)",
|
|
170
|
+
code: `<section aria-labelledby="selected-work" className="flex flex-col gap-6">
|
|
171
|
+
<SectionHeader
|
|
172
|
+
titleId="selected-work"
|
|
173
|
+
size="lg"
|
|
174
|
+
title="Selected work"
|
|
175
|
+
description="Five projects from the last two years."
|
|
176
|
+
actions={
|
|
177
|
+
<Button variant="ghost" size="sm" render={<a href="/work" />} aria-label="View all work">
|
|
178
|
+
View all
|
|
179
|
+
</Button>
|
|
180
|
+
}
|
|
181
|
+
/>
|
|
182
|
+
<Grid minItemWidth="16rem" gap={3}>
|
|
183
|
+
<Card><CardHeader><CardTitle>Tidewater</CardTitle><CardDescription>Brand identity</CardDescription></CardHeader></Card>
|
|
184
|
+
<Card><CardHeader><CardTitle>Northlight</CardTitle><CardDescription>Product design</CardDescription></CardHeader></Card>
|
|
185
|
+
</Grid>
|
|
164
186
|
</section>`
|
|
165
187
|
},
|
|
166
188
|
{
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
useState
|
|
9
9
|
} from "react";
|
|
10
10
|
import { cn } from "../../lib/cn.js";
|
|
11
|
+
import { FocusReturnProvider, useFocusReturn, useFocusReturnDefault } from "../../lib/focus-return.js";
|
|
11
12
|
import { withPortalThemeScope } from "../../lib/portal-theme-scope.js";
|
|
12
13
|
import { PresentationProvider, StackedActionsContext, usePresentation } from "../../lib/presentation.js";
|
|
13
14
|
import { mergeRefs } from "../../lib/refs.js";
|
|
@@ -27,15 +28,20 @@ function Sheet({
|
|
|
27
28
|
defaultSnapPoint,
|
|
28
29
|
onSnapPointChange,
|
|
29
30
|
snapToSequentialPoints,
|
|
31
|
+
open,
|
|
32
|
+
onOpenChange,
|
|
30
33
|
...props
|
|
31
34
|
}) {
|
|
35
|
+
const focus = useFocusReturn(open, onOpenChange);
|
|
32
36
|
const [side, setSide] = useState("right");
|
|
33
37
|
const snaps = Boolean(snapPoints?.length);
|
|
34
38
|
const context = useMemo(() => ({ reportSide: setSide, snaps }), [snaps]);
|
|
35
|
-
return /* @__PURE__ */ jsx(SheetRootContext.Provider, { value: context, children: /* @__PURE__ */ jsx(
|
|
39
|
+
return /* @__PURE__ */ jsx(SheetRootContext.Provider, { value: context, children: /* @__PURE__ */ jsx(FocusReturnProvider, { value: focus.focusReturn, children: /* @__PURE__ */ jsx(
|
|
36
40
|
Base.Root,
|
|
37
41
|
{
|
|
38
42
|
...props,
|
|
43
|
+
open,
|
|
44
|
+
onOpenChange: focus.onOpenChange,
|
|
39
45
|
snapPoints: snaps && side === "bottom" ? snapPoints : void 0,
|
|
40
46
|
snapPoint,
|
|
41
47
|
defaultSnapPoint,
|
|
@@ -43,7 +49,7 @@ function Sheet({
|
|
|
43
49
|
snapToSequentialPoints,
|
|
44
50
|
swipeDirection: SWIPE_DIRECTION[side]
|
|
45
51
|
}
|
|
46
|
-
) });
|
|
52
|
+
) }) });
|
|
47
53
|
}
|
|
48
54
|
function isDevelopment() {
|
|
49
55
|
try {
|
|
@@ -81,10 +87,12 @@ function SheetContent({
|
|
|
81
87
|
children,
|
|
82
88
|
side,
|
|
83
89
|
initialFocus,
|
|
90
|
+
finalFocus,
|
|
84
91
|
ref,
|
|
85
92
|
style,
|
|
86
93
|
...props
|
|
87
94
|
}) {
|
|
95
|
+
const focusReturn = useFocusReturnDefault();
|
|
88
96
|
const root = useContext(SheetRootContext);
|
|
89
97
|
const resolvedSide = side ?? "right";
|
|
90
98
|
const reportSide = root?.reportSide;
|
|
@@ -98,7 +106,10 @@ function SheetContent({
|
|
|
98
106
|
}
|
|
99
107
|
const popupRef = useRef(null);
|
|
100
108
|
const [popup, setPopup] = useState(null);
|
|
101
|
-
const mergedRef = useMemo(
|
|
109
|
+
const mergedRef = useMemo(
|
|
110
|
+
() => mergeRefs(popupRef, ref, setPopup, focusReturn?.popupRef),
|
|
111
|
+
[ref, focusReturn]
|
|
112
|
+
);
|
|
102
113
|
const keyboardInset = useKeyboardInset(resolvedSide === "bottom" && popup !== null);
|
|
103
114
|
const keyboardVars = { "--sheet-keyboard-inset": `${keyboardInset}px` };
|
|
104
115
|
useIsomorphicLayoutEffect(() => {
|
|
@@ -121,6 +132,7 @@ function SheetContent({
|
|
|
121
132
|
{
|
|
122
133
|
ref: mergedRef,
|
|
123
134
|
initialFocus: initialFocus === void 0 ? (type) => type === "touch" ? popupRef.current : true : initialFocus,
|
|
135
|
+
finalFocus: finalFocus === void 0 ? focusReturn?.finalFocus : finalFocus,
|
|
124
136
|
"data-sable-sheet": resolvedSide,
|
|
125
137
|
"data-snap-points": snaps ? "" : void 0,
|
|
126
138
|
className: cn(sheetVariants({ side }), className),
|
|
@@ -93,7 +93,7 @@ var sheetMeta = {
|
|
|
93
93
|
{
|
|
94
94
|
name: "Sheet (Root)",
|
|
95
95
|
type: "Base UI Drawer.Root props (open, defaultOpen, onOpenChange, onOpenChangeComplete, modal, disablePointerDismissal, actionsRef, handle, snapPoints, snapPoint, defaultSnapPoint, onSnapPointChange, snapToSequentialPoints) \u2014 minus swipeDirection",
|
|
96
|
-
description: `Controls open state. Uncontrolled by default. The dismiss swipe direction is not a prop: it follows SheetContent's side. onOpenChange reports reason "swipe" when a swipe closed it.`
|
|
96
|
+
description: `Controls open state. Uncontrolled by default. The dismiss swipe direction is not a prop: it follows SheetContent's side. onOpenChange reports reason "swipe" when a swipe closed it. Records the element focused when the sheet opens, trigger or not, and returns focus there on close.`
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
name: "snapPoints",
|
|
@@ -171,7 +171,7 @@ var sheetMeta = {
|
|
|
171
171
|
"The panel scrolls its own content (thin scrollbar, contained overscroll) when it outgrows its bound \u2014 the full height on left/right, 80vh on top/bottom.",
|
|
172
172
|
"Esc and click-outside dismiss by default; on touch, a swipe toward the sheet's own edge dismisses too. A short or slow drag springs back.",
|
|
173
173
|
"Swipe is an extra path, never the only one: Esc, click-outside and SheetClose all still work.",
|
|
174
|
-
"Focus matches Dialog: the first tabbable on mouse/keyboard open, the panel itself on touch (so the virtual keyboard stays down)
|
|
174
|
+
"Focus matches Dialog: the first tabbable on mouse/keyboard open, the panel itself on touch (so the virtual keyboard stays down). On close, focus returns to the element that held it when the sheet opened: the SheetTrigger, or your own button when it drives a controlled open. It is captured before the content mounts, so an autoFocus field inside cannot displace it; an explicit finalFocus on SheetContent wins.",
|
|
175
175
|
"Render a SheetClose inside SheetContent so screen-reader users can escape the popup.",
|
|
176
176
|
"Slide animation runs via data-[starting-style]/data-[ending-style] attributes on the motion-duration token; while swiping the panel tracks the finger directly, and a released swipe finishes on the same token (scaled by flick speed), so reduced motion makes it instant.",
|
|
177
177
|
"A mouse drag that starts on sheet content selects text rather than dragging the panel; mouse dragging only starts from the padding edge.",
|
|
@@ -11,7 +11,12 @@ var sideNavItemVariants = cva(
|
|
|
11
11
|
md: "gap-2.5 rounded-sm px-3 py-1.5 text-body-sm",
|
|
12
12
|
lg: "gap-3 rounded-sm px-3 py-2 text-body-md",
|
|
13
13
|
// The app rail (Sidebar): chrome-weight rows on the label tier.
|
|
14
|
-
rail: `${RAIL_ROW_INLINE} rounded-sm py-2.5 text-label-lg
|
|
14
|
+
rail: `${RAIL_ROW_INLINE} rounded-sm py-2.5 text-label-lg`,
|
|
15
|
+
// The same rail inside a compact scope (a Workspace): the same
|
|
16
|
+
// columns, a 20px icon and a 32px row. Sidebar picks it from the
|
|
17
|
+
// density, so a tool's rail is not a page's 44px rows beside
|
|
18
|
+
// 28px chrome.
|
|
19
|
+
"rail-compact": `${RAIL_ROW_INLINE} rounded-sm py-1.5 text-label-lg`
|
|
15
20
|
},
|
|
16
21
|
activeVariant: {
|
|
17
22
|
content: "text-foreground hover:bg-muted focus-visible:ring-ring data-[active]:bg-accent data-[active]:text-accent-foreground",
|
|
@@ -24,8 +29,8 @@ var sideNavItemVariants = cva(
|
|
|
24
29
|
var sideNavItemIconVariants = cva(
|
|
25
30
|
"flex shrink-0 items-center justify-center",
|
|
26
31
|
{
|
|
27
|
-
// md 20px icon in a 20px box; lg 20px in 24px; rail 24px in 24px.
|
|
28
|
-
variants: { size: { md: "size-[1em] text-xl", lg: "size-[1.2em] text-xl", rail: "size-[1em] text-2xl" } },
|
|
32
|
+
// md 20px icon in a 20px box; lg 20px in 24px; rail 24px in 24px; compact rail 20px in 20px.
|
|
33
|
+
variants: { size: { md: "size-[1em] text-xl", lg: "size-[1.2em] text-xl", rail: "size-[1em] text-2xl", "rail-compact": "size-[1em] text-xl" } },
|
|
29
34
|
defaultVariants: { size: "md" }
|
|
30
35
|
}
|
|
31
36
|
);
|
|
@@ -6,13 +6,17 @@ import {
|
|
|
6
6
|
SidebarNav,
|
|
7
7
|
SidebarFooter,
|
|
8
8
|
SidebarItem,
|
|
9
|
+
SidebarGroup,
|
|
10
|
+
SidebarAccount,
|
|
9
11
|
useSidebar
|
|
10
12
|
} from "./sidebar.js";
|
|
11
13
|
import { sidebarMeta } from "./sidebar.meta.js";
|
|
12
14
|
export {
|
|
13
15
|
Sidebar,
|
|
16
|
+
SidebarAccount,
|
|
14
17
|
SidebarBrand,
|
|
15
18
|
SidebarFooter,
|
|
19
|
+
SidebarGroup,
|
|
16
20
|
SidebarHeader,
|
|
17
21
|
SidebarItem,
|
|
18
22
|
SidebarNav,
|