@sableui/react 0.1.0 → 0.2.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/CHANGELOG.md +66 -0
- package/dist/{_contract-CYScOdf5.d.ts → _contract-o3tUc8Ig.d.ts} +1 -1
- package/dist/{chunk-J23ELQQZ.js → chunk-AOWBYEQ6.js} +4177 -1917
- package/dist/{chunk-RWP4XBSK.js → chunk-KFZ43RKU.js} +1 -1
- package/dist/{chunk-JI7WJP2L.js → chunk-X276MF2W.js} +5 -0
- package/dist/foundations/index.js +1 -1
- package/dist/index.d.ts +155 -101
- package/dist/index.js +582 -2778
- package/dist/ios/index.d.ts +6 -1
- package/dist/ios/index.js +4 -4
- package/dist/ios/preview.js +1 -1
- package/dist/ios/styles.css +20 -6
- package/dist/patterns/index.js +1683 -469
- package/dist/presets/index.d.ts +1 -1
- package/dist/presets/index.js +1 -1
- package/dist/styles.css +238 -40
- package/dist/{theme-provider-BWcizhAt.d.ts → theme-provider-BE-KqUG-.d.ts} +1 -1
- package/dist/themes/atelier/atelier.css +3 -0
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/base/base.css +11 -4
- package/dist/themes/base/index.d.ts +1 -1
- package/dist/themes/ember/ember.css +3 -0
- 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/meridian/meridian.css +10 -5
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/poppy/poppy.css +16 -0
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/prism/prism.css +4 -1
- package/dist/themes/verdant/index.d.ts +1 -1
- package/dist/themes/verdant/verdant.css +7 -2
- package/llms/components/bubble.md +5 -5
- package/llms/components/checkbox-card.md +1 -1
- package/llms/components/context-card.md +2 -2
- package/llms/components/dialog.md +1 -1
- package/llms/components/empty-state.md +4 -4
- package/llms/components/entity.md +1 -1
- package/llms/components/fieldset.md +1 -1
- package/llms/components/kbd.md +18 -6
- package/llms/components/pill.md +1 -1
- package/llms/components/side-nav.md +1 -1
- package/llms/components/skeleton.md +1 -1
- package/llms/components/theme-selector.md +1 -1
- package/llms/foundations/shape.md +1 -1
- package/llms/patterns/auth-page.md +132 -42
- package/llms/patterns/brand-landing-page.md +271 -175
- package/llms/patterns/chat-panel.md +120 -39
- package/llms/patterns/dashboard-page.md +277 -65
- package/llms/patterns/feed-page.md +245 -64
- package/llms/patterns/landing-page.md +473 -75
- package/llms/patterns/list-detail-page.md +385 -68
- package/llms/patterns/settings-page.md +279 -67
- package/llms/reference/token-intent.md +4 -2
- package/llms/reference/utilities.md +20 -5
- package/llms-full.txt +2244 -727
- package/llms.txt +14 -15
- package/package.json +1 -1
- package/registry.json +261 -238
- package/targets/ios.json +4 -15
- package/llms/components/description.md +0 -96
package/dist/patterns/index.js
CHANGED
|
@@ -1,103 +1,199 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Region, Stack, Row,
|
|
1
|
+
import { Form, Field, Input, Button, Separator, Card, CardContent, Badge, Entity, Avatar, TextReveal, StatusDot, Reveal, CardHeader, CardTitle, CardDescription, CardAction, MessageScroller, Marker, Message, Bubble, CodeBlock, Toolbar, SimpleTooltip, ToolbarButton, Attachment, StreamingText, CardFooter, ChatComposer, SettingsMenu, SettingsRow, MenuRadioGroup, MenuRadioItem, AppShell, PageHeader, DataTable, Meter, CardList, RelativeTime, Sidebar, SidebarHeader, SidebarNav, SidebarItem, NotificationBadge, SidebarFooter, DotsMenu, MenuItem, MenuSeparator, BottomNav, BottomNavItem, AppHeader, Textarea, Tabs, TabsList, TabsTrigger, TabsPanel, Skeleton, Browser, MeterLabel, MeterValue, MeterTrack, Code, SearchInput, EmptyState, Select, SelectTrigger, SelectContent, SelectItem, DestructiveActionModal, SideNav, SideNavItem, Switch } from '../chunk-AOWBYEQ6.js';
|
|
2
|
+
import { Region, Stack, Row, Grid, Page, Columns, Col, AspectBox } from '../chunk-3IWZYBGH.js';
|
|
3
3
|
import '../chunk-SRQWQUTW.js';
|
|
4
4
|
import { Icon } from '../chunk-PAOZBZME.js';
|
|
5
5
|
import '../chunk-5Z5KLOAU.js';
|
|
6
|
-
import {
|
|
6
|
+
import { useState, useRef, useEffect } from 'react';
|
|
7
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
7
8
|
|
|
9
|
+
var artwork = [
|
|
10
|
+
// design-lint-disable-next-line inline-color -- relative colour derived from the theme's own --tone token, so it follows every look
|
|
11
|
+
"radial-gradient(in oklab at 18% 22%, oklch(from var(--tone-info) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h), transparent 58%)",
|
|
12
|
+
// design-lint-disable-next-line inline-color -- relative colour derived from the theme's own --tone token, so it follows every look
|
|
13
|
+
"radial-gradient(in oklab at 82% 18%, oklch(from var(--tone-collectible) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h), transparent 55%)",
|
|
14
|
+
// design-lint-disable-next-line inline-color -- relative colour derived from the theme's own --tone token, so it follows every look
|
|
15
|
+
"radial-gradient(in oklab at 70% 88%, oklch(from var(--tone-edition) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h), transparent 60%)",
|
|
16
|
+
// design-lint-disable-next-line inline-color -- relative colour derived from the theme's own --tone token, so it follows every look
|
|
17
|
+
"radial-gradient(in oklab at 12% 92%, oklch(from var(--tone-standard) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h), transparent 55%)",
|
|
18
|
+
"var(--primary)"
|
|
19
|
+
].join(", ");
|
|
8
20
|
function AuthPage() {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
const [pending2, setPending] = useState(false);
|
|
22
|
+
const [errors, setErrors] = useState({});
|
|
23
|
+
return /* @__PURE__ */ jsxs("main", { className: "grid min-h-dvh bg-background lg:grid-cols-[minmax(0,1fr)_minmax(0,1.1fr)]", children: [
|
|
24
|
+
/* @__PURE__ */ jsx(Region, { max: "form", safeArea: ["top", "bottom"], className: "flex flex-col", children: /* @__PURE__ */ jsxs(Stack, { gap: 0, className: "flex-1 py-8 sm:py-10", children: [
|
|
25
|
+
/* @__PURE__ */ jsxs("a", { href: "/", className: "flex items-center gap-2.5 self-start rounded-sm motion-interactive motion-press", children: [
|
|
26
|
+
/* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-2xl" }),
|
|
27
|
+
/* @__PURE__ */ jsx("span", { className: "text-title-lg", children: "Atelier" })
|
|
14
28
|
] }),
|
|
15
|
-
/* @__PURE__ */
|
|
16
|
-
/* @__PURE__ */
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 4, justify: "center", className: "flex-1 py-12", children: [
|
|
30
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 1.5, children: [
|
|
31
|
+
/* @__PURE__ */ jsx("h1", { className: "text-heading-1", children: "Welcome back" }),
|
|
32
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-lg text-muted-foreground", children: "Sign in to collect new releases and manage your editions." })
|
|
33
|
+
] }),
|
|
34
|
+
/* @__PURE__ */ jsxs(
|
|
35
|
+
Form,
|
|
36
|
+
{
|
|
37
|
+
className: "gap-5",
|
|
38
|
+
errors,
|
|
39
|
+
onFormSubmit: () => {
|
|
40
|
+
setPending(true);
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
setPending(false);
|
|
43
|
+
setErrors({ password: "That email and password don\u2019t match. Try again or reset your password." });
|
|
44
|
+
}, 900);
|
|
45
|
+
},
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ jsx(Field, { label: "Email", name: "email", validate: (value) => value ? null : "Enter your email address.", children: /* @__PURE__ */ jsx(Input, { type: "email", autoComplete: "email webauthn", placeholder: "you@example.com" }) }),
|
|
48
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 1.5, children: [
|
|
49
|
+
/* @__PURE__ */ jsx(Field, { label: "Password", name: "password", validate: (value) => value ? null : "Enter your password.", children: /* @__PURE__ */ jsx(Input, { type: "password", autoComplete: "current-password" }) }),
|
|
50
|
+
/* @__PURE__ */ jsx("a", { href: "/forgot-password", className: "hit-target self-end text-body-sm text-muted-foreground link-underline motion-interactive", children: "Forgot password?" })
|
|
51
|
+
] }),
|
|
52
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", size: "cta", loading: pending2, loadingLabel: "Signing in", className: "w-full", children: "Sign in" })
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
),
|
|
19
56
|
/* @__PURE__ */ jsx(Separator, { label: "or" }),
|
|
20
|
-
/* @__PURE__ */
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
57
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 1.5, children: [
|
|
58
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "cta", className: "w-full", children: "Sign in with a passkey" }),
|
|
59
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "cta", className: "w-full", children: "Connect a wallet" })
|
|
60
|
+
] }),
|
|
61
|
+
/* @__PURE__ */ jsxs("p", { className: "text-body-md text-muted-foreground", children: [
|
|
62
|
+
"No account?",
|
|
63
|
+
" ",
|
|
64
|
+
/* @__PURE__ */ jsx("a", { href: "/signup", className: "hit-target link-underline motion-interactive text-foreground", children: "Create one" })
|
|
65
|
+
] })
|
|
66
|
+
] }),
|
|
67
|
+
/* @__PURE__ */ jsxs("p", { className: "text-body-xs text-muted-foreground", children: [
|
|
68
|
+
"By signing in you agree to the",
|
|
69
|
+
" ",
|
|
70
|
+
/* @__PURE__ */ jsx("a", { href: "/terms", className: "link-underline motion-interactive", children: "Terms" }),
|
|
71
|
+
" ",
|
|
72
|
+
"and",
|
|
73
|
+
" ",
|
|
74
|
+
/* @__PURE__ */ jsx("a", { href: "/privacy", className: "link-underline motion-interactive", children: "Privacy Policy" }),
|
|
75
|
+
"."
|
|
76
|
+
] })
|
|
77
|
+
] }) }),
|
|
78
|
+
/* @__PURE__ */ jsx("aside", { "aria-label": "Featured release", className: "hidden p-3 lg:flex", children: /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col justify-end rounded-xl p-8", style: { background: artwork }, children: /* @__PURE__ */ jsx(Card, { className: "max-w-md", children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
79
|
+
/* @__PURE__ */ jsx(Badge, { tone: "edition", className: "self-start", children: "Edition of 50" }),
|
|
80
|
+
/* @__PURE__ */ jsx("p", { className: "text-title-lg", children: "\u201CEvery edition is a conversation with whoever holds it next.\u201D" }),
|
|
81
|
+
/* @__PURE__ */ jsx(
|
|
82
|
+
Entity,
|
|
83
|
+
{
|
|
84
|
+
className: "p-0",
|
|
85
|
+
leading: /* @__PURE__ */ jsx(Avatar, { fallback: "MO" }),
|
|
86
|
+
title: "Mara Okafor",
|
|
87
|
+
subtitle: "Untitled (gold rush), 2026"
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
] }) }) }) }) })
|
|
91
|
+
] });
|
|
29
92
|
}
|
|
30
93
|
|
|
31
94
|
// src/patterns/auth-page.meta.ts
|
|
32
95
|
var authPageMeta = {
|
|
33
96
|
name: "AuthPage",
|
|
34
|
-
summary: "
|
|
97
|
+
summary: "Split sign-in page: a form column (brand mark, heading, email + password, primary sign-in, passkey and wallet alternatives, sign-up switch, terms) beside a brand panel painted from the theme's tones with a featured release card. Below lg the panel drops away and the form is the whole screen.",
|
|
35
98
|
intent: "Reach for this for sign-in, sign-up, forgot-password, and any single-purpose one-column form route (invite accept, unsubscribe). Not for multi-section forms (use SettingsPage).",
|
|
36
99
|
notes: [
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
100
|
+
"The form column is a `form`-capped Region (480px), so the controls keep the same measure whether the panel is present or not.",
|
|
101
|
+
"No card around the form: the page itself is the surface, and the brand panel carries the identity. A card on a blank page reads as a template.",
|
|
102
|
+
"The heading is `text-heading-1` in an h1 \u2014 it is the page outline, so it takes a heading tier, not a card title.",
|
|
103
|
+
"Brand mark, form and terms are a three-part column: mark pinned to the top, form vertically centred in the remaining space, terms pinned to the bottom.",
|
|
104
|
+
"Forgot password sits under the password field, right-aligned, before the submit button \u2014 so it comes right after the field in tab order. Field has no label-row action slot yet.",
|
|
105
|
+
"One primary action. Passkey and wallet are outline buttons under an `or` Separator; switching to sign-up is a quiet `link-underline` link.",
|
|
106
|
+
"Validation uses Field `validate` for product copy instead of the browser's generic required message. A rejected sign-in comes back through Form `errors` on the password field; the submit Button shows `loading` while the request is in flight.",
|
|
107
|
+
"The brand panel artwork is a mesh of the theme's decorative tones over `--primary`, so it re-paints for every theme and mode. The relative-colour `oklch(from var(--tone-*) \u2026)` lift needs an `inline-color` disable with a reason.",
|
|
108
|
+
"The panel is `hidden lg:flex`: on a phone the form is the whole screen, with no decorative scroll above it."
|
|
42
109
|
],
|
|
43
110
|
a11y: [
|
|
44
|
-
|
|
45
|
-
"Field auto-wires labels, describedby, and invalid state onto the inputs.",
|
|
46
|
-
"Autocomplete attributes (email
|
|
111
|
+
'The route renders inside <main> with one h1; the brand panel is an <aside> labelled "Featured release".',
|
|
112
|
+
"Field auto-wires labels, describedby, and invalid state onto the inputs; on submit, the first invalid field is focused.",
|
|
113
|
+
"Autocomplete attributes (`email webauthn`, `current-password`) keep password managers and passkey autofill working.",
|
|
114
|
+
"The Forgot password and Create one links use `hit-target` to meet the 44px touch floor without changing layout.",
|
|
115
|
+
"The artwork swatches are aria-hidden decoration; the brand link's accessible name is the wordmark."
|
|
47
116
|
]
|
|
48
117
|
};
|
|
49
|
-
var navItems = [
|
|
50
|
-
|
|
51
|
-
{ label: "
|
|
52
|
-
{ label: "
|
|
53
|
-
{ label: "
|
|
118
|
+
var navItems = [
|
|
119
|
+
{ label: "Platform", href: "#platform" },
|
|
120
|
+
{ label: "How it works", href: "#how-it-works" },
|
|
121
|
+
{ label: "Customers", href: "#customers" },
|
|
122
|
+
{ label: "Pricing", href: "/pricing" }
|
|
54
123
|
];
|
|
55
|
-
var
|
|
124
|
+
var spaces = [
|
|
125
|
+
{ name: "Waypoint", value: "98.4%", label: "clear routes" },
|
|
126
|
+
{ name: "Fielddesk", value: "12", label: "open holds" },
|
|
127
|
+
{ name: "Archive", value: "4.8k", label: "sealed records" }
|
|
128
|
+
];
|
|
129
|
+
var routes = [
|
|
56
130
|
{ label: "Intake", state: "Open", tone: "info" },
|
|
57
131
|
{ label: "Review", state: "Live", tone: "edition" },
|
|
58
132
|
{ label: "Archive", state: "Safe", tone: "success" }
|
|
59
133
|
];
|
|
60
134
|
var bars = [
|
|
61
|
-
{ height: "38%",
|
|
62
|
-
{ height: "56%",
|
|
63
|
-
{ height: "44%",
|
|
64
|
-
{ height: "68%",
|
|
65
|
-
{ height: "52%",
|
|
66
|
-
{ height: "78%",
|
|
67
|
-
{ height: "60%",
|
|
68
|
-
{
|
|
69
|
-
{
|
|
135
|
+
{ week: "w1", height: "38%", current: false },
|
|
136
|
+
{ week: "w2", height: "56%", current: false },
|
|
137
|
+
{ week: "w3", height: "44%", current: false },
|
|
138
|
+
{ week: "w4", height: "68%", current: false },
|
|
139
|
+
{ week: "w5", height: "52%", current: false },
|
|
140
|
+
{ week: "w6", height: "78%", current: false },
|
|
141
|
+
{ week: "w7", height: "60%", current: false },
|
|
142
|
+
{ week: "w8", height: "72%", current: false },
|
|
143
|
+
{ week: "w9", height: "88%", current: true }
|
|
70
144
|
];
|
|
71
|
-
var
|
|
145
|
+
var sceneMetrics = [
|
|
146
|
+
{ label: "Cleared", value: "1.8M", detail: "last 30 days" },
|
|
147
|
+
{ label: "Review", value: "42s", detail: "median path" },
|
|
148
|
+
{ label: "Spaces", value: "284", detail: "active teams" }
|
|
149
|
+
];
|
|
150
|
+
var values = [
|
|
72
151
|
{
|
|
73
152
|
icon: "lock",
|
|
74
153
|
title: "Private by default",
|
|
75
|
-
body: "
|
|
154
|
+
body: "Roles, approvals, and handoff history stay with the people who need them. No shared inboxes, no forwarded screenshots."
|
|
76
155
|
},
|
|
77
156
|
{
|
|
78
|
-
icon: "
|
|
79
|
-
title: "
|
|
80
|
-
body: "
|
|
157
|
+
icon: "bell",
|
|
158
|
+
title: "Signals before surprises",
|
|
159
|
+
body: "Cove watches every route and surfaces stalled reviews and expiring approvals while there is still time to act."
|
|
81
160
|
},
|
|
82
161
|
{
|
|
83
|
-
icon: "
|
|
84
|
-
title: "
|
|
85
|
-
body: "
|
|
162
|
+
icon: "clock",
|
|
163
|
+
title: "A trail for every decision",
|
|
164
|
+
body: "Each approval keeps its context, reviewer, and timestamp, so an audit takes an afternoon instead of a quarter."
|
|
86
165
|
}
|
|
87
166
|
];
|
|
88
|
-
var
|
|
89
|
-
{
|
|
90
|
-
|
|
91
|
-
|
|
167
|
+
var steps = [
|
|
168
|
+
{
|
|
169
|
+
number: "01",
|
|
170
|
+
title: "Connect your spaces",
|
|
171
|
+
body: "Bring teams, vendors, and record stores into one workspace, with roles that mirror how you already work."
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
number: "02",
|
|
175
|
+
title: "Route every approval",
|
|
176
|
+
body: "Requests follow the path you define, with reviewers, deadlines, and escalation built in from the start."
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
number: "03",
|
|
180
|
+
title: "Seal the record",
|
|
181
|
+
body: "Approved work is archived with its full context and is ready for review the moment it closes."
|
|
182
|
+
}
|
|
183
|
+
];
|
|
184
|
+
var outcomes = [
|
|
185
|
+
{ value: "3.2\xD7", label: "faster approvals in the first quarter" },
|
|
186
|
+
{ value: "68%", label: "fewer follow-ups on handoffs" },
|
|
187
|
+
{ value: "1 day", label: "to prepare a quarterly audit" }
|
|
92
188
|
];
|
|
93
189
|
var footerGroups = [
|
|
94
190
|
{
|
|
95
191
|
title: "Platform",
|
|
96
192
|
links: [
|
|
97
193
|
{ label: "Overview", href: "#platform" },
|
|
98
|
-
{ label: "Signals", href: "
|
|
99
|
-
{ label: "Approvals", href: "
|
|
100
|
-
{ label: "Records", href: "
|
|
194
|
+
{ label: "Signals", href: "/signals" },
|
|
195
|
+
{ label: "Approvals", href: "/approvals" },
|
|
196
|
+
{ label: "Records", href: "/records" }
|
|
101
197
|
]
|
|
102
198
|
},
|
|
103
199
|
{
|
|
@@ -105,7 +201,7 @@ var footerGroups = [
|
|
|
105
201
|
links: [
|
|
106
202
|
{ label: "About", href: "/about" },
|
|
107
203
|
{ label: "Security", href: "/security" },
|
|
108
|
-
{ label: "Customers", href: "
|
|
204
|
+
{ label: "Customers", href: "#customers" },
|
|
109
205
|
{ label: "Careers", href: "/careers" }
|
|
110
206
|
]
|
|
111
207
|
},
|
|
@@ -119,10 +215,10 @@ var footerGroups = [
|
|
|
119
215
|
]
|
|
120
216
|
}
|
|
121
217
|
];
|
|
122
|
-
var
|
|
123
|
-
{ label: "
|
|
124
|
-
{ label: "
|
|
125
|
-
{ label: "
|
|
218
|
+
var legalLinks = [
|
|
219
|
+
{ label: "Privacy", href: "/privacy" },
|
|
220
|
+
{ label: "Terms", href: "/terms" },
|
|
221
|
+
{ label: "Contact", href: "/contact" }
|
|
126
222
|
];
|
|
127
223
|
function BrandLandingPage() {
|
|
128
224
|
return /* @__PURE__ */ jsxs("div", { className: "min-h-dvh overflow-hidden bg-background text-foreground", children: [
|
|
@@ -136,7 +232,7 @@ function BrandLandingPage() {
|
|
|
136
232
|
/* @__PURE__ */ jsx("span", { className: "grid size-8 place-items-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsx(Icon, { name: "sparkles", className: "text-sm" }) }),
|
|
137
233
|
/* @__PURE__ */ jsx("span", { className: "text-title-lg", children: "Cove" })
|
|
138
234
|
] }),
|
|
139
|
-
/* @__PURE__ */ jsx("div", { className: "hidden lg:block", children: /* @__PURE__ */ jsx(Row, { gap: 1, children: navItems.map((item) => /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href:
|
|
235
|
+
/* @__PURE__ */ jsx("div", { className: "hidden lg:block", children: /* @__PURE__ */ jsx(Row, { gap: 1, children: navItems.map((item) => /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: item.href }), children: item.label }, item.label)) }) }),
|
|
140
236
|
/* @__PURE__ */ jsxs(Row, { gap: 1, children: [
|
|
141
237
|
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", className: "max-sm:hidden", render: /* @__PURE__ */ jsx("a", { href: "/login" }), children: "Sign in" }),
|
|
142
238
|
/* @__PURE__ */ jsx(Button, { size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/demo" }), children: "Request access" })
|
|
@@ -145,118 +241,155 @@ function BrandLandingPage() {
|
|
|
145
241
|
}
|
|
146
242
|
) }) }),
|
|
147
243
|
/* @__PURE__ */ jsxs("main", { children: [
|
|
148
|
-
/* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "pb-
|
|
149
|
-
/* @__PURE__ */ jsxs(Stack, { gap:
|
|
244
|
+
/* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "pb-20 pt-14 sm:pt-20 lg:pb-28", children: /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-12 lg:grid-cols-[1fr_1.1fr] lg:gap-10", children: [
|
|
245
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 3, className: "max-w-2xl", children: [
|
|
150
246
|
/* @__PURE__ */ jsx(Badge, { appearance: "outline", icon: /* @__PURE__ */ jsx(Icon, { name: "lock", className: "text-sm" }), children: "Private operations, made legible" }),
|
|
151
247
|
/* @__PURE__ */ jsx(
|
|
152
248
|
TextReveal,
|
|
153
249
|
{
|
|
154
250
|
as: "h1",
|
|
155
|
-
text: "A calmer command center for high-trust
|
|
156
|
-
className: "
|
|
251
|
+
text: "A calmer command center for high-trust work",
|
|
252
|
+
className: "text-balance text-display-lg sm:text-display-2xl",
|
|
157
253
|
once: true
|
|
158
254
|
}
|
|
159
255
|
),
|
|
160
256
|
/* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-body-lg text-muted-foreground", children: "Cove gives teams a polished front door and a serious operating surface: approvals, signals, records, and handoffs in one understandable flow." }),
|
|
161
|
-
/* @__PURE__ */ jsxs(Row, { gap: 2, wrap: true, children: [
|
|
257
|
+
/* @__PURE__ */ jsxs(Row, { gap: 2, wrap: true, className: "pt-3", children: [
|
|
162
258
|
/* @__PURE__ */ jsx(Button, { size: "cta", render: /* @__PURE__ */ jsx("a", { href: "/demo" }), children: "Book a walkthrough" }),
|
|
163
|
-
/* @__PURE__ */ jsx(Button, { size: "cta", variant: "outline", render: /* @__PURE__ */ jsx("a", { href: "#platform" }), children: "Explore platform" })
|
|
259
|
+
/* @__PURE__ */ jsx(Button, { size: "cta", variant: "outline", render: /* @__PURE__ */ jsx("a", { href: "#platform" }), children: "Explore the platform" })
|
|
164
260
|
] })
|
|
165
261
|
] }),
|
|
166
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
167
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-
|
|
262
|
+
/* @__PURE__ */ jsx("div", { className: "dark", "aria-hidden": true, inert: true, children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-xl border border-border-floating bg-popover p-3 text-popover-foreground shadow-xl", children: /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/3 border border-border bg-card", children: [
|
|
263
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-4 py-3", children: [
|
|
168
264
|
/* @__PURE__ */ jsxs(Row, { gap: 1, children: [
|
|
169
265
|
/* @__PURE__ */ jsx(StatusDot, { tone: "warning" }),
|
|
170
266
|
/* @__PURE__ */ jsx(StatusDot, { tone: "edition" }),
|
|
171
267
|
/* @__PURE__ */ jsx(StatusDot, { tone: "success" })
|
|
172
268
|
] }),
|
|
173
|
-
/* @__PURE__ */ jsx("span", { className: "text-label-sm text-
|
|
269
|
+
/* @__PURE__ */ jsx("span", { className: "text-label-sm text-muted-foreground", children: "Live workspace" })
|
|
174
270
|
] }),
|
|
175
271
|
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 p-3 md:grid-cols-[0.7fr_1.3fr]", children: [
|
|
176
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
177
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-
|
|
178
|
-
/* @__PURE__ */ jsx("p", { className: "text-label-sm text-
|
|
179
|
-
/* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children:
|
|
272
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3 max-md:hidden", children: [
|
|
273
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-border bg-background p-4", children: [
|
|
274
|
+
/* @__PURE__ */ jsx("p", { className: "text-label-sm text-muted-foreground", children: "Spaces" }),
|
|
275
|
+
/* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: spaces.map((space) => /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/10 bg-card p-3", children: [
|
|
180
276
|
/* @__PURE__ */ jsxs(Row, { justify: "between", children: [
|
|
181
|
-
/* @__PURE__ */ jsx("span", { className: "text-title-sm", children:
|
|
182
|
-
/* @__PURE__ */ jsx("span", { className: "text-mono-sm
|
|
277
|
+
/* @__PURE__ */ jsx("span", { className: "text-title-sm", children: space.name }),
|
|
278
|
+
/* @__PURE__ */ jsx("span", { className: "text-mono-sm", children: space.value })
|
|
183
279
|
] }),
|
|
184
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-body-xs text-
|
|
185
|
-
] },
|
|
280
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-body-xs text-muted-foreground", children: space.label })
|
|
281
|
+
] }, space.name)) })
|
|
186
282
|
] }),
|
|
187
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-
|
|
283
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-border bg-background p-4", children: [
|
|
188
284
|
/* @__PURE__ */ jsxs(Row, { justify: "between", children: [
|
|
189
285
|
/* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Route status" }),
|
|
190
|
-
/* @__PURE__ */ jsx(Badge, { tone: "success", size: "sm", children: "Healthy" })
|
|
286
|
+
/* @__PURE__ */ jsx(Badge, { tone: "success", size: "sm", dot: true, children: "Healthy" })
|
|
191
287
|
] }),
|
|
192
|
-
/* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children:
|
|
288
|
+
/* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: routes.map((route) => /* @__PURE__ */ jsxs(Row, { justify: "between", className: "rounded-full bg-card px-3 py-2", children: [
|
|
193
289
|
/* @__PURE__ */ jsxs(Row, { gap: 1, children: [
|
|
194
|
-
/* @__PURE__ */ jsx(StatusDot, { tone:
|
|
195
|
-
/* @__PURE__ */ jsx("span", { className: "text-body-sm", children:
|
|
290
|
+
/* @__PURE__ */ jsx(StatusDot, { tone: route.tone }),
|
|
291
|
+
/* @__PURE__ */ jsx("span", { className: "text-body-sm", children: route.label })
|
|
196
292
|
] }),
|
|
197
|
-
/* @__PURE__ */ jsx("span", { className: "text-label-sm text-
|
|
198
|
-
] },
|
|
293
|
+
/* @__PURE__ */ jsx("span", { className: "text-label-sm text-muted-foreground", children: route.state })
|
|
294
|
+
] }, route.label)) })
|
|
199
295
|
] })
|
|
200
296
|
] }),
|
|
201
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-
|
|
202
|
-
/* @__PURE__ */ jsxs(Row, { justify: "between", className: "border-b border-
|
|
297
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/6 border border-border bg-background p-4", children: [
|
|
298
|
+
/* @__PURE__ */ jsxs(Row, { justify: "between", className: "border-b border-border pb-4", children: [
|
|
203
299
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
204
|
-
/* @__PURE__ */ jsx("p", { className: "text-label-sm text-
|
|
205
|
-
/* @__PURE__ */ jsx("
|
|
300
|
+
/* @__PURE__ */ jsx("p", { className: "text-label-sm text-muted-foreground", children: "Signal map" }),
|
|
301
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-heading-4 sm:text-heading-3", children: "Every approval path in view" })
|
|
206
302
|
] }),
|
|
207
303
|
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", children: "Review" })
|
|
208
304
|
] }),
|
|
209
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-
|
|
210
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-9 items-end gap-
|
|
305
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-5 grid gap-4 sm:mt-6 sm:gap-5", children: [
|
|
306
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-9 items-end gap-1.5 rounded-concentric-xl/10 bg-card p-4 sm:gap-2", children: bars.map((bar) => /* @__PURE__ */ jsx("div", { className: "flex h-36 items-end sm:h-56", children: /* @__PURE__ */ jsx(
|
|
211
307
|
"div",
|
|
212
308
|
{
|
|
213
|
-
className: `w-full rounded-full ${bar.
|
|
309
|
+
className: `w-full rounded-full ${bar.current ? "bg-success" : "bg-secondary"}`,
|
|
214
310
|
style: { height: bar.height }
|
|
215
311
|
}
|
|
216
|
-
) },
|
|
217
|
-
/* @__PURE__ */ jsx("div", { className: "grid
|
|
218
|
-
/* @__PURE__ */ jsx("p", { className: "text-label-sm text-
|
|
219
|
-
/* @__PURE__ */ jsx("p", { className: "mt-2 text-heading-3", children: metric.value }),
|
|
220
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-body-xs text-
|
|
312
|
+
) }, bar.week)) }),
|
|
313
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2", children: sceneMetrics.map((metric) => /* @__PURE__ */ jsxs("div", { className: "rounded-concentric-xl/10 border border-border bg-card p-3", children: [
|
|
314
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-label-sm text-muted-foreground", children: metric.label }),
|
|
315
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-heading-4 sm:text-heading-3", children: metric.value }),
|
|
316
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-body-xs text-muted-foreground max-sm:hidden", children: metric.detail })
|
|
221
317
|
] }, metric.label)) })
|
|
222
318
|
] })
|
|
223
319
|
] })
|
|
224
320
|
] })
|
|
225
321
|
] }) }) })
|
|
226
322
|
] }) }),
|
|
227
|
-
/* @__PURE__ */ jsx("section", { id: "platform", className: "border-y border-border bg-muted/40 py-
|
|
228
|
-
/* @__PURE__ */ jsxs(Stack, { gap:
|
|
229
|
-
/* @__PURE__ */ jsx(
|
|
230
|
-
/* @__PURE__ */ jsx("h2", { className: "text-heading-1", children: "
|
|
231
|
-
/* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-body-
|
|
323
|
+
/* @__PURE__ */ jsx("section", { id: "platform", "aria-labelledby": "brand-platform-title", className: "border-y border-border bg-muted/40 py-20 lg:py-28", children: /* @__PURE__ */ jsx(Region, { max: "wide", children: /* @__PURE__ */ jsxs(Stack, { gap: 6, children: [
|
|
324
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, className: "max-w-2xl", children: [
|
|
325
|
+
/* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Why Cove" }),
|
|
326
|
+
/* @__PURE__ */ jsx("h2", { id: "brand-platform-title", className: "text-balance text-heading-1", children: "One private workspace for the decisions that matter." }),
|
|
327
|
+
/* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-body-lg text-muted-foreground", children: "Most operations work lives in threads, spreadsheets, and memory. Cove gives it a home: every request routed, every signal visible, every outcome on the record." })
|
|
328
|
+
] }),
|
|
329
|
+
/* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx(Grid, { minItemWidth: "calc(var(--spacing) * 64)", gap: 2, children: values.map((value) => /* @__PURE__ */ jsx(Card, { render: /* @__PURE__ */ jsx("article", {}), className: "h-full", children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-2", children: [
|
|
330
|
+
/* @__PURE__ */ jsx("span", { className: "mb-4 grid size-11 place-items-center rounded-md border border-border sable-well", children: /* @__PURE__ */ jsx(Icon, { name: value.icon, className: "text-xl" }) }),
|
|
331
|
+
/* @__PURE__ */ jsx("h3", { className: "text-title-lg", children: value.title }),
|
|
332
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: value.body })
|
|
333
|
+
] }) }, value.title)) }) })
|
|
334
|
+
] }) }) }),
|
|
335
|
+
/* @__PURE__ */ jsx(Region, { as: "section", id: "how-it-works", "aria-labelledby": "brand-steps-title", max: "wide", className: "py-20 lg:py-28", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-12 lg:grid-cols-[1fr_2fr] lg:gap-16", children: [
|
|
336
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, className: "max-w-md", children: [
|
|
337
|
+
/* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "How it works" }),
|
|
338
|
+
/* @__PURE__ */ jsx("h2", { id: "brand-steps-title", className: "text-balance text-heading-1", children: "From request to sealed record." }),
|
|
339
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-md text-muted-foreground", children: "Teams are usually live within a week. Cove adapts to the routes you already run instead of asking you to start over." }),
|
|
340
|
+
/* @__PURE__ */ jsx("div", { className: "pt-3", children: /* @__PURE__ */ jsx(Button, { variant: "outline", iconEnd: /* @__PURE__ */ jsx(Icon, { name: "arrow-right" }), render: /* @__PURE__ */ jsx("a", { href: "/guides/rollout" }), children: "Read the rollout guide" }) })
|
|
341
|
+
] }),
|
|
342
|
+
/* @__PURE__ */ jsx("ol", { className: "grid gap-10 sm:grid-cols-3 sm:gap-6", children: steps.map((step) => /* @__PURE__ */ jsxs("li", { className: "flex flex-col gap-2 border-t border-border-strong pt-6", children: [
|
|
343
|
+
/* @__PURE__ */ jsx("span", { className: "text-mono-sm text-muted-foreground", children: step.number }),
|
|
344
|
+
/* @__PURE__ */ jsx("h3", { className: "mt-3 text-title-lg sm:mt-6", children: step.title }),
|
|
345
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: step.body })
|
|
346
|
+
] }, step.number)) })
|
|
347
|
+
] }) }),
|
|
348
|
+
/* @__PURE__ */ jsx(Region, { as: "section", id: "customers", "aria-label": "Customer story", max: "wide", className: "pb-20 lg:pb-28", children: /* @__PURE__ */ jsx(Card, { render: /* @__PURE__ */ jsx("figure", {}), children: /* @__PURE__ */ jsxs("div", { className: "grid lg:grid-cols-[1.5fr_1fr]", children: [
|
|
349
|
+
/* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col justify-between gap-10 sm:p-10", children: [
|
|
350
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
351
|
+
/* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Customer story" }),
|
|
352
|
+
/* @__PURE__ */ jsx("blockquote", { className: "text-balance text-heading-3 sm:text-heading-2", children: "\u201CWe used to chase approvals across four tools. Now every request has an owner, a deadline, and a record, and our auditors stopped asking for screenshots.\u201D" })
|
|
353
|
+
] }),
|
|
354
|
+
/* @__PURE__ */ jsxs("figcaption", { className: "flex items-center gap-3", children: [
|
|
355
|
+
/* @__PURE__ */ jsx(Avatar, { size: "lg", fallback: "MO" }),
|
|
356
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
357
|
+
/* @__PURE__ */ jsx("p", { className: "text-title-sm", children: "Maya Okafor" }),
|
|
358
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: "Head of operations, Fielddesk" })
|
|
359
|
+
] })
|
|
360
|
+
] })
|
|
232
361
|
] }),
|
|
233
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
234
|
-
|
|
235
|
-
/* @__PURE__ */
|
|
236
|
-
|
|
237
|
-
|
|
362
|
+
/* @__PURE__ */ jsx("div", { className: "border-border max-lg:border-t lg:border-s", children: /* @__PURE__ */ jsx(CardContent, { className: "flex h-full flex-col justify-center gap-6 sm:p-10", children: outcomes.map((outcome, index) => /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
363
|
+
index > 0 && /* @__PURE__ */ jsx(Separator, {}),
|
|
364
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
365
|
+
/* @__PURE__ */ jsx("p", { className: "text-heading-1 sm:text-display-lg", children: outcome.value }),
|
|
366
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-body-sm text-muted-foreground", children: outcome.label })
|
|
367
|
+
] })
|
|
368
|
+
] }, outcome.label)) }) })
|
|
238
369
|
] }) }) }),
|
|
239
|
-
/* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "
|
|
240
|
-
/* @__PURE__ */
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
370
|
+
/* @__PURE__ */ jsx(Region, { as: "section", "aria-labelledby": "brand-cta-title", max: "wide", className: "pb-20 lg:pb-28", children: /* @__PURE__ */ jsx("div", { className: "dark", children: /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-border bg-card px-6 py-12 text-card-foreground sm:px-12 sm:py-16", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-8 lg:flex-row lg:items-end lg:justify-between", children: [
|
|
371
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 3, className: "max-w-2xl", children: [
|
|
372
|
+
/* @__PURE__ */ jsx("h2", { id: "brand-cta-title", className: "text-balance text-heading-1", children: "See your own approval paths in Cove." }),
|
|
373
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-lg text-muted-foreground", children: "A 30-minute walkthrough with your routes, your roles, and your records. No slides." })
|
|
374
|
+
] }),
|
|
375
|
+
/* @__PURE__ */ jsxs(Row, { gap: 2, wrap: true, children: [
|
|
376
|
+
/* @__PURE__ */ jsx(Button, { size: "cta", render: /* @__PURE__ */ jsx("a", { href: "/demo" }), children: "Book a walkthrough" }),
|
|
377
|
+
/* @__PURE__ */ jsx(Button, { size: "cta", variant: "ghost", render: /* @__PURE__ */ jsx("a", { href: "/pricing" }), children: "View pricing" })
|
|
378
|
+
] })
|
|
379
|
+
] }) }) }) })
|
|
244
380
|
] }),
|
|
245
|
-
/* @__PURE__ */ jsx("footer", { className: "border-t border-border bg-muted/35", children: /* @__PURE__ */ jsxs(Region, { max: "wide", className: "py-
|
|
246
|
-
/* @__PURE__ */ jsxs("div", { className: "grid gap-
|
|
247
|
-
/* @__PURE__ */ jsxs(Stack, { gap:
|
|
381
|
+
/* @__PURE__ */ jsx("footer", { className: "border-t border-border bg-muted/35", children: /* @__PURE__ */ jsxs(Region, { max: "wide", className: "py-12 sm:py-16", children: [
|
|
382
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-10 lg:grid-cols-[1fr_1.35fr_1fr] lg:gap-8", children: [
|
|
383
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 4, className: "max-w-sm", children: [
|
|
248
384
|
/* @__PURE__ */ jsxs("a", { href: "/", className: "flex w-fit items-center gap-2 rounded-full motion-interactive motion-press", children: [
|
|
249
385
|
/* @__PURE__ */ jsx("span", { className: "grid size-9 place-items-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsx(Icon, { name: "sparkles", className: "text-sm" }) }),
|
|
250
386
|
/* @__PURE__ */ jsx("span", { className: "text-title-lg", children: "Cove" })
|
|
251
387
|
] }),
|
|
252
388
|
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: "A calm operating surface for teams that need trust, speed, and context without the noise." }),
|
|
253
|
-
/* @__PURE__ */
|
|
254
|
-
/* @__PURE__ */ jsx(Badge, { size: "sm", icon: /* @__PURE__ */ jsx(Icon, { name: "lock" }), children: "Private" }),
|
|
255
|
-
/* @__PURE__ */ jsx(Badge, { tone: "success", size: "sm", icon: /* @__PURE__ */ jsx(Icon, { name: "circle-check" }), children: "Live" })
|
|
256
|
-
] })
|
|
389
|
+
/* @__PURE__ */ jsx(Badge, { appearance: "plain", tone: "success", dot: true, children: "All systems normal" })
|
|
257
390
|
] }),
|
|
258
|
-
/* @__PURE__ */ jsx("nav", { "aria-label": "Footer", className: "grid
|
|
259
|
-
/* @__PURE__ */ jsx("h2", { className: "text-label-md
|
|
391
|
+
/* @__PURE__ */ jsx("nav", { "aria-label": "Footer", className: "grid grid-cols-3 gap-6", children: footerGroups.map((group) => /* @__PURE__ */ jsxs("div", { children: [
|
|
392
|
+
/* @__PURE__ */ jsx("h2", { className: "text-label-md", children: group.title }),
|
|
260
393
|
/* @__PURE__ */ jsx("ul", { className: "mt-3 space-y-1", children: group.links.map((link) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
261
394
|
"a",
|
|
262
395
|
{
|
|
@@ -266,62 +399,38 @@ function BrandLandingPage() {
|
|
|
266
399
|
}
|
|
267
400
|
) }, link.label)) })
|
|
268
401
|
] }, group.title)) }),
|
|
269
|
-
/* @__PURE__ */ jsxs(Card, { children: [
|
|
402
|
+
/* @__PURE__ */ jsxs(Card, { className: "h-fit", children: [
|
|
270
403
|
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
271
|
-
/* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h2", { children: "
|
|
272
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "One
|
|
273
|
-
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx("span", { className: "grid size-9 place-items-center rounded-full bg-highlight text-highlight-foreground", children: /* @__PURE__ */ jsx(Icon, { name: "sparkles", className: "text-sm" }) }) })
|
|
404
|
+
/* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h2", { children: "The Cove digest" }) }),
|
|
405
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "One note a month on trusted workflows and product releases." })
|
|
274
406
|
] }),
|
|
275
|
-
/* @__PURE__ */
|
|
276
|
-
/* @__PURE__ */
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
] }),
|
|
290
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2", children: footerSignals.map((signal) => /* @__PURE__ */ jsxs("div", { className: "rounded-md bg-muted p-3", children: [
|
|
291
|
-
/* @__PURE__ */ jsx("p", { className: "text-mono-sm", children: signal.value }),
|
|
292
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-caption text-muted-foreground", children: signal.label })
|
|
293
|
-
] }, signal.label)) })
|
|
294
|
-
] })
|
|
407
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("form", { action: "/newsletter", className: "grid gap-2 sm:grid-cols-[1fr_auto]", children: [
|
|
408
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "brand-footer-email", className: "sr-only", children: "Work email" }),
|
|
409
|
+
/* @__PURE__ */ jsx(
|
|
410
|
+
Input,
|
|
411
|
+
{
|
|
412
|
+
id: "brand-footer-email",
|
|
413
|
+
name: "email",
|
|
414
|
+
type: "email",
|
|
415
|
+
autoComplete: "email",
|
|
416
|
+
placeholder: "you@company.com"
|
|
417
|
+
}
|
|
418
|
+
),
|
|
419
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", className: "w-full sm:w-auto", children: "Subscribe" })
|
|
420
|
+
] }) })
|
|
295
421
|
] })
|
|
296
422
|
] }),
|
|
297
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-
|
|
423
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-12 flex flex-col gap-3 border-t border-border pt-6 text-caption text-muted-foreground sm:flex-row sm:items-center sm:justify-between", children: [
|
|
298
424
|
/* @__PURE__ */ jsx("p", { children: "\xA9 2026 Cove. All rights reserved." }),
|
|
299
|
-
/* @__PURE__ */
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
/* @__PURE__ */ jsx(
|
|
309
|
-
"a",
|
|
310
|
-
{
|
|
311
|
-
href: "/terms",
|
|
312
|
-
className: "flex min-h-9 min-w-9 items-center motion-interactive hover:text-foreground",
|
|
313
|
-
children: "Terms"
|
|
314
|
-
}
|
|
315
|
-
),
|
|
316
|
-
/* @__PURE__ */ jsx(
|
|
317
|
-
"a",
|
|
318
|
-
{
|
|
319
|
-
href: "/contact",
|
|
320
|
-
className: "flex min-h-9 min-w-9 items-center motion-interactive hover:text-foreground",
|
|
321
|
-
children: "Contact"
|
|
322
|
-
}
|
|
323
|
-
)
|
|
324
|
-
] })
|
|
425
|
+
/* @__PURE__ */ jsx(Row, { gap: 4, wrap: true, children: legalLinks.map((link) => /* @__PURE__ */ jsx(
|
|
426
|
+
"a",
|
|
427
|
+
{
|
|
428
|
+
href: link.href,
|
|
429
|
+
className: "flex min-h-9 min-w-9 items-center motion-interactive hover:text-foreground",
|
|
430
|
+
children: link.label
|
|
431
|
+
},
|
|
432
|
+
link.label
|
|
433
|
+
)) })
|
|
325
434
|
] })
|
|
326
435
|
] }) })
|
|
327
436
|
] });
|
|
@@ -330,26 +439,30 @@ function BrandLandingPage() {
|
|
|
330
439
|
// src/patterns/brand-landing-page.meta.ts
|
|
331
440
|
var brandLandingPageMeta = {
|
|
332
441
|
name: "BrandLandingPage",
|
|
333
|
-
summary: "High-fidelity brand/product landing page: pill navigation, editorial hero type, a dark product scene,
|
|
442
|
+
summary: "High-fidelity brand/product landing page: pill navigation, editorial hero type, a dark product scene, a value section, a how-it-works sequence, a customer story with outcomes, a closing CTA band, and a complete branded footer.",
|
|
334
443
|
intent: "Use this for a polished product marketing route that still needs to show a credible app surface. It is warmer and more tactile than the canonical LandingPage, but avoids trend-driven neon and generic hype cues.",
|
|
335
444
|
notes: [
|
|
336
|
-
"
|
|
445
|
+
"The story runs promise \u2192 product \u2192 value \u2192 process \u2192 proof \u2192 ask: hero, product scene, 'Why Cove' value cards, a three-step how-it-works list, a customer story with outcome figures, then one closing CTA. Each section adds new evidence; no number is repeated from the product scene.",
|
|
446
|
+
"One display headline owns the first viewport; supporting copy stays in body-lg at prose measure. Section headings are heading-1 under a `text-overline` eyebrow.",
|
|
337
447
|
"The nav spends one compact pill surface, with a clear brand signal and two primary paths: sign in and request access.",
|
|
338
|
-
"The product scene
|
|
339
|
-
"The
|
|
340
|
-
"
|
|
341
|
-
"
|
|
342
|
-
"
|
|
448
|
+
"The product scene and the closing CTA band are dark islands: a `dark` class on the wrapper re-scopes every semantic token, so surfaces, muted ink, Badge tones and Buttons take the theme's own dark values in either page mode. Never fake a dark panel with overlay ink at reduced opacity \u2014 status tones tuned for a light page vanish on it.",
|
|
449
|
+
"The scene is a picture of the product: `aria-hidden` + `inert` keep its sample button and figures out of the tab order and the outline. On narrow screens it drops the side column and keeps the signal map, so it scales instead of stacking into a second page.",
|
|
450
|
+
"The chart keeps past weeks on the quiet `secondary` fill and gives the current week the one accent, so the signal reads at a glance.",
|
|
451
|
+
"Value cards are real Cards with a neutral `sable-well` icon tile; the customer story is a Card rendered as a figure, with outcome figures split by Separator. The scene's nested panels take `rounded-concentric-xl/<inset>` from the shell's radius, so the corners stay parallel at any radius preset.",
|
|
452
|
+
"The footer closes the page with brand reassurance, a plain status Badge, a three-column sitemap (three columns at every width), a digest signup, and legal links.",
|
|
453
|
+
"All figures, names and the testimonial are sample content; replace them with real, verifiable claims."
|
|
343
454
|
],
|
|
344
455
|
a11y: [
|
|
345
456
|
"The brand nav is a labeled nav landmark with real links and visible text.",
|
|
457
|
+
"Each content section is a labelled region (by its h2, or aria-label for the customer story); the decorative product scene is hidden from assistive tech and inert.",
|
|
458
|
+
"The how-it-works steps are an ordered list; the testimonial is a figure with a blockquote and a figcaption naming the speaker.",
|
|
346
459
|
"The footer is a real footer landmark with a labeled footer nav, visible link text, and a labeled email field.",
|
|
347
|
-
"TextReveal preserves the full headline as one accessible label and honors reduced-motion policy.",
|
|
348
|
-
"Status dots are paired with visible text labels so color is never the only signal."
|
|
460
|
+
"TextReveal preserves the full headline as one accessible label and honors reduced-motion policy; Reveal shows the value cards immediately when motion is off.",
|
|
461
|
+
"Status dots and the status Badge are paired with visible text labels so color is never the only signal."
|
|
349
462
|
]
|
|
350
463
|
};
|
|
351
464
|
var pending = true;
|
|
352
|
-
var streamed = "
|
|
465
|
+
var streamed = "Yes, appends are already safe: your layout effect measures before paint. The jump is on the prepend path, which reads scrollHeight after the new rows";
|
|
353
466
|
var turns = [
|
|
354
467
|
{
|
|
355
468
|
id: "t_01",
|
|
@@ -359,6 +472,7 @@ var turns = [
|
|
|
359
472
|
{
|
|
360
473
|
id: "t_02",
|
|
361
474
|
mine: false,
|
|
475
|
+
step: "Read message-scroller.md",
|
|
362
476
|
body: "Almost always the anchor. If you preserve the scroll position by measuring the container, prepending history moves everything under the reader."
|
|
363
477
|
},
|
|
364
478
|
{
|
|
@@ -368,6 +482,14 @@ var turns = [
|
|
|
368
482
|
}
|
|
369
483
|
];
|
|
370
484
|
function ChatPanel() {
|
|
485
|
+
const assistant = /* @__PURE__ */ jsx(
|
|
486
|
+
Avatar,
|
|
487
|
+
{
|
|
488
|
+
size: "sm",
|
|
489
|
+
alt: "Assistant",
|
|
490
|
+
fallback: /* @__PURE__ */ jsx(Icon, { name: "sparkles", className: "text-base" })
|
|
491
|
+
}
|
|
492
|
+
);
|
|
371
493
|
return /* @__PURE__ */ jsx(Page, { as: "main", max: "feed", gap: 0, className: "py-6", children: /* @__PURE__ */ jsxs(
|
|
372
494
|
Card,
|
|
373
495
|
{
|
|
@@ -377,45 +499,78 @@ function ChatPanel() {
|
|
|
377
499
|
/* @__PURE__ */ jsxs(CardHeader, { className: "border-b border-border pb-(--card-p)", children: [
|
|
378
500
|
/* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h1", { id: "chat-title", children: "Scroll behaviour" }) }),
|
|
379
501
|
/* @__PURE__ */ jsx(CardDescription, { children: "Assistant \xB7 answers from your team's docs" }),
|
|
380
|
-
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */
|
|
502
|
+
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", children: [
|
|
503
|
+
/* @__PURE__ */ jsx(Icon, { name: "edit" }),
|
|
504
|
+
"New chat"
|
|
505
|
+
] }) })
|
|
381
506
|
] }),
|
|
382
507
|
/* @__PURE__ */ jsxs(MessageScroller, { className: "min-h-0 flex-1 p-(--card-p)", children: [
|
|
383
508
|
/* @__PURE__ */ jsx(Marker, { variant: "separator", children: "Today" }),
|
|
384
|
-
turns.map((turn, i) =>
|
|
509
|
+
turns.map((turn, i) => {
|
|
510
|
+
const continues = i > 0 && turns[i - 1].mine === turn.mine;
|
|
511
|
+
return /* @__PURE__ */ jsx(
|
|
512
|
+
Message,
|
|
513
|
+
{
|
|
514
|
+
messageId: turn.id,
|
|
515
|
+
align: turn.mine ? "end" : "start",
|
|
516
|
+
grouped: continues,
|
|
517
|
+
avatar: turn.mine ? void 0 : assistant,
|
|
518
|
+
header: turn.step ? /* @__PURE__ */ jsx(Marker, { variant: "note", icon: "book", className: "justify-start", children: turn.step }) : void 0,
|
|
519
|
+
children: /* @__PURE__ */ jsx(
|
|
520
|
+
Bubble,
|
|
521
|
+
{
|
|
522
|
+
variant: turn.mine ? "primary" : "muted",
|
|
523
|
+
align: turn.mine ? "end" : "start",
|
|
524
|
+
tail: !continues,
|
|
525
|
+
children: turn.body
|
|
526
|
+
}
|
|
527
|
+
)
|
|
528
|
+
},
|
|
529
|
+
turn.id
|
|
530
|
+
);
|
|
531
|
+
}),
|
|
532
|
+
/* @__PURE__ */ jsx(
|
|
385
533
|
Message,
|
|
386
534
|
{
|
|
387
|
-
messageId:
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
535
|
+
messageId: "t_04",
|
|
536
|
+
grouped: true,
|
|
537
|
+
avatar: assistant,
|
|
538
|
+
footer: /* @__PURE__ */ jsxs(Toolbar, { "aria-label": "Answer actions", children: [
|
|
539
|
+
/* @__PURE__ */ jsx(SimpleTooltip, { content: "Copy answer", children: /* @__PURE__ */ jsx(ToolbarButton, { size: "icon-sm", "aria-label": "Copy answer", children: /* @__PURE__ */ jsx(Icon, { name: "copy" }) }) }),
|
|
540
|
+
/* @__PURE__ */ jsx(SimpleTooltip, { content: "Regenerate", children: /* @__PURE__ */ jsx(ToolbarButton, { size: "icon-sm", "aria-label": "Regenerate answer", children: /* @__PURE__ */ jsx(Icon, { name: "refresh" }) }) })
|
|
541
|
+
] }),
|
|
542
|
+
children: /* @__PURE__ */ jsxs(Bubble, { variant: "plain", className: "max-w-full", children: [
|
|
543
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-md text-foreground", children: "Here is the shape of it:" }),
|
|
544
|
+
/* @__PURE__ */ jsx(
|
|
545
|
+
CodeBlock,
|
|
546
|
+
{
|
|
547
|
+
code: "const shift = anchor.offsetTop - lastTop\nif (shift !== 0) viewport.scrollTop += shift",
|
|
548
|
+
filename: "preserve.ts"
|
|
549
|
+
}
|
|
550
|
+
)
|
|
551
|
+
] })
|
|
552
|
+
}
|
|
553
|
+
),
|
|
554
|
+
/* @__PURE__ */ jsxs(Message, { messageId: "t_05", align: "end", children: [
|
|
555
|
+
/* @__PURE__ */ jsx(Bubble, { variant: "primary", align: "end", children: "Here's ours. Does the same fix cover new messages arriving?" }),
|
|
556
|
+
/* @__PURE__ */ jsx(
|
|
557
|
+
Attachment,
|
|
558
|
+
{
|
|
559
|
+
name: "scroller.tsx",
|
|
560
|
+
meta: "4.2 KB \xB7 TSX",
|
|
561
|
+
onOpen: () => void 0
|
|
562
|
+
}
|
|
563
|
+
)
|
|
416
564
|
] }),
|
|
417
|
-
/* @__PURE__ */ jsx(
|
|
418
|
-
|
|
565
|
+
/* @__PURE__ */ jsx(
|
|
566
|
+
Message,
|
|
567
|
+
{
|
|
568
|
+
messageId: "t_06",
|
|
569
|
+
avatar: assistant,
|
|
570
|
+
header: /* @__PURE__ */ jsx(Marker, { variant: "note", icon: "file", className: "justify-start", children: "Read scroller.tsx" }),
|
|
571
|
+
children: /* @__PURE__ */ jsx(Bubble, { variant: "plain", children: /* @__PURE__ */ jsx(StreamingText, { text: streamed, streaming: pending }) })
|
|
572
|
+
}
|
|
573
|
+
)
|
|
419
574
|
] }),
|
|
420
575
|
/* @__PURE__ */ jsx(CardFooter, { className: "border-t border-border pt-(--card-p)", children: /* @__PURE__ */ jsx(
|
|
421
576
|
ChatComposer,
|
|
@@ -424,6 +579,7 @@ function ChatPanel() {
|
|
|
424
579
|
placeholder: "Ask a follow-up\u2026",
|
|
425
580
|
busy: pending,
|
|
426
581
|
onStop: () => void 0,
|
|
582
|
+
stopLabel: "Stop answering",
|
|
427
583
|
onAttach: () => void 0,
|
|
428
584
|
footer: "Enter to send \xB7 Shift+Enter for a new line",
|
|
429
585
|
actions: /* @__PURE__ */ jsxs(
|
|
@@ -473,7 +629,11 @@ var chatPanelMeta = {
|
|
|
473
629
|
"Region cap is `feed` (640px) \u2014 a conversation is a reading column, and the Bubble measure cap is tuned to the same assumption.",
|
|
474
630
|
"The composer sits outside the scroll region, in a bordered CardFooter, so it stays put while the transcript moves.",
|
|
475
631
|
"Grouping is derived from the data (`turns[i - 1].mine === turn.mine`) rather than hand-set, and the tail is dropped on continuations \u2014 that pairing is what makes a run read as one block.",
|
|
476
|
-
'
|
|
632
|
+
'What the assistant did before answering \u2014 a doc it read, a tool it ran \u2014 is a `Marker variant="note"` in that message\'s `header`, so it reads as part of the turn rather than as an event between turns.',
|
|
633
|
+
'The assistant\'s long-form answer uses `variant="plain"` and continues the same grouped run. A tinted box around a code block reads as a quote rather than as a turn. A plain Bubble sizes to its content with no measure cap, so it takes `max-w-full` here: bounded by the column, a wide code line scrolls inside the CodeBlock instead of pushing the transcript sideways on a phone.',
|
|
634
|
+
"A settled answer carries its actions (copy, regenerate) in the Message `footer` as a `Toolbar` of icon `ToolbarButton`s with tooltips \u2014 one tab stop, arrow keys between them. The answer still streaming has none: there is nothing to copy or regenerate until it settles, and Stop lives in the composer.",
|
|
635
|
+
"One turn, one Message: the reader's question and the file it refers to stack inside a single end-aligned Message rather than being sent as two.",
|
|
636
|
+
'Assistant turns share one Avatar with a glyph fallback and `alt="Assistant"` \u2014 initials like "AI" read as a person, and a fallback-only avatar is otherwise unnamed.',
|
|
477
637
|
"The answer in flight is a `StreamingText` in a `plain` Bubble, and ONE `pending` flag drives both it and the composer's `busy`. They are the same wait reported at the two ends of the panel; letting them drift gives you a composer still saying 'sending' under an answer that already finished.",
|
|
478
638
|
"A spinner `Marker` (or `ThinkingOrb`) belongs BEFORE the first token and is replaced once tokens land, not kept alongside. Two indicators for one wait read as two things happening \u2014 which is also why `StreamingText` takes `caret={false}` when something adjacent already reports it.",
|
|
479
639
|
'MessageScroller needs no configuring for this: it re-follows on "a message grew" (ResizeObserver) as well as "a message arrived", so the newest token stays pinned while a reader who has scrolled up is left alone.',
|
|
@@ -482,40 +642,77 @@ var chatPanelMeta = {
|
|
|
482
642
|
a11y: [
|
|
483
643
|
"The panel header's title is the page's <h1> (an h1 inside CardTitle, which carries the type); the transcript is a scroll region with native overflow, so keyboard scrolling works unchanged.",
|
|
484
644
|
"The answer in flight carries `aria-busy` rather than sitting in a live region. That is deliberate: a live region around streamed text announces every token, which is unusable. Announce once when the turn settles, from whatever owns the turn \u2014 the incomplete state is still exposed, just not narrated.",
|
|
485
|
-
|
|
645
|
+
'The composer is a real <form> whose Enter handling skips IME composition, and every icon-only control in the panel carries an accessible label \u2014 the answer actions also carry a tooltip, and the stop control is named for what it stops (`stopLabel="Stop answering"`).'
|
|
486
646
|
]
|
|
487
647
|
};
|
|
488
|
-
var STATUS_TONE = {
|
|
489
|
-
var
|
|
648
|
+
var STATUS_TONE = { live: "success", scheduled: "info", "sold-out": "edition", closed: "neutral" };
|
|
649
|
+
var STATUS_LABEL = { live: "Live", scheduled: "Scheduled", "sold-out": "Sold out", closed: "Closed" };
|
|
490
650
|
var MIN = 60 * 1e3;
|
|
491
651
|
var HOUR = 60 * MIN;
|
|
652
|
+
var DAY = 24 * HOUR;
|
|
653
|
+
var usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 });
|
|
654
|
+
var usdCents = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
|
|
655
|
+
var shortDate = (offset) => new Date(Date.now() + offset).toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
492
656
|
var stats = [
|
|
493
|
-
{ label: "Revenue", value: "$
|
|
494
|
-
{ label: "Collects", value: "1,284", delta: "+3.1%", up: true, note: "182
|
|
495
|
-
{ label: "
|
|
496
|
-
{ label: "Sell-through", value: "64%", delta: "\u22121.8%", up: false, note: "
|
|
657
|
+
{ label: "Revenue", value: "$18,240", delta: "+8.2%", up: true, note: "vs. $16,857" },
|
|
658
|
+
{ label: "Collects", value: "1,284", delta: "+3.1%", up: true, note: "182 first-time" },
|
|
659
|
+
{ label: "Collectors", value: "312", delta: "+12.4%", up: true, note: "Best month yet" },
|
|
660
|
+
{ label: "Sell-through", value: "64%", delta: "\u22121.8%", up: false, note: "6 open editions" }
|
|
497
661
|
];
|
|
498
|
-
var
|
|
499
|
-
{ id: "
|
|
500
|
-
{ id: "
|
|
501
|
-
{ id: "
|
|
502
|
-
{ id: "
|
|
503
|
-
{ id: "
|
|
504
|
-
{ id: "
|
|
505
|
-
{ id: "
|
|
662
|
+
var editions = [
|
|
663
|
+
{ id: "ed_01", title: "Gold Rush", status: "live", sold: 184, supply: 250, price: 48, released: -6 * DAY },
|
|
664
|
+
{ id: "ed_02", title: "Quiet Harbor", status: "sold-out", sold: 100, supply: 100, price: 120, released: -19 * DAY },
|
|
665
|
+
{ id: "ed_03", title: "Dusk, no. 4", status: "live", sold: 61, supply: 150, price: 64, released: -2 * DAY },
|
|
666
|
+
{ id: "ed_04", title: "Tidewater", status: "live", sold: 212, supply: 300, price: 36, released: -27 * DAY },
|
|
667
|
+
{ id: "ed_05", title: "Salt Study", status: "closed", sold: 38, supply: 75, price: 90, released: -44 * DAY },
|
|
668
|
+
{ id: "ed_06", title: "Paper Moon", status: "sold-out", sold: 50, supply: 50, price: 220, released: -58 * DAY },
|
|
669
|
+
{ id: "ed_07", title: "Night Signal", status: "scheduled", sold: 0, supply: 200, price: 56, released: 3 * DAY },
|
|
670
|
+
{ id: "ed_08", title: "Low Tide, Blue", status: "closed", sold: 72, supply: 120, price: 42, released: -71 * DAY },
|
|
671
|
+
{ id: "ed_09", title: "Ember Field", status: "sold-out", sold: 80, supply: 80, price: 140, released: -86 * DAY }
|
|
672
|
+
];
|
|
673
|
+
var editionCount = 24;
|
|
674
|
+
var payout = {
|
|
675
|
+
amount: 8214.6,
|
|
676
|
+
date: 5 * DAY,
|
|
677
|
+
lines: [
|
|
678
|
+
{ label: "Sales", value: 9127.33 },
|
|
679
|
+
{ label: "Platform fee (10%)", value: -912.73 },
|
|
680
|
+
{ label: "Deposits to", value: "Checking \u2022\u20224821" }
|
|
681
|
+
]
|
|
682
|
+
};
|
|
683
|
+
var collectors = [
|
|
684
|
+
{ id: "co_01", name: "Nina Simone", handle: "@nina", initials: "NS", pieces: 14, spent: 1460 },
|
|
685
|
+
{ id: "co_02", name: "Grace Hopper", handle: "@grace", initials: "GH", pieces: 11, spent: 1128 },
|
|
686
|
+
{ id: "co_03", name: "Frida Kahlo", handle: "@frida", initials: "FK", pieces: 9, spent: 942 },
|
|
687
|
+
{ id: "co_04", name: "Mary Shelley", handle: "@mary", initials: "MS", pieces: 6, spent: 588 }
|
|
688
|
+
];
|
|
689
|
+
var upcoming = [
|
|
690
|
+
{ id: "up_01", title: "Night Signal", detail: "Edition of 200 \xB7 $56", at: 3 * DAY },
|
|
691
|
+
{ id: "up_02", title: "Tidewater, second printing", detail: "Edition of 100 \xB7 $40", at: 12 * DAY },
|
|
692
|
+
{ id: "up_03", title: "Dusk, no. 5", detail: "Edition of 150 \xB7 $64", at: 26 * DAY },
|
|
693
|
+
{ id: "up_04", title: "Quiet Harbor, artist proofs", detail: "Edition of 10 \xB7 $480", at: 34 * DAY }
|
|
506
694
|
];
|
|
507
695
|
var activity = [
|
|
508
|
-
{ id: "ac_01", who: "Nina Simone", initials: "NS", what: "collected Gold Rush
|
|
509
|
-
{ id: "ac_02", who: "Grace Hopper", initials: "GH", what: "
|
|
510
|
-
{ id: "ac_03", who: "
|
|
511
|
-
{ id: "ac_04", who: "Mary Shelley", initials: "MS", what: "
|
|
512
|
-
{ id: "ac_05", who: "
|
|
696
|
+
{ id: "ac_01", who: "Nina Simone", initials: "NS", what: "collected Gold Rush \xB7 185 of 250", when: 2 * MIN },
|
|
697
|
+
{ id: "ac_02", who: "Grace Hopper", initials: "GH", what: "collected Dusk, no. 4 \xB7 61 of 150", when: 26 * MIN },
|
|
698
|
+
{ id: "ac_03", who: "Frida Kahlo", initials: "FK", what: "tipped $20 on Tidewater", when: 1 * HOUR },
|
|
699
|
+
{ id: "ac_04", who: "Mary Shelley", initials: "MS", what: "started following you", when: 3 * HOUR },
|
|
700
|
+
{ id: "ac_05", who: "Nina Simone", initials: "NS", what: "set a reminder for Night Signal", when: 6 * HOUR }
|
|
513
701
|
];
|
|
514
702
|
function DashboardPage() {
|
|
515
703
|
return /* @__PURE__ */ jsx(
|
|
516
704
|
AppShell,
|
|
517
705
|
{
|
|
518
|
-
header: /* @__PURE__ */ jsx(
|
|
706
|
+
header: /* @__PURE__ */ jsx(
|
|
707
|
+
AppHeader,
|
|
708
|
+
{
|
|
709
|
+
title: /* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", children: [
|
|
710
|
+
/* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-xl" }),
|
|
711
|
+
/* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" })
|
|
712
|
+
] }),
|
|
713
|
+
actions: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: "AL", alt: "Ada Lovelace" })
|
|
714
|
+
}
|
|
715
|
+
),
|
|
519
716
|
bottomNav: /* @__PURE__ */ jsxs(BottomNav, { "aria-label": "Primary", children: [
|
|
520
717
|
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "grid" }), label: "Overview", showLabel: true, active: true, render: /* @__PURE__ */ jsx("a", { href: "/" }) }),
|
|
521
718
|
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }), label: "Editions", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/editions" }) }),
|
|
@@ -524,75 +721,215 @@ function DashboardPage() {
|
|
|
524
721
|
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), label: "Settings", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/settings" }) })
|
|
525
722
|
] }),
|
|
526
723
|
sidebar: /* @__PURE__ */ jsxs(Sidebar, { children: [
|
|
527
|
-
/* @__PURE__ */ jsx(SidebarHeader, { children: /* @__PURE__ */
|
|
724
|
+
/* @__PURE__ */ jsx(SidebarHeader, { children: /* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", className: "px-3", children: [
|
|
725
|
+
/* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-xl" }),
|
|
726
|
+
/* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" })
|
|
727
|
+
] }) }),
|
|
528
728
|
/* @__PURE__ */ jsxs(SidebarNav, { children: [
|
|
529
729
|
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "grid" }), label: "Overview", active: true, render: /* @__PURE__ */ jsx("a", { href: "/" }) }),
|
|
530
730
|
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }), label: "Editions", render: /* @__PURE__ */ jsx("a", { href: "/editions" }) }),
|
|
531
731
|
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "user" }), label: "Collectors", render: /* @__PURE__ */ jsx("a", { href: "/collectors" }) }),
|
|
532
|
-
/* @__PURE__ */ jsx(
|
|
732
|
+
/* @__PURE__ */ jsx(
|
|
733
|
+
SidebarItem,
|
|
734
|
+
{
|
|
735
|
+
icon: /* @__PURE__ */ jsx(Icon, { name: "bell" }),
|
|
736
|
+
label: "Activity",
|
|
737
|
+
badge: /* @__PURE__ */ jsx(NotificationBadge, { count: 4, tone: "neutral" }),
|
|
738
|
+
render: /* @__PURE__ */ jsx("a", { href: "/activity" })
|
|
739
|
+
}
|
|
740
|
+
),
|
|
533
741
|
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), label: "Settings", render: /* @__PURE__ */ jsx("a", { href: "/settings" }) })
|
|
534
|
-
] })
|
|
742
|
+
] }),
|
|
743
|
+
/* @__PURE__ */ jsx(SidebarFooter, { className: "[--foreground:var(--sidebar-foreground)]", children: /* @__PURE__ */ jsx(
|
|
744
|
+
Entity,
|
|
745
|
+
{
|
|
746
|
+
className: "[--entity-py:0]",
|
|
747
|
+
leading: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: "AL", alt: "" }),
|
|
748
|
+
title: "Ada Lovelace",
|
|
749
|
+
action: /* @__PURE__ */ jsxs(DotsMenu, { label: "Account", size: "icon-sm", children: [
|
|
750
|
+
/* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/@ada" }), children: "View profile" }),
|
|
751
|
+
/* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/settings" }), children: "Settings" }),
|
|
752
|
+
/* @__PURE__ */ jsx(MenuSeparator, {}),
|
|
753
|
+
/* @__PURE__ */ jsx(MenuItem, { children: "Sign out" })
|
|
754
|
+
] })
|
|
755
|
+
}
|
|
756
|
+
) })
|
|
535
757
|
] }),
|
|
536
758
|
children: /* @__PURE__ */ jsxs(Page, { max: "wide", gap: 4, className: "py-6", children: [
|
|
537
759
|
/* @__PURE__ */ jsx(
|
|
538
760
|
PageHeader,
|
|
539
761
|
{
|
|
540
762
|
title: "Overview",
|
|
541
|
-
description: "Sales
|
|
763
|
+
description: "Sales and collector activity for the last 30 days.",
|
|
542
764
|
actions: /* @__PURE__ */ jsxs(Row, { gap: 2, children: [
|
|
543
|
-
/* @__PURE__ */
|
|
544
|
-
|
|
765
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", children: [
|
|
766
|
+
/* @__PURE__ */ jsx(Icon, { name: "download" }),
|
|
767
|
+
"Export"
|
|
768
|
+
] }),
|
|
769
|
+
/* @__PURE__ */ jsxs(Button, { children: [
|
|
770
|
+
/* @__PURE__ */ jsx(Icon, { name: "plus" }),
|
|
771
|
+
"New edition"
|
|
772
|
+
] })
|
|
545
773
|
] })
|
|
546
774
|
}
|
|
547
775
|
),
|
|
548
|
-
/* @__PURE__ */ jsx(Grid, { cols: { base:
|
|
776
|
+
/* @__PURE__ */ jsx(Grid, { cols: { base: 2, xl: 4 }, gap: 3, children: stats.map((stat) => /* @__PURE__ */ jsxs(Card, { children: [
|
|
549
777
|
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
550
778
|
/* @__PURE__ */ jsx(CardDescription, { children: stat.label }),
|
|
551
|
-
/* @__PURE__ */ jsx(CardTitle, { className: "text-mono-xl", children: stat.value })
|
|
552
|
-
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsxs(Badge, { tone: stat.up ? "success" : "neutral", size: "sm", children: [
|
|
553
|
-
/* @__PURE__ */ jsx(Icon, { name: stat.up ? "chevron-up" : "chevron-down" }),
|
|
554
|
-
stat.delta
|
|
555
|
-
] }) })
|
|
779
|
+
/* @__PURE__ */ jsx(CardTitle, { className: "text-mono-xl", children: stat.value })
|
|
556
780
|
] }),
|
|
557
|
-
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */
|
|
781
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Row, { gap: 1, wrap: true, children: [
|
|
782
|
+
/* @__PURE__ */ jsx(
|
|
783
|
+
Badge,
|
|
784
|
+
{
|
|
785
|
+
tone: stat.up ? "success" : "warning",
|
|
786
|
+
size: "sm",
|
|
787
|
+
icon: /* @__PURE__ */ jsx(Icon, { name: stat.up ? "arrow-up" : "arrow-down" }),
|
|
788
|
+
children: stat.delta
|
|
789
|
+
}
|
|
790
|
+
),
|
|
791
|
+
/* @__PURE__ */ jsx("span", { className: "text-caption text-muted-foreground", children: stat.note })
|
|
792
|
+
] }) })
|
|
558
793
|
] }, stat.label)) }),
|
|
559
794
|
/* @__PURE__ */ jsxs(Grid, { cols: { base: 1, xl: 3 }, gap: 3, children: [
|
|
560
|
-
/* @__PURE__ */ jsxs(Card, { className: "xl:col-span-2", children: [
|
|
795
|
+
/* @__PURE__ */ jsxs(Card, { className: "flex flex-col xl:col-span-2", children: [
|
|
561
796
|
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
562
|
-
/* @__PURE__ */ jsx(CardTitle, { children: "
|
|
563
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "
|
|
564
|
-
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/deployments" }), children: "View all" }) })
|
|
797
|
+
/* @__PURE__ */ jsx(CardTitle, { children: "Editions" }),
|
|
798
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "Sales across your releases, newest first." })
|
|
565
799
|
] }),
|
|
566
|
-
/* @__PURE__ */ jsx(CardContent, { flush: true, children: /* @__PURE__ */ jsx(
|
|
800
|
+
/* @__PURE__ */ jsx(CardContent, { flush: true, className: "mt-4 flex-1", children: /* @__PURE__ */ jsx(
|
|
567
801
|
DataTable,
|
|
568
802
|
{
|
|
569
|
-
data:
|
|
803
|
+
data: editions,
|
|
570
804
|
rowKey: (row) => row.id,
|
|
805
|
+
defaultSort: { key: "released", direction: "desc" },
|
|
571
806
|
columns: [
|
|
572
|
-
{ key: "
|
|
573
|
-
{
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
807
|
+
{ key: "title", header: "Edition", sortable: true, className: "text-label-md whitespace-nowrap" },
|
|
808
|
+
{
|
|
809
|
+
key: "status",
|
|
810
|
+
header: "Status",
|
|
811
|
+
cell: (row) => /* @__PURE__ */ jsx(Badge, { appearance: "plain", tone: STATUS_TONE[row.status], dot: true, children: STATUS_LABEL[row.status] })
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
key: "sold",
|
|
815
|
+
header: "Sold",
|
|
816
|
+
sortable: true,
|
|
817
|
+
sortValue: (row) => row.sold / row.supply,
|
|
818
|
+
cell: (row) => /* @__PURE__ */ jsxs(Row, { gap: 3, align: "center", className: "min-w-40", children: [
|
|
819
|
+
/* @__PURE__ */ jsx(
|
|
820
|
+
Meter,
|
|
821
|
+
{
|
|
822
|
+
value: row.sold,
|
|
823
|
+
max: row.supply,
|
|
824
|
+
size: "sm",
|
|
825
|
+
tone: row.sold === row.supply ? "success" : "primary",
|
|
826
|
+
"aria-label": `${row.title} sold`,
|
|
827
|
+
getAriaValueText: (_, v) => `${v} of ${row.supply} sold`,
|
|
828
|
+
className: "flex-1"
|
|
829
|
+
}
|
|
830
|
+
),
|
|
831
|
+
/* @__PURE__ */ jsxs("span", { className: "text-mono-caption whitespace-nowrap text-muted-foreground", children: [
|
|
832
|
+
row.sold,
|
|
833
|
+
"/",
|
|
834
|
+
row.supply
|
|
835
|
+
] })
|
|
836
|
+
] })
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
key: "revenue",
|
|
840
|
+
header: "Revenue",
|
|
841
|
+
align: "end",
|
|
842
|
+
sortable: true,
|
|
843
|
+
sortValue: (row) => row.sold * row.price,
|
|
844
|
+
cell: (row) => row.sold > 0 ? /* @__PURE__ */ jsx("span", { className: "text-mono-sm", children: usd.format(row.sold * row.price) }) : /* @__PURE__ */ jsx("span", { className: "text-mono-sm text-muted-foreground", children: "\u2014" })
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
key: "released",
|
|
848
|
+
header: "Release",
|
|
849
|
+
align: "end",
|
|
850
|
+
sortable: true,
|
|
851
|
+
cell: (row) => /* @__PURE__ */ jsx("span", { className: "text-body-sm whitespace-nowrap text-muted-foreground", children: shortDate(row.released) })
|
|
852
|
+
}
|
|
577
853
|
]
|
|
578
854
|
}
|
|
579
|
-
) })
|
|
855
|
+
) }),
|
|
856
|
+
/* @__PURE__ */ jsxs(CardFooter, { className: "justify-between", children: [
|
|
857
|
+
/* @__PURE__ */ jsxs("span", { className: "text-caption text-muted-foreground", children: [
|
|
858
|
+
"Showing ",
|
|
859
|
+
editions.length,
|
|
860
|
+
" of ",
|
|
861
|
+
editionCount,
|
|
862
|
+
" editions"
|
|
863
|
+
] }),
|
|
864
|
+
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/editions" }), children: [
|
|
865
|
+
"View all editions",
|
|
866
|
+
/* @__PURE__ */ jsx(Icon, { name: "arrow-right" })
|
|
867
|
+
] })
|
|
868
|
+
] })
|
|
580
869
|
] }),
|
|
581
|
-
/* @__PURE__ */ jsxs(
|
|
870
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
871
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
872
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
873
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "Next payout" }),
|
|
874
|
+
/* @__PURE__ */ jsx(CardTitle, { className: "text-mono-xl", children: usdCents.format(payout.amount) }),
|
|
875
|
+
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, { tone: "info", size: "sm", icon: /* @__PURE__ */ jsx(Icon, { name: "calendar" }), children: shortDate(payout.date) }) })
|
|
876
|
+
] }),
|
|
877
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("dl", { className: "sable-list-dl text-body-sm", children: payout.lines.map((line) => /* @__PURE__ */ jsxs("div", { className: "contents", children: [
|
|
878
|
+
/* @__PURE__ */ jsx("dt", { children: line.label }),
|
|
879
|
+
/* @__PURE__ */ jsx("dd", { className: typeof line.value === "number" ? "text-end text-mono-sm" : "text-end", children: typeof line.value === "number" ? usdCents.format(line.value) : line.value })
|
|
880
|
+
] }, line.label)) }) })
|
|
881
|
+
] }),
|
|
882
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
883
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
884
|
+
/* @__PURE__ */ jsx(CardTitle, { children: "Top collectors" }),
|
|
885
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "By spend this month." }),
|
|
886
|
+
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/collectors" }), children: "View all" }) })
|
|
887
|
+
] }),
|
|
888
|
+
/* @__PURE__ */ jsx(CardList, { children: collectors.map((person) => /* @__PURE__ */ jsx(
|
|
889
|
+
Entity,
|
|
890
|
+
{
|
|
891
|
+
leading: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: person.initials, alt: "" }),
|
|
892
|
+
title: person.name,
|
|
893
|
+
subtitle: `${person.handle} \xB7 ${person.pieces} pieces`,
|
|
894
|
+
action: /* @__PURE__ */ jsx("span", { className: "text-mono-sm", children: usd.format(person.spent) })
|
|
895
|
+
},
|
|
896
|
+
person.id
|
|
897
|
+
)) })
|
|
898
|
+
] })
|
|
899
|
+
] })
|
|
900
|
+
] }),
|
|
901
|
+
/* @__PURE__ */ jsxs(Grid, { cols: { base: 1, xl: 3 }, gap: 3, children: [
|
|
902
|
+
/* @__PURE__ */ jsxs(Card, { className: "flex flex-col", children: [
|
|
903
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
904
|
+
/* @__PURE__ */ jsx(CardTitle, { children: "Upcoming releases" }),
|
|
905
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "Scheduled to open to collectors." })
|
|
906
|
+
] }),
|
|
907
|
+
/* @__PURE__ */ jsx(CardList, { children: upcoming.map((release) => /* @__PURE__ */ jsx(
|
|
908
|
+
Entity,
|
|
909
|
+
{
|
|
910
|
+
title: release.title,
|
|
911
|
+
subtitle: release.detail,
|
|
912
|
+
action: /* @__PURE__ */ jsx(RelativeTime, { date: Date.now() + release.at, className: "text-caption text-muted-foreground" })
|
|
913
|
+
},
|
|
914
|
+
release.id
|
|
915
|
+
)) }),
|
|
916
|
+
/* @__PURE__ */ jsx(CardFooter, { className: "mt-auto", children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/editions/new" }), children: [
|
|
917
|
+
/* @__PURE__ */ jsx(Icon, { name: "calendar" }),
|
|
918
|
+
"Schedule a release"
|
|
919
|
+
] }) })
|
|
920
|
+
] }),
|
|
921
|
+
/* @__PURE__ */ jsxs(Card, { className: "xl:col-span-2", children: [
|
|
582
922
|
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
583
923
|
/* @__PURE__ */ jsx(CardTitle, { children: "Activity" }),
|
|
584
|
-
/* @__PURE__ */ jsx(CardDescription, { children: "Collects,
|
|
924
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "Collects, tips, and follows." }),
|
|
925
|
+
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/activity" }), children: "View all" }) })
|
|
585
926
|
] }),
|
|
586
927
|
/* @__PURE__ */ jsx(CardList, { children: activity.map((item) => /* @__PURE__ */ jsx(
|
|
587
928
|
Entity,
|
|
588
929
|
{
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
/* @__PURE__ */ jsx("span", { className: "text-label-md", children: item.who }),
|
|
593
|
-
" ",
|
|
594
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: item.what })
|
|
595
|
-
] }),
|
|
930
|
+
leading: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: item.initials, alt: "" }),
|
|
931
|
+
title: item.who,
|
|
932
|
+
subtitle: item.what,
|
|
596
933
|
action: /* @__PURE__ */ jsx(RelativeTime, { date: Date.now() - item.when, className: "text-caption text-muted-foreground" })
|
|
597
934
|
},
|
|
598
935
|
item.id
|
|
@@ -607,150 +944,561 @@ function DashboardPage() {
|
|
|
607
944
|
// src/patterns/dashboard-page.meta.ts
|
|
608
945
|
var dashboardPageMeta = {
|
|
609
946
|
name: "DashboardPage",
|
|
610
|
-
summary: "App-shell dashboard:
|
|
611
|
-
intent: "Reach for this when a route's job is monitoring or managing many records (
|
|
947
|
+
summary: "App-shell creator dashboard: a branded Sidebar with an account footer (AppHeader + BottomNav below lg), a KPI row, an edge-to-edge editions table with sell-through Meters, and supporting payout, collector, release and activity cards \u2014 the default shape for an operator or analytics home route.",
|
|
948
|
+
intent: "Reach for this when a route's job is monitoring or managing many records (sales, orders, editions, posts) with a few headline numbers. Not for reading-focused routes (use FeedPage) or single-record routes (use ListDetailPage's detail side).",
|
|
612
949
|
notes: [
|
|
613
950
|
"Region cap is `wide` (1280px): dashboards hold tables and multi-column grids, never a reading column.",
|
|
614
|
-
"
|
|
615
|
-
"Stat cards
|
|
616
|
-
"
|
|
617
|
-
"The
|
|
618
|
-
|
|
951
|
+
"Hierarchy runs headline \u2192 records \u2192 context: KPI row, then the primary table beside its two closest companions (payout, top collectors), then the lower-priority lists (upcoming, activity). Every row of the grid is filled so the route ends on content, not empty canvas.",
|
|
952
|
+
"Stat cards: CardHeader carries the label (CardDescription) and the value; KPI values are `text-mono-xl` \u2014 the metric mono tier, tabular figures \u2014 never a heading tier. The trend Badge sits with its comparison note in CardContent, so the value keeps the full card width when the row folds to 2\xD72 on a phone.",
|
|
953
|
+
"Stat cards use an explicit cols map (2 \u2192 4) \u2014 a 4-stat row must fold to 2\xD72, never strand an orphan the way auto-fit can.",
|
|
954
|
+
"The table runs flush to the card edges with `<CardContent flush>`, which hands the card's padding to the table (`--table-edge-px`), so the first and last columns line up with the CardHeader at any card density \u2014 never hand-set pl-6/pr-6. Numeric columns align end in mono; Table scrolls horizontally inside the card on narrow screens.",
|
|
955
|
+
'Sold / supply is a `Meter` (how full a known range is), not `Progress` (task progress). Status is `Badge appearance="plain" dot`, with the app\'s own states mapped to tones once at the top of the file.',
|
|
956
|
+
"Lists are Entity rows in CardList, inset to the card padding and divided by hairlines \u2014 never CardContent with hand-tuned px. A card that shares a grid row with a taller one becomes `flex flex-col` and pins its CardFooter with `mt-auto`.",
|
|
957
|
+
"Brand and account live in the Sidebar: SidebarHeader holds the mark + wordmark, SidebarFooter holds the signed-in Entity with a DotsMenu. The footer rebinds `--foreground` to `--sidebar-foreground`, because Entity's title and the ghost trigger ink with page tokens and a theme may paint the rail dark on a light page; it keeps to a title line, since there is no sidebar-muted ink for a subtitle.",
|
|
619
958
|
"The page has exactly one heading-1 (PageHeader); card titles are Title tier, not headings.",
|
|
620
959
|
"Chrome (Sidebar) is outside the content grid; the Page region caps only content.",
|
|
621
|
-
"Sidebar hides below lg, so the shell's header carries an AppHeader and its footer a BottomNav with the same destinations \u2014 never strand mobile users without navigation."
|
|
960
|
+
"Sidebar hides below lg, so the shell's header carries an AppHeader (brand + account) and its footer a BottomNav with the same destinations \u2014 never strand mobile users without navigation."
|
|
622
961
|
],
|
|
623
962
|
a11y: [
|
|
624
963
|
'AppShell provides the skip link and <main id="main-content"> landmark.',
|
|
625
964
|
"The active SidebarItem and BottomNavItem carry aria-current via `active`.",
|
|
626
|
-
"DataTable headers are real <th>; sortable columns are announced."
|
|
965
|
+
"DataTable headers are real <th>; sortable columns are announced.",
|
|
966
|
+
'Each sell-through Meter has an aria-label naming the edition and aria-valuetext reading "184 of 250 sold".',
|
|
967
|
+
"Avatars beside a visible name use an empty alt so the name is not read twice."
|
|
627
968
|
]
|
|
628
969
|
};
|
|
629
970
|
var posts = [
|
|
630
971
|
{
|
|
631
972
|
id: "p_01",
|
|
632
973
|
author: "Ada Lovelace",
|
|
633
|
-
handle: "@ada
|
|
974
|
+
handle: "@ada",
|
|
634
975
|
initials: "AL",
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
976
|
+
time: "2h",
|
|
977
|
+
following: true,
|
|
978
|
+
body: "Gold Rush is live. 100 signed prints, each paired with the on-chain original. Thank you for waiting.",
|
|
979
|
+
media: { src: "/media/edition-cover.svg", alt: "Gold Rush, a gold-leaf study in overlapping arcs" },
|
|
980
|
+
drop: { title: "Gold Rush", price: "$120", collected: 62, supply: 100 },
|
|
638
981
|
likes: 128,
|
|
639
982
|
comments: 24
|
|
640
983
|
},
|
|
641
984
|
{
|
|
642
985
|
id: "p_02",
|
|
643
986
|
author: "Grace Hopper",
|
|
644
|
-
handle: "@grace
|
|
987
|
+
handle: "@grace",
|
|
645
988
|
initials: "GH",
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
989
|
+
time: "5h",
|
|
990
|
+
following: true,
|
|
991
|
+
body: "Process shot from this week. Compilers and copper ink \u2014 the plotter ran for eleven hours on this one.",
|
|
992
|
+
media: { src: "/media/process-shot.svg", alt: "Work-in-progress plotter drawing in copper ink" },
|
|
649
993
|
likes: 86,
|
|
650
994
|
comments: 9
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
id: "p_03",
|
|
998
|
+
author: "Nina Simone",
|
|
999
|
+
handle: "@nina",
|
|
1000
|
+
initials: "NS",
|
|
1001
|
+
time: "8h",
|
|
1002
|
+
following: false,
|
|
1003
|
+
body: "Collectors of Dusk, no. 3 \u2014 your unlockable score is now in your library. Headphones on for this one.",
|
|
1004
|
+
likes: 214,
|
|
1005
|
+
comments: 31
|
|
651
1006
|
}
|
|
652
1007
|
];
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1008
|
+
var suggestions = [
|
|
1009
|
+
{ id: "u_01", name: "Mary Shelley", handle: "@mary", initials: "MS" },
|
|
1010
|
+
{ id: "u_02", name: "Nina Simone", handle: "@nina", initials: "NS" }
|
|
1011
|
+
];
|
|
1012
|
+
var upcoming2 = [
|
|
1013
|
+
{ id: "d_01", title: "Quiet Harbor", artist: "Mary Shelley", initials: "MS", date: "Sat, Oct 12" },
|
|
1014
|
+
{ id: "d_02", title: "Dusk, no. 4", artist: "Nina Simone", initials: "NS", date: "Tue, Oct 15" }
|
|
1015
|
+
];
|
|
1016
|
+
var renderPost = (post) => /* @__PURE__ */ jsx("article", { children: /* @__PURE__ */ jsxs(Card, { children: [
|
|
1017
|
+
/* @__PURE__ */ jsx(CardList, { children: /* @__PURE__ */ jsx(
|
|
1018
|
+
Entity,
|
|
1019
|
+
{
|
|
1020
|
+
leading: /* @__PURE__ */ jsx(Avatar, { fallback: post.initials, alt: post.author }),
|
|
1021
|
+
title: post.author,
|
|
1022
|
+
subtitle: `${post.handle} \xB7 ${post.time}`,
|
|
1023
|
+
action: /* @__PURE__ */ jsxs(DotsMenu, { label: `Post options for ${post.author}`, size: "icon-sm", children: [
|
|
1024
|
+
/* @__PURE__ */ jsx(MenuItem, { children: "Copy link" }),
|
|
1025
|
+
/* @__PURE__ */ jsxs(MenuItem, { children: [
|
|
1026
|
+
"Mute ",
|
|
1027
|
+
post.handle
|
|
1028
|
+
] }),
|
|
1029
|
+
/* @__PURE__ */ jsx(MenuSeparator, {}),
|
|
1030
|
+
/* @__PURE__ */ jsx(MenuItem, { variant: "destructive", children: "Report post" })
|
|
1031
|
+
] })
|
|
1032
|
+
}
|
|
1033
|
+
) }),
|
|
1034
|
+
/* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-4 pt-0", children: [
|
|
1035
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-md text-foreground", children: post.body }),
|
|
1036
|
+
post.media ? /* @__PURE__ */ jsxs(AspectBox, { ratio: 16 / 9, className: "overflow-hidden rounded-concentric-lg/6 bg-muted", children: [
|
|
1037
|
+
/* @__PURE__ */ jsx("img", { src: post.media.src, alt: post.media.alt, className: "size-full object-cover" }),
|
|
1038
|
+
post.drop ? /* @__PURE__ */ jsx("div", { className: "absolute start-3 top-3", children: /* @__PURE__ */ jsx(Badge, { appearance: "overlay", size: "sm", dot: true, children: "Live drop" }) }) : null
|
|
1039
|
+
] }) : null,
|
|
1040
|
+
post.drop ? /* @__PURE__ */ jsxs(Stack, { gap: 1.5, children: [
|
|
1041
|
+
/* @__PURE__ */ jsxs(Row, { gap: 2, align: "center", justify: "between", children: [
|
|
1042
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1043
|
+
/* @__PURE__ */ jsx("p", { className: "text-title-md text-foreground", children: post.drop.title }),
|
|
1044
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: `${post.drop.price} \xB7 Edition of ${post.drop.supply}` })
|
|
1045
|
+
] }),
|
|
1046
|
+
/* @__PURE__ */ jsx(Button, { children: `Collect for ${post.drop.price}` })
|
|
1047
|
+
] }),
|
|
1048
|
+
/* @__PURE__ */ jsxs(
|
|
1049
|
+
Meter,
|
|
659
1050
|
{
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
1051
|
+
value: post.drop.collected,
|
|
1052
|
+
max: post.drop.supply,
|
|
1053
|
+
size: "sm",
|
|
1054
|
+
getAriaValueText: (_, value) => `${value} of ${post.drop?.supply} collected`,
|
|
1055
|
+
children: [
|
|
1056
|
+
/* @__PURE__ */ jsxs(Row, { gap: 2, align: "center", justify: "between", className: "mb-2", children: [
|
|
1057
|
+
/* @__PURE__ */ jsx(MeterLabel, { children: `${post.drop.collected} collected` }),
|
|
1058
|
+
/* @__PURE__ */ jsx(MeterValue, { children: (_, value) => `${(post.drop?.supply ?? 0) - value} left` })
|
|
1059
|
+
] }),
|
|
1060
|
+
/* @__PURE__ */ jsx(MeterTrack, {})
|
|
1061
|
+
]
|
|
664
1062
|
}
|
|
665
|
-
)
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
/* @__PURE__ */
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
1063
|
+
)
|
|
1064
|
+
] }) : null,
|
|
1065
|
+
/* @__PURE__ */ jsxs(Row, { gap: 0.5, className: "-ms-3", children: [
|
|
1066
|
+
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", "aria-label": `Like, ${post.likes} likes`, children: [
|
|
1067
|
+
/* @__PURE__ */ jsx(Icon, { name: "heart" }),
|
|
1068
|
+
post.likes
|
|
1069
|
+
] }),
|
|
1070
|
+
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", "aria-label": `Comment, ${post.comments} comments`, children: [
|
|
1071
|
+
/* @__PURE__ */ jsx(Icon, { name: "comment" }),
|
|
1072
|
+
post.comments
|
|
1073
|
+
] }),
|
|
1074
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": "Share", children: /* @__PURE__ */ jsx(Icon, { name: "share" }) })
|
|
1075
|
+
] })
|
|
1076
|
+
] })
|
|
1077
|
+
] }) }, post.id);
|
|
1078
|
+
function FeedPage() {
|
|
1079
|
+
return /* @__PURE__ */ jsx(Page, { as: "main", max: "wide", gap: 4, className: "py-6", children: /* @__PURE__ */ jsxs(Row, { gap: 4, align: "start", justify: "center", children: [
|
|
1080
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 3, className: "min-w-0 max-w-(--region-feed) flex-1", children: [
|
|
1081
|
+
/* @__PURE__ */ jsx(PageHeader, { title: "Home" }),
|
|
1082
|
+
/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Row, { gap: 1.5, align: "start", children: [
|
|
1083
|
+
/* @__PURE__ */ jsx(Avatar, { fallback: "AL", alt: "Ada Lovelace" }),
|
|
1084
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 1.5, className: "min-w-0 flex-1", children: [
|
|
1085
|
+
/* @__PURE__ */ jsx(
|
|
1086
|
+
Textarea,
|
|
1087
|
+
{
|
|
1088
|
+
autoResize: true,
|
|
1089
|
+
rows: 2,
|
|
1090
|
+
"aria-label": "New post",
|
|
1091
|
+
placeholder: "Share a process shot or an upcoming drop"
|
|
1092
|
+
}
|
|
1093
|
+
),
|
|
1094
|
+
/* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", justify: "between", children: [
|
|
1095
|
+
/* @__PURE__ */ jsxs(Row, { gap: 0.5, children: [
|
|
1096
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Add images", children: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }) }),
|
|
1097
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Attach an edition", children: /* @__PURE__ */ jsx(Icon, { name: "gem" }) })
|
|
1098
|
+
] }),
|
|
1099
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", children: "Post" })
|
|
1100
|
+
] })
|
|
679
1101
|
] })
|
|
1102
|
+
] }) }) }),
|
|
1103
|
+
/* @__PURE__ */ jsxs(Tabs, { defaultValue: "for-you", children: [
|
|
1104
|
+
/* @__PURE__ */ jsxs(TabsList, { className: "flex", children: [
|
|
1105
|
+
/* @__PURE__ */ jsx(TabsTrigger, { value: "for-you", children: "For you" }),
|
|
1106
|
+
/* @__PURE__ */ jsx(TabsTrigger, { value: "following", children: "Following" })
|
|
1107
|
+
] }),
|
|
1108
|
+
/* @__PURE__ */ jsx(TabsPanel, { value: "for-you", children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1109
|
+
posts.map(renderPost),
|
|
1110
|
+
/* @__PURE__ */ jsx(Card, { "aria-busy": "true", children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-4", children: [
|
|
1111
|
+
/* @__PURE__ */ jsxs(Row, { gap: 1.5, align: "center", children: [
|
|
1112
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "size-10 rounded-full" }),
|
|
1113
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 1, children: [
|
|
1114
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-32" }),
|
|
1115
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-20" })
|
|
1116
|
+
] })
|
|
1117
|
+
] }),
|
|
1118
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-full" }),
|
|
1119
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-2/3" })
|
|
1120
|
+
] }) }),
|
|
1121
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", loading: true, loadingLabel: "Loading posts", children: "Load more posts" })
|
|
1122
|
+
] }) }),
|
|
1123
|
+
/* @__PURE__ */ jsx(TabsPanel, { value: "following", children: /* @__PURE__ */ jsx(Stack, { gap: 3, children: posts.filter((post) => post.following).map(renderPost) }) })
|
|
680
1124
|
] })
|
|
681
|
-
] })
|
|
682
|
-
/* @__PURE__ */ jsx(
|
|
683
|
-
|
|
1125
|
+
] }),
|
|
1126
|
+
/* @__PURE__ */ jsx("aside", { "aria-label": "Suggestions", className: "sticky top-6 hidden w-80 shrink-0 lg:block", children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1127
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
1128
|
+
/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { role: "heading", "aria-level": 2, children: "Who to follow" }) }),
|
|
1129
|
+
/* @__PURE__ */ jsx(CardList, { children: suggestions.map((person) => /* @__PURE__ */ jsx(
|
|
1130
|
+
Entity,
|
|
1131
|
+
{
|
|
1132
|
+
leading: /* @__PURE__ */ jsx(Avatar, { fallback: person.initials, alt: person.name }),
|
|
1133
|
+
title: person.name,
|
|
1134
|
+
subtitle: person.handle,
|
|
1135
|
+
action: /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", "aria-label": `Follow ${person.name}`, children: "Follow" })
|
|
1136
|
+
},
|
|
1137
|
+
person.id
|
|
1138
|
+
)) })
|
|
1139
|
+
] }),
|
|
1140
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
1141
|
+
/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { role: "heading", "aria-level": 2, children: "Upcoming drops" }) }),
|
|
1142
|
+
/* @__PURE__ */ jsx(CardList, { children: upcoming2.map((drop) => /* @__PURE__ */ jsx(
|
|
1143
|
+
Entity,
|
|
1144
|
+
{
|
|
1145
|
+
leading: /* @__PURE__ */ jsx(Avatar, { fallback: drop.initials, alt: drop.artist }),
|
|
1146
|
+
title: drop.title,
|
|
1147
|
+
subtitle: `${drop.artist} \xB7 ${drop.date}`,
|
|
1148
|
+
action: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": `Remind me about ${drop.title}`, children: /* @__PURE__ */ jsx(Icon, { name: "bell" }) })
|
|
1149
|
+
},
|
|
1150
|
+
drop.id
|
|
1151
|
+
)) })
|
|
1152
|
+
] })
|
|
1153
|
+
] }) })
|
|
1154
|
+
] }) });
|
|
684
1155
|
}
|
|
685
1156
|
|
|
686
1157
|
// src/patterns/feed-page.meta.ts
|
|
687
1158
|
var feedPageMeta = {
|
|
688
1159
|
name: "FeedPage",
|
|
689
|
-
summary: "
|
|
690
|
-
intent: "Reach for this for timelines, activity streams, and any reverse-chronological reading surface. Not for record management (DashboardPage) or side-by-side browsing (ListDetailPage).",
|
|
1160
|
+
summary: "Social reading feed with a side rail: a feed-width main column (composer, For you / Following Tabs, one Card per post, an edition drop with price, supply Meter and Collect, a Skeleton for the next page) beside a sticky rail of who-to-follow and upcoming drops that drops away below lg.",
|
|
1161
|
+
intent: "Reach for this for timelines, activity streams, and any reverse-chronological reading surface where people also act on what they read. Not for record management (DashboardPage) or side-by-side browsing (ListDetailPage).",
|
|
691
1162
|
notes: [
|
|
692
|
-
"
|
|
693
|
-
"
|
|
694
|
-
"The
|
|
695
|
-
"
|
|
696
|
-
|
|
1163
|
+
"The main column is capped at `--region-feed` (640px) inside a `wide` page, so posts keep a reading measure while the rail fills the desktop width. The rail is a fixed `w-80` aside, `sticky` at lg and `hidden` below it \u2014 its content is supplementary, so nothing is lost on mobile.",
|
|
1164
|
+
"Feed scope is `Tabs` (underline, stretched to the column with `flex`): peer views of the same feed. The composer sits above them because posting applies to both.",
|
|
1165
|
+
"The composer is a Card holding `Textarea autoResize` plus ghost icon Buttons and a secondary `Post` \u2014 not ChatComposer, which submits on Enter and is for conversations.",
|
|
1166
|
+
"Each post is a Card: an Entity byline in a CardList (with a DotsMenu for post actions), body copy, then media in an AspectBox at the asset's own ratio, inset with `rounded-concentric-lg/6` so its corners follow the card's in every theme.",
|
|
1167
|
+
'An edition drop adds a `Badge appearance="overlay"` on the artwork, title and price, the one primary Button on the page (`Collect`), and a `Meter` for supply \u2014 collected against a known maximum is a level, not task progress.',
|
|
1168
|
+
"Actions are ghost sm Buttons (icon + count) in a Row pulled out by the button's own padding (`-ms-3`) so the first icon sits on the text edge.",
|
|
1169
|
+
"Loading the next page shows a Skeleton post in the shape the post will land in, above the explicit load-more Button in its `loading` state \u2014 predictable for keyboard and AT users, unlike infinite scroll."
|
|
697
1170
|
],
|
|
698
1171
|
a11y: [
|
|
699
|
-
"Each post is an <article>, so AT users can jump between posts.",
|
|
700
|
-
|
|
701
|
-
"
|
|
1172
|
+
"Each post is an <article>, so AT users can jump between posts; the rail is a labelled <aside> landmark.",
|
|
1173
|
+
`Rail card titles carry role="heading" aria-level=2 under the page's one h1.`,
|
|
1174
|
+
"Icon-only buttons and every Follow / Remind button carry aria-labels naming their target; counts stay in the accessible name.",
|
|
1175
|
+
'The supply Meter is role=meter with a getAriaValueText reading ("62 of 100 collected").',
|
|
1176
|
+
'The Skeleton card is aria-busy; the loading Button keeps its label ("Loading posts").'
|
|
702
1177
|
]
|
|
703
1178
|
};
|
|
704
|
-
var
|
|
1179
|
+
var navLinks = [
|
|
1180
|
+
{ label: "Docs", href: "/docs" },
|
|
1181
|
+
{ label: "Components", href: "/components" },
|
|
1182
|
+
{ label: "Patterns", href: "/patterns" },
|
|
1183
|
+
{ label: "Themes", href: "/themes" }
|
|
1184
|
+
];
|
|
1185
|
+
var deploys = [
|
|
1186
|
+
{
|
|
1187
|
+
initials: "MO",
|
|
1188
|
+
name: "Maya Ortiz",
|
|
1189
|
+
title: "Add usage-based billing",
|
|
1190
|
+
meta: "main \xB7 a3f9c21 \xB7 2m ago",
|
|
1191
|
+
status: "Building",
|
|
1192
|
+
tone: "info",
|
|
1193
|
+
pulse: true
|
|
1194
|
+
},
|
|
705
1195
|
{
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
1196
|
+
initials: "SR",
|
|
1197
|
+
name: "Sam Reyes",
|
|
1198
|
+
title: "Fix search ranking for long queries",
|
|
1199
|
+
meta: "main \xB7 81be04d \xB7 18m ago",
|
|
1200
|
+
status: "Ready",
|
|
1201
|
+
tone: "success",
|
|
1202
|
+
pulse: false
|
|
709
1203
|
},
|
|
1204
|
+
{
|
|
1205
|
+
initials: "JL",
|
|
1206
|
+
name: "Jun Lee",
|
|
1207
|
+
title: "Rotate webhook signing keys",
|
|
1208
|
+
meta: "main \xB7 5c7e9aa \xB7 1h ago",
|
|
1209
|
+
status: "Ready",
|
|
1210
|
+
tone: "success",
|
|
1211
|
+
pulse: false
|
|
1212
|
+
}
|
|
1213
|
+
];
|
|
1214
|
+
var features = [
|
|
710
1215
|
{
|
|
711
1216
|
icon: "component",
|
|
712
|
-
title: "Behavior
|
|
713
|
-
body: "Focus, keyboard
|
|
1217
|
+
title: "Behavior built in",
|
|
1218
|
+
body: "Focus management, keyboard navigation, and ARIA come from Base UI primitives under every control."
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
icon: "eyedropper",
|
|
1222
|
+
title: "Semantic tokens end to end",
|
|
1223
|
+
body: "Re-theme the whole product, light and dark, from one preset without touching a component."
|
|
714
1224
|
},
|
|
715
1225
|
{
|
|
716
1226
|
icon: "layout",
|
|
717
1227
|
title: "Pages, not just parts",
|
|
718
|
-
body: "
|
|
1228
|
+
body: "Landing, dashboard, settings, auth, and list-detail shells to copy and adapt."
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
icon: "book",
|
|
1232
|
+
title: "A contract agents can read",
|
|
1233
|
+
body: "registry.json, llms.txt, and a CLI hand agents the exact props, examples, and rules."
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
icon: "circle-check",
|
|
1237
|
+
title: "Lint that knows the design",
|
|
1238
|
+
body: "sable-lint flags raw colors, hand-built controls, and motion that ignores reduced motion."
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
icon: "play",
|
|
1242
|
+
title: "Motion with an off switch",
|
|
1243
|
+
body: "Named recipes run on one scale variable, so reduced motion stops every transition at once."
|
|
1244
|
+
}
|
|
1245
|
+
];
|
|
1246
|
+
var steps2 = [
|
|
1247
|
+
{
|
|
1248
|
+
title: "Install the package",
|
|
1249
|
+
body: "One dependency. React 19 and Tailwind v4 are the only peers."
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
title: "Import the styles",
|
|
1253
|
+
body: "Tailwind first, then Sable's tokens, then a source glob over the package."
|
|
1254
|
+
},
|
|
1255
|
+
{
|
|
1256
|
+
title: "Compose your screens",
|
|
1257
|
+
body: "Build from components and layout primitives. Your theme does the rest."
|
|
1258
|
+
}
|
|
1259
|
+
];
|
|
1260
|
+
var stylesCode = `@import "tailwindcss";
|
|
1261
|
+
@import "@sableui/react/styles.css";
|
|
1262
|
+
@source "../node_modules/@sableui/react/dist/**/*.{js,mjs}";`;
|
|
1263
|
+
var composeCode = `import { Button, Card, CardContent, CardHeader, CardTitle } from "@sableui/react"
|
|
1264
|
+
|
|
1265
|
+
export function DeployCard() {
|
|
1266
|
+
return (
|
|
1267
|
+
<Card>
|
|
1268
|
+
<CardHeader>
|
|
1269
|
+
<CardTitle>Production</CardTitle>
|
|
1270
|
+
</CardHeader>
|
|
1271
|
+
<CardContent>
|
|
1272
|
+
<Button>Deploy</Button>
|
|
1273
|
+
</CardContent>
|
|
1274
|
+
</Card>
|
|
1275
|
+
)
|
|
1276
|
+
}`;
|
|
1277
|
+
var terminalCode = `$ npx sable search "confirm before deleting"
|
|
1278
|
+
component DestructiveActionModal
|
|
1279
|
+
component Dialog
|
|
1280
|
+
|
|
1281
|
+
$ npx sable component DestructiveActionModal --props
|
|
1282
|
+
props, a11y contract, composed examples
|
|
1283
|
+
|
|
1284
|
+
$ npx sable lint src
|
|
1285
|
+
0 findings in 42 files`;
|
|
1286
|
+
var agentPoints = [
|
|
1287
|
+
"Search ranks components, patterns, tokens, and docs for a plain-language need.",
|
|
1288
|
+
"Every component ships its props, accessibility contract, and composed examples.",
|
|
1289
|
+
"The same lint runs in the editor, in CI, and inside the agent's own loop."
|
|
1290
|
+
];
|
|
1291
|
+
var footerGroups2 = [
|
|
1292
|
+
{
|
|
1293
|
+
title: "Product",
|
|
1294
|
+
links: [
|
|
1295
|
+
{ label: "Components", href: "/components" },
|
|
1296
|
+
{ label: "Patterns", href: "/patterns" },
|
|
1297
|
+
{ label: "Themes", href: "/themes" },
|
|
1298
|
+
{ label: "Canvas", href: "/canvas" }
|
|
1299
|
+
]
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
title: "Resources",
|
|
1303
|
+
links: [
|
|
1304
|
+
{ label: "Documentation", href: "/docs" },
|
|
1305
|
+
{ label: "Changelog", href: "/changelog" },
|
|
1306
|
+
{ label: "llms.txt", href: "/llms.txt" },
|
|
1307
|
+
{ label: "registry.json", href: "/registry.json" }
|
|
1308
|
+
]
|
|
1309
|
+
},
|
|
1310
|
+
{
|
|
1311
|
+
title: "Community",
|
|
1312
|
+
links: [
|
|
1313
|
+
{ label: "GitHub", href: "/github" },
|
|
1314
|
+
{ label: "npm", href: "/npm" },
|
|
1315
|
+
{ label: "Discussions", href: "/discussions" }
|
|
1316
|
+
]
|
|
719
1317
|
}
|
|
720
1318
|
];
|
|
721
1319
|
function LandingPage() {
|
|
722
|
-
return /* @__PURE__ */ jsxs("div", { className: "min-h-dvh bg-background", children: [
|
|
723
|
-
/* @__PURE__ */ jsx("header", { children: /* @__PURE__ */ jsxs(Region, { as: "nav", "aria-label": "Main", className: "grid h-16 grid-cols-[1fr_auto_1fr] items-center", children: [
|
|
724
|
-
/* @__PURE__ */ jsx("
|
|
725
|
-
/* @__PURE__ */
|
|
726
|
-
|
|
727
|
-
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/
|
|
728
|
-
/* @__PURE__ */ jsx(Button, {
|
|
729
|
-
] })
|
|
730
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", className: "justify-self-end", render: /* @__PURE__ */ jsx("a", { href: "/signup" }), children: "Get started" })
|
|
1320
|
+
return /* @__PURE__ */ jsxs("div", { className: "min-h-dvh bg-background text-foreground", children: [
|
|
1321
|
+
/* @__PURE__ */ jsx("header", { className: "border-b border-border", children: /* @__PURE__ */ jsxs(Region, { as: "nav", "aria-label": "Main", max: "wide", className: "grid h-16 grid-cols-[1fr_auto_1fr] items-center", children: [
|
|
1322
|
+
/* @__PURE__ */ jsx("a", { href: "/", className: "justify-self-start text-title-lg motion-interactive hover:text-muted-foreground", children: "Sable" }),
|
|
1323
|
+
/* @__PURE__ */ jsx("div", { className: "justify-self-center max-md:hidden", children: /* @__PURE__ */ jsx(Row, { gap: 1, children: navLinks.map((link) => /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: link.href }), children: link.label }, link.label)) }) }),
|
|
1324
|
+
/* @__PURE__ */ jsxs(Row, { gap: 1, className: "col-start-3 justify-self-end", children: [
|
|
1325
|
+
/* @__PURE__ */ jsx("div", { className: "max-sm:hidden", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/github" }), children: "GitHub" }) }),
|
|
1326
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/docs/getting-started" }), children: "Get started" })
|
|
1327
|
+
] })
|
|
731
1328
|
] }) }),
|
|
732
1329
|
/* @__PURE__ */ jsxs("main", { children: [
|
|
733
|
-
/* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "
|
|
734
|
-
/* @__PURE__ */
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
/* @__PURE__ */
|
|
739
|
-
|
|
740
|
-
|
|
1330
|
+
/* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "pb-16 pt-16 sm:pt-24", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, align: "center", children: [
|
|
1331
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 4, align: "center", className: "text-center", children: [
|
|
1332
|
+
/* @__PURE__ */ jsx(Badge, { appearance: "outline", size: "sm", icon: /* @__PURE__ */ jsx(Icon, { name: "sparkles" }), children: "Open source \xB7 MIT licensed" }),
|
|
1333
|
+
/* @__PURE__ */ jsx("h1", { className: "max-w-4xl text-balance text-display-lg sm:text-display-2xl", children: "Product UI your agents can't get wrong" }),
|
|
1334
|
+
/* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-pretty text-body-lg text-muted-foreground", children: "Sable is a themeable React component library with enforced tokens, typography, and motion, documented for coding agents as carefully as for people." }),
|
|
1335
|
+
/* @__PURE__ */ jsxs(Row, { gap: 2, justify: "center", wrap: true, className: "pt-2", children: [
|
|
1336
|
+
/* @__PURE__ */ jsx(Button, { size: "cta", render: /* @__PURE__ */ jsx("a", { href: "/docs/getting-started" }), children: "Get started" }),
|
|
1337
|
+
/* @__PURE__ */ jsx(Button, { size: "cta", variant: "outline", render: /* @__PURE__ */ jsx("a", { href: "/components" }), children: "Browse components" })
|
|
1338
|
+
] })
|
|
1339
|
+
] }),
|
|
1340
|
+
/* @__PURE__ */ jsx("div", { className: "w-full max-w-sm", children: /* @__PURE__ */ jsx(CodeBlock, { prompt: "$", code: "pnpm add @sableui/react" }) })
|
|
741
1341
|
] }) }),
|
|
742
|
-
/* @__PURE__ */ jsx(Region, { as: "section", max: "wide", className: "pb-
|
|
743
|
-
/* @__PURE__ */ jsx(
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1342
|
+
/* @__PURE__ */ jsx(Region, { as: "section", "aria-label": "Product preview", max: "wide", className: "pb-20 sm:pb-28", children: /* @__PURE__ */ jsxs("figure", { children: [
|
|
1343
|
+
/* @__PURE__ */ jsx(Browser, { url: "atlas.example/deploys", className: "shadow-xl", children: /* @__PURE__ */ jsxs("div", { inert: true, className: "grid gap-4 p-4 sm:p-6 lg:grid-cols-[minmax(0,1fr)_20rem]", children: [
|
|
1344
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 4, className: "min-w-0", children: [
|
|
1345
|
+
/* @__PURE__ */ jsx(
|
|
1346
|
+
PageHeader,
|
|
1347
|
+
{
|
|
1348
|
+
titleAs: "h2",
|
|
1349
|
+
title: "Deployments",
|
|
1350
|
+
description: "atlas-web \xB7 production",
|
|
1351
|
+
actions: /* @__PURE__ */ jsxs(Row, { gap: 2, children: [
|
|
1352
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: "View logs" }),
|
|
1353
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", iconStart: /* @__PURE__ */ jsx(Icon, { name: "upload" }), children: "Deploy" })
|
|
1354
|
+
] })
|
|
1355
|
+
}
|
|
1356
|
+
),
|
|
1357
|
+
/* @__PURE__ */ jsxs(Grid, { cols: { base: 2, md: 3 }, gap: 3, children: [
|
|
1358
|
+
/* @__PURE__ */ jsxs(Card, { density: "compact", children: [
|
|
1359
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
1360
|
+
/* @__PURE__ */ jsx(CardTitle, { children: "Uptime" }),
|
|
1361
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "Last 30 days" })
|
|
1362
|
+
] }),
|
|
1363
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("p", { className: "text-heading-3", children: "99.98%" }) })
|
|
1364
|
+
] }),
|
|
1365
|
+
/* @__PURE__ */ jsxs(Card, { density: "compact", children: [
|
|
1366
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
1367
|
+
/* @__PURE__ */ jsx(CardTitle, { children: "p95 latency" }),
|
|
1368
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "Edge, all regions" })
|
|
1369
|
+
] }),
|
|
1370
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("p", { className: "text-heading-3", children: "182 ms" }) })
|
|
1371
|
+
] }),
|
|
1372
|
+
/* @__PURE__ */ jsxs(Card, { density: "compact", className: "max-md:hidden", children: [
|
|
1373
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
1374
|
+
/* @__PURE__ */ jsx(CardTitle, { children: "Build minutes" }),
|
|
1375
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "Team plan" })
|
|
1376
|
+
] }),
|
|
1377
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Meter, { value: 64, tone: "standard", children: [
|
|
1378
|
+
/* @__PURE__ */ jsxs(Row, { justify: "between", wrap: true, className: "mb-2", children: [
|
|
1379
|
+
/* @__PURE__ */ jsx(MeterLabel, { children: "Used" }),
|
|
1380
|
+
/* @__PURE__ */ jsx(MeterValue, { children: (_, value) => `${(value * 30).toLocaleString("en-US")} of 3,000` })
|
|
1381
|
+
] }),
|
|
1382
|
+
/* @__PURE__ */ jsx(MeterTrack, {})
|
|
1383
|
+
] }) })
|
|
1384
|
+
] })
|
|
1385
|
+
] }),
|
|
1386
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
1387
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
1388
|
+
/* @__PURE__ */ jsx(CardTitle, { children: "Recent deploys" }),
|
|
1389
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "Production branch" }),
|
|
1390
|
+
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, { appearance: "plain", tone: "success", dot: true, children: "Healthy" }) })
|
|
1391
|
+
] }),
|
|
1392
|
+
/* @__PURE__ */ jsx(CardList, { children: deploys.map((deploy) => /* @__PURE__ */ jsx(
|
|
1393
|
+
Entity,
|
|
1394
|
+
{
|
|
1395
|
+
leading: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: deploy.initials, alt: deploy.name }),
|
|
1396
|
+
title: deploy.title,
|
|
1397
|
+
subtitle: deploy.meta,
|
|
1398
|
+
action: /* @__PURE__ */ jsx(Badge, { appearance: "plain", tone: deploy.tone, dot: true, pulse: deploy.pulse, children: deploy.status })
|
|
1399
|
+
},
|
|
1400
|
+
deploy.meta
|
|
1401
|
+
)) })
|
|
1402
|
+
] })
|
|
1403
|
+
] }),
|
|
1404
|
+
/* @__PURE__ */ jsxs(Card, { className: "max-lg:hidden", children: [
|
|
1405
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
1406
|
+
/* @__PURE__ */ jsx(CardTitle, { children: "Release assistant" }),
|
|
1407
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "Summaries from today's deploys" })
|
|
1408
|
+
] }),
|
|
1409
|
+
/* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-4", children: [
|
|
1410
|
+
/* @__PURE__ */ jsx(Message, { align: "end", children: /* @__PURE__ */ jsx(Bubble, { variant: "primary", align: "end", children: "What shipped to production today?" }) }),
|
|
1411
|
+
/* @__PURE__ */ jsx(Message, { children: /* @__PURE__ */ jsx(Bubble, { children: "Three deploys. Search ranking is fixed, webhook keys are rotated, and usage-based billing is building now." }) }),
|
|
1412
|
+
/* @__PURE__ */ jsx(ChatComposer, { placeholder: "Ask about a deploy" })
|
|
1413
|
+
] })
|
|
1414
|
+
] })
|
|
1415
|
+
] }) }),
|
|
1416
|
+
/* @__PURE__ */ jsx("figcaption", { className: "mt-4 text-center text-body-sm text-muted-foreground", children: "Every surface in this preview is a Sable component, styled by the active theme." })
|
|
1417
|
+
] }) }),
|
|
1418
|
+
/* @__PURE__ */ jsx("section", { "aria-labelledby": "landing-features", className: "border-y border-border bg-muted/40 py-20 sm:py-28", children: /* @__PURE__ */ jsx(Region, { max: "wide", children: /* @__PURE__ */ jsxs(Stack, { gap: 10, children: [
|
|
1419
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 3, className: "max-w-2xl", children: [
|
|
1420
|
+
/* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Why Sable" }),
|
|
1421
|
+
/* @__PURE__ */ jsx("h2", { id: "landing-features", className: "text-balance text-heading-1", children: "A design system with the rules written down" }),
|
|
1422
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-lg text-muted-foreground", children: "The decisions a senior designer would make are encoded once, so every screen your team or your agent builds starts from them." })
|
|
1423
|
+
] }),
|
|
1424
|
+
/* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx(Grid, { cols: { base: 1, sm: 2, lg: 3 }, gap: 4, children: features.map((feature) => /* @__PURE__ */ jsx(Card, { render: /* @__PURE__ */ jsx("article", {}), className: "h-full", children: /* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-3", children: [
|
|
1425
|
+
/* @__PURE__ */ jsx("span", { className: "grid size-10 place-items-center rounded-md sable-well", children: /* @__PURE__ */ jsx(Icon, { name: feature.icon, className: "text-xl" }) }),
|
|
1426
|
+
/* @__PURE__ */ jsx("h3", { className: "pt-2 text-title-lg", children: feature.title }),
|
|
1427
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: feature.body })
|
|
1428
|
+
] }) }, feature.title)) }) })
|
|
1429
|
+
] }) }) }),
|
|
1430
|
+
/* @__PURE__ */ jsx(Region, { as: "section", "aria-labelledby": "landing-setup", max: "wide", className: "py-20 sm:py-28", children: /* @__PURE__ */ jsxs("div", { className: "grid items-start gap-10 lg:grid-cols-[minmax(0,5fr)_minmax(0,7fr)] lg:gap-16", children: [
|
|
1431
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
|
|
1432
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1433
|
+
/* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Setup" }),
|
|
1434
|
+
/* @__PURE__ */ jsx("h2", { id: "landing-setup", className: "text-balance text-heading-1", children: "From install to first screen in three steps" })
|
|
1435
|
+
] }),
|
|
1436
|
+
/* @__PURE__ */ jsx("ol", { className: "flex flex-col gap-6", children: steps2.map((step, index) => /* @__PURE__ */ jsxs("li", { className: "grid grid-cols-[auto_1fr] gap-4", children: [
|
|
1437
|
+
/* @__PURE__ */ jsxs("span", { className: "pt-0.5 text-mono-md text-muted-foreground", children: [
|
|
1438
|
+
"0",
|
|
1439
|
+
index + 1
|
|
1440
|
+
] }),
|
|
1441
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1442
|
+
/* @__PURE__ */ jsx("h3", { className: "text-title-lg", children: step.title }),
|
|
1443
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-body-md text-muted-foreground", children: step.body })
|
|
1444
|
+
] })
|
|
1445
|
+
] }, step.title)) })
|
|
1446
|
+
] }),
|
|
1447
|
+
/* @__PURE__ */ jsx(Reveal, { className: "min-w-0", children: /* @__PURE__ */ jsxs(Tabs, { defaultValue: "compose", children: [
|
|
1448
|
+
/* @__PURE__ */ jsxs(TabsList, { children: [
|
|
1449
|
+
/* @__PURE__ */ jsx(TabsTrigger, { value: "install", children: "Install" }),
|
|
1450
|
+
/* @__PURE__ */ jsx(TabsTrigger, { value: "styles", children: "Styles" }),
|
|
1451
|
+
/* @__PURE__ */ jsx(TabsTrigger, { value: "compose", children: "Compose" })
|
|
1452
|
+
] }),
|
|
1453
|
+
/* @__PURE__ */ jsx(TabsPanel, { value: "install", className: "pt-4", children: /* @__PURE__ */ jsx(CodeBlock, { prompt: "$", code: "pnpm add @sableui/react" }) }),
|
|
1454
|
+
/* @__PURE__ */ jsx(TabsPanel, { value: "styles", className: "pt-4", children: /* @__PURE__ */ jsx(CodeBlock, { filename: "app.css", code: stylesCode }) }),
|
|
1455
|
+
/* @__PURE__ */ jsx(TabsPanel, { value: "compose", className: "pt-4", children: /* @__PURE__ */ jsx(CodeBlock, { filename: "deploy-card.tsx", code: composeCode }) })
|
|
1456
|
+
] }) })
|
|
1457
|
+
] }) }),
|
|
1458
|
+
/* @__PURE__ */ jsx("section", { "aria-labelledby": "landing-agents", className: "border-t border-border py-20 sm:py-28", children: /* @__PURE__ */ jsx(Region, { max: "wide", children: /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-10 lg:grid-cols-2 lg:gap-16", children: [
|
|
1459
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 5, children: [
|
|
1460
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1461
|
+
/* @__PURE__ */ jsx("p", { className: "text-overline text-muted-foreground", children: "Built for agents" }),
|
|
1462
|
+
/* @__PURE__ */ jsx("h2", { id: "landing-agents", className: "text-balance text-heading-1", children: "Your agent reads the same contract you do" }),
|
|
1463
|
+
/* @__PURE__ */ jsxs("p", { className: "text-body-lg text-muted-foreground", children: [
|
|
1464
|
+
"Point Claude Code, Codex, or Cursor at the installed package. The",
|
|
1465
|
+
" ",
|
|
1466
|
+
/* @__PURE__ */ jsx(Code, { children: "sable" }),
|
|
1467
|
+
" CLI answers what to use, how to use it, and whether the result holds up."
|
|
1468
|
+
] })
|
|
1469
|
+
] }),
|
|
1470
|
+
/* @__PURE__ */ jsx("ul", { className: "sable-list", children: agentPoints.map((point) => /* @__PURE__ */ jsx("li", { children: point }, point)) })
|
|
1471
|
+
] }),
|
|
1472
|
+
/* @__PURE__ */ jsx(Reveal, { className: "min-w-0", children: /* @__PURE__ */ jsx(CodeBlock, { filename: "Terminal", code: terminalCode, highlight: false }) })
|
|
1473
|
+
] }) }) }),
|
|
1474
|
+
/* @__PURE__ */ jsx(Region, { as: "section", "aria-labelledby": "landing-cta", max: "wide", className: "pb-20 sm:pb-28", children: /* @__PURE__ */ jsx(Reveal, { children: /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Stack, { gap: 4, align: "center", className: "py-8 text-center sm:py-12", children: [
|
|
1475
|
+
/* @__PURE__ */ jsx("h2", { id: "landing-cta", className: "max-w-2xl text-balance text-heading-1", children: "Build the product, not another component library" }),
|
|
1476
|
+
/* @__PURE__ */ jsx("p", { className: "max-w-(--measure-prose) text-body-lg text-muted-foreground", children: "Install Sable, pick a preset, and ship the first screen today." }),
|
|
1477
|
+
/* @__PURE__ */ jsxs(Row, { gap: 2, justify: "center", wrap: true, className: "pt-2", children: [
|
|
1478
|
+
/* @__PURE__ */ jsx(Button, { size: "cta", render: /* @__PURE__ */ jsx("a", { href: "/docs/getting-started" }), children: "Get started" }),
|
|
1479
|
+
/* @__PURE__ */ jsx(Button, { size: "cta", variant: "outline", render: /* @__PURE__ */ jsx("a", { href: "/github" }), children: "Star on GitHub" })
|
|
1480
|
+
] })
|
|
1481
|
+
] }) }) }) }) })
|
|
747
1482
|
] }),
|
|
748
|
-
/* @__PURE__ */ jsx("footer", { className: "border-t border-border", children: /* @__PURE__ */ jsxs(Region, {
|
|
749
|
-
/* @__PURE__ */
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
1483
|
+
/* @__PURE__ */ jsx("footer", { className: "border-t border-border", children: /* @__PURE__ */ jsxs(Region, { max: "wide", className: "py-12", children: [
|
|
1484
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-10 md:grid-cols-[minmax(0,2fr)_minmax(0,3fr)]", children: [
|
|
1485
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 3, className: "max-w-xs", children: [
|
|
1486
|
+
/* @__PURE__ */ jsx("a", { href: "/", className: "w-fit text-title-lg motion-interactive hover:text-muted-foreground", children: "Sable" }),
|
|
1487
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: "React components, patterns, and tokens for products built with AI agents." })
|
|
1488
|
+
] }),
|
|
1489
|
+
/* @__PURE__ */ jsx("nav", { "aria-label": "Footer", className: "grid grid-cols-2 gap-8 sm:grid-cols-3", children: footerGroups2.map((group) => /* @__PURE__ */ jsxs("div", { children: [
|
|
1490
|
+
/* @__PURE__ */ jsx("h2", { className: "text-label-md text-muted-foreground", children: group.title }),
|
|
1491
|
+
/* @__PURE__ */ jsx("ul", { className: "mt-3 flex flex-col gap-1", children: group.links.map((link) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
1492
|
+
"a",
|
|
1493
|
+
{
|
|
1494
|
+
href: link.href,
|
|
1495
|
+
className: "flex min-h-9 items-center text-body-sm motion-interactive hover:text-muted-foreground",
|
|
1496
|
+
children: link.label
|
|
1497
|
+
}
|
|
1498
|
+
) }, link.label)) })
|
|
1499
|
+
] }, group.title)) })
|
|
1500
|
+
] }),
|
|
1501
|
+
/* @__PURE__ */ jsx("div", { className: "mt-12 border-t border-border pt-6", children: /* @__PURE__ */ jsx("p", { className: "text-caption text-muted-foreground", children: "\xA9 2026 Sable. Released under the MIT license." }) })
|
|
754
1502
|
] }) })
|
|
755
1503
|
] });
|
|
756
1504
|
}
|
|
@@ -758,71 +1506,367 @@ function LandingPage() {
|
|
|
758
1506
|
// src/patterns/landing-page.meta.ts
|
|
759
1507
|
var landingPageMeta = {
|
|
760
1508
|
name: "LandingPage",
|
|
761
|
-
summary: "
|
|
762
|
-
intent: "Reach for this for the marketing
|
|
1509
|
+
summary: "Product / open-source landing page: a centered hero with an eyebrow Badge, one display headline, lede, two CTAs and a copyable install command; a Browser-framed product preview composed from real components; a six-up feature grid; a setup section with tabbed code; an agent-workflow section with terminal output; a CTA card; and a link-column footer.",
|
|
1510
|
+
intent: "Reach for this for the marketing route of a developer product, library, or tool: the page where the product shows itself working and the reader installs or signs up. For a branded, editorial marketing site use BrandLandingPage. Not for product UI (display type and one-per-view rules forbid it inside the app).",
|
|
763
1511
|
notes: [
|
|
764
|
-
"Display type is the marketing voice: exactly one display headline per page
|
|
765
|
-
'The eyebrow is a small outline Badge (`size="sm"`)
|
|
766
|
-
"The lede is body-lg capped at the prose measure
|
|
767
|
-
|
|
768
|
-
"
|
|
769
|
-
"
|
|
1512
|
+
"Display type is the marketing voice: exactly one display headline per page (`text-display-lg` on phones, `text-display-2xl` from `sm`). Every section below uses `text-heading-1` with a `text-overline` kicker.",
|
|
1513
|
+
'The eyebrow is a small outline Badge (`size="sm"`): the badge\'s own size, never a type class forced onto its label. Hierarchy above the fold is eyebrow, display, lede, CTAs, install command.',
|
|
1514
|
+
"The lede is body-lg capped at the prose measure. The headline may be wide; reading text may not.",
|
|
1515
|
+
'Two CTAs max per row: one primary, one quiet outline. The install line is a `CodeBlock` with `prompt="$"`, so it truncates on one line and copy takes only the command.',
|
|
1516
|
+
"Show the product instead of describing it. The preview is real components (PageHeader, metric Cards, a Meter, a CardList of Entity rows, Message and Bubble, ChatComposer) inside `Browser`, wrapped in an `inert` div so the picture's controls never take focus. Secondary columns hide on small screens rather than stacking into a long mock.",
|
|
1517
|
+
"Features are Cards in a fixed `Grid cols={{ base: 1, sm: 2, lg: 3 }}`, so six items always land as full rows (3 x 2, 2 x 3, 1 x 6) without an orphan.",
|
|
1518
|
+
"Multi-line code uses `CodeBlock` with a filename; switch related snippets with `Tabs`, defaulting to the most complete one. Terminal output passes `highlight={false}` so a code highlighter does not colour prose.",
|
|
1519
|
+
"Reveal wraps below-the-fold blocks only (features, code, CTA). It no-ops under reduced motion via the motion policy.",
|
|
1520
|
+
"Hide nav groups on narrow screens by wrapping them in a `max-md:hidden` div: a `Row` or `Button` sets its own display, which a `hidden` class on the same element does not reliably override."
|
|
770
1521
|
],
|
|
771
1522
|
a11y: [
|
|
772
|
-
"The hero headline is the page h1; feature titles are h2s
|
|
1523
|
+
"The hero headline is the page h1; section titles are h2s and feature and step titles are h3s. Footer column labels are h2s inside the footer nav.",
|
|
1524
|
+
"The product preview is a `figure` with a visible figcaption. Its content is `inert`, which removes it from the tab order and the accessibility tree, so its headings and buttons do not pollute the outline.",
|
|
773
1525
|
"Reveal renders content immediately (no animation) when prefers-reduced-motion is set.",
|
|
774
|
-
"CTA buttons are real links
|
|
1526
|
+
"CTA buttons are real links with visible text, with no icon-only CTAs. Footer links keep a 36px minimum row height for touch."
|
|
775
1527
|
]
|
|
776
1528
|
};
|
|
777
|
-
var
|
|
778
|
-
{
|
|
779
|
-
{
|
|
780
|
-
{
|
|
781
|
-
{
|
|
782
|
-
|
|
783
|
-
|
|
1529
|
+
var roles = [
|
|
1530
|
+
{ value: "owner", label: "Owner" },
|
|
1531
|
+
{ value: "admin", label: "Admin" },
|
|
1532
|
+
{ value: "editor", label: "Editor" },
|
|
1533
|
+
{ value: "viewer", label: "Viewer" }
|
|
1534
|
+
];
|
|
1535
|
+
var roleLabel = {
|
|
1536
|
+
owner: "Owner",
|
|
1537
|
+
admin: "Admin",
|
|
1538
|
+
editor: "Editor",
|
|
1539
|
+
viewer: "Viewer"
|
|
1540
|
+
};
|
|
1541
|
+
var roleSummary = {
|
|
1542
|
+
owner: "Full access, including payouts and billing. Transfer ownership in studio settings.",
|
|
1543
|
+
admin: "Manages members, editions and payouts.",
|
|
1544
|
+
editor: "Drafts, prices and releases editions.",
|
|
1545
|
+
viewer: "Sees editions and sales, but can't change them."
|
|
1546
|
+
};
|
|
1547
|
+
var seats = 10;
|
|
1548
|
+
var initialMembers = [
|
|
1549
|
+
{
|
|
1550
|
+
id: "u_01",
|
|
1551
|
+
name: "Ada Lovelace",
|
|
1552
|
+
handle: "@ada",
|
|
1553
|
+
email: "ada@atelier.art",
|
|
1554
|
+
initials: "AL",
|
|
1555
|
+
role: "owner",
|
|
1556
|
+
pending: false,
|
|
1557
|
+
joined: "Mar 4, 2024",
|
|
1558
|
+
lastActive: "Online now",
|
|
1559
|
+
editions: 12,
|
|
1560
|
+
activity: [
|
|
1561
|
+
{ id: "a1", icon: "gem", text: "Released Analytical Engine No. 3 \u2014 edition of 50", when: "2 hours ago" },
|
|
1562
|
+
{ id: "a2", icon: "mail", text: "Invited louise@bourgeois.studio as Editor", when: "2 days ago" }
|
|
1563
|
+
]
|
|
1564
|
+
},
|
|
1565
|
+
{
|
|
1566
|
+
id: "u_02",
|
|
1567
|
+
name: "Grace Hopper",
|
|
1568
|
+
handle: "@grace",
|
|
1569
|
+
email: "grace@atelier.art",
|
|
1570
|
+
initials: "GH",
|
|
1571
|
+
role: "admin",
|
|
1572
|
+
pending: false,
|
|
1573
|
+
joined: "Mar 9, 2024",
|
|
1574
|
+
lastActive: "20 minutes ago",
|
|
1575
|
+
editions: 4,
|
|
1576
|
+
activity: [
|
|
1577
|
+
{ id: "a1", icon: "user", text: "Changed Frida Kahlo's role to Viewer", when: "Yesterday" },
|
|
1578
|
+
{ id: "a2", icon: "edit", text: "Updated pricing on First Compiler \u2014 edition of 10", when: "3 days ago" },
|
|
1579
|
+
{ id: "a3", icon: "gem", text: "Released First Compiler \u2014 edition of 10", when: "Sep 28" }
|
|
1580
|
+
]
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
id: "u_03",
|
|
1584
|
+
name: "Mary Shelley",
|
|
1585
|
+
handle: "@mary",
|
|
1586
|
+
email: "mary@atelier.art",
|
|
1587
|
+
initials: "MS",
|
|
1588
|
+
role: "editor",
|
|
1589
|
+
pending: false,
|
|
1590
|
+
joined: "Jun 18, 2024",
|
|
1591
|
+
lastActive: "3 hours ago",
|
|
1592
|
+
editions: 7,
|
|
1593
|
+
activity: [
|
|
1594
|
+
{ id: "a1", icon: "calendar", text: "Scheduled Frankenstein Studies for Oct 14", when: "3 hours ago" },
|
|
1595
|
+
{ id: "a2", icon: "gem", text: "Released The Last Man \u2014 edition of 25", when: "Sep 21" }
|
|
1596
|
+
]
|
|
1597
|
+
},
|
|
1598
|
+
{
|
|
1599
|
+
id: "u_04",
|
|
1600
|
+
name: "Nina Simone",
|
|
1601
|
+
handle: "@nina",
|
|
1602
|
+
email: "nina@atelier.art",
|
|
1603
|
+
initials: "NS",
|
|
1604
|
+
role: "editor",
|
|
1605
|
+
pending: false,
|
|
1606
|
+
joined: "Jan 12, 2025",
|
|
1607
|
+
lastActive: "Yesterday",
|
|
1608
|
+
editions: 3,
|
|
1609
|
+
activity: [
|
|
1610
|
+
{ id: "a1", icon: "upload", text: "Uploaded artwork for Little Girl Blue", when: "Yesterday" },
|
|
1611
|
+
{ id: "a2", icon: "gem", text: "Released Feeling Good \u2014 edition of 100", when: "Aug 30" }
|
|
1612
|
+
]
|
|
1613
|
+
},
|
|
1614
|
+
{
|
|
1615
|
+
id: "u_05",
|
|
1616
|
+
name: "Frida Kahlo",
|
|
1617
|
+
handle: "@frida",
|
|
1618
|
+
email: "frida@atelier.art",
|
|
1619
|
+
initials: "FK",
|
|
1620
|
+
role: "viewer",
|
|
1621
|
+
pending: false,
|
|
1622
|
+
joined: "Jul 2, 2025",
|
|
1623
|
+
lastActive: "4 days ago",
|
|
1624
|
+
editions: 0,
|
|
1625
|
+
activity: [{ id: "a1", icon: "comment", text: "Commented on the Las Dos Fridas draft", when: "4 days ago" }]
|
|
1626
|
+
},
|
|
1627
|
+
{
|
|
1628
|
+
id: "i_01",
|
|
1629
|
+
name: "louise@bourgeois.studio",
|
|
1630
|
+
email: "louise@bourgeois.studio",
|
|
1631
|
+
initials: "",
|
|
1632
|
+
role: "editor",
|
|
1633
|
+
pending: true,
|
|
1634
|
+
invitedBy: "Ada Lovelace",
|
|
1635
|
+
sent: "2 days ago",
|
|
1636
|
+
expires: "In 5 days",
|
|
1637
|
+
activity: []
|
|
1638
|
+
}
|
|
784
1639
|
];
|
|
785
1640
|
function ListDetailPage() {
|
|
1641
|
+
const [members, setMembers] = useState(initialMembers);
|
|
1642
|
+
const [selectedId, setSelectedId] = useState("u_02");
|
|
1643
|
+
const [query, setQuery] = useState("");
|
|
1644
|
+
const [showDetail, setShowDetail] = useState(false);
|
|
1645
|
+
const [confirming, setConfirming] = useState(false);
|
|
1646
|
+
const listRef = useRef(null);
|
|
1647
|
+
const backRef = useRef(null);
|
|
1648
|
+
const searchRef = useRef(null);
|
|
1649
|
+
const needle = query.trim().toLowerCase();
|
|
1650
|
+
const visible = members.filter(
|
|
1651
|
+
(m) => [m.name, m.email, m.handle ?? "", roleLabel[m.role]].some((v) => v.toLowerCase().includes(needle))
|
|
1652
|
+
);
|
|
1653
|
+
const selected = members.find((m) => m.id === selectedId) ?? null;
|
|
1654
|
+
const active = members.filter((m) => !m.pending).length;
|
|
1655
|
+
const pending2 = members.length - active;
|
|
1656
|
+
useEffect(() => {
|
|
1657
|
+
if (showDetail && backRef.current?.offsetParent) backRef.current.focus();
|
|
1658
|
+
}, [showDetail, selectedId]);
|
|
1659
|
+
function select(id) {
|
|
1660
|
+
setSelectedId(id);
|
|
1661
|
+
setShowDetail(true);
|
|
1662
|
+
}
|
|
1663
|
+
function focusRow(id) {
|
|
1664
|
+
requestAnimationFrame(() => {
|
|
1665
|
+
listRef.current?.querySelector(`[data-member-id="${id}"] button`)?.focus();
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
function backToList() {
|
|
1669
|
+
setShowDetail(false);
|
|
1670
|
+
focusRow(selectedId ?? void 0);
|
|
1671
|
+
}
|
|
1672
|
+
function setRole(id, role) {
|
|
1673
|
+
setMembers((list) => list.map((m) => m.id === id ? { ...m, role } : m));
|
|
1674
|
+
}
|
|
1675
|
+
function resend(id) {
|
|
1676
|
+
setMembers(
|
|
1677
|
+
(list) => list.map((m) => m.id === id ? { ...m, sent: "Just now", expires: "In 7 days", resent: true } : m)
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
function remove(id) {
|
|
1681
|
+
const index = members.findIndex((m) => m.id === id);
|
|
1682
|
+
const rest = members.filter((m) => m.id !== id);
|
|
1683
|
+
setMembers(rest);
|
|
1684
|
+
const next = rest[Math.min(index, rest.length - 1)]?.id;
|
|
1685
|
+
setSelectedId(next ?? null);
|
|
1686
|
+
setShowDetail(false);
|
|
1687
|
+
focusRow(next);
|
|
1688
|
+
}
|
|
786
1689
|
return /* @__PURE__ */ jsxs(Page, { as: "main", max: "wide", gap: 4, className: "py-6", children: [
|
|
787
1690
|
/* @__PURE__ */ jsx(
|
|
788
1691
|
PageHeader,
|
|
789
1692
|
{
|
|
790
1693
|
title: "Members",
|
|
791
|
-
description: "
|
|
792
|
-
actions: /* @__PURE__ */ jsx(Button, { children: "Invite member" })
|
|
1694
|
+
description: `${active} members \xB7 ${pending2} pending ${pending2 === 1 ? "invite" : "invites"} \xB7 ${members.length} of ${seats} seats used`,
|
|
1695
|
+
actions: /* @__PURE__ */ jsx(Button, { iconStart: /* @__PURE__ */ jsx(Icon, { name: "plus" }), children: "Invite member" })
|
|
793
1696
|
}
|
|
794
1697
|
),
|
|
795
1698
|
/* @__PURE__ */ jsxs(Columns, { count: { base: 1, lg: 12 }, children: [
|
|
796
|
-
/* @__PURE__ */ jsx(Col, { span: { base: 1, lg:
|
|
797
|
-
/* @__PURE__ */ jsx(
|
|
798
|
-
|
|
799
|
-
Entity,
|
|
1699
|
+
/* @__PURE__ */ jsx(Col, { span: { base: 1, lg: 5 }, className: showDetail ? "max-lg:hidden" : void 0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1700
|
+
/* @__PURE__ */ jsx(
|
|
1701
|
+
SearchInput,
|
|
800
1702
|
{
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1703
|
+
ref: searchRef,
|
|
1704
|
+
value: query,
|
|
1705
|
+
onValueChange: setQuery,
|
|
1706
|
+
placeholder: "Search name, email or role",
|
|
1707
|
+
"aria-label": "Search members"
|
|
1708
|
+
}
|
|
1709
|
+
),
|
|
1710
|
+
/* @__PURE__ */ jsx(Card, { ref: listRef, className: "sable-surface", children: visible.length > 0 ? /* @__PURE__ */ jsx(CardList, { "aria-label": "Members", children: visible.map((m) => {
|
|
1711
|
+
const isSelected = m.id === selectedId;
|
|
1712
|
+
return /* @__PURE__ */ jsx(
|
|
1713
|
+
Entity,
|
|
1714
|
+
{
|
|
1715
|
+
"data-member-id": m.id,
|
|
1716
|
+
"data-selected": isSelected ? "" : void 0,
|
|
1717
|
+
className: "sable-surface-interactive",
|
|
1718
|
+
interactive: true,
|
|
1719
|
+
onSelect: () => select(m.id),
|
|
1720
|
+
leading: /* @__PURE__ */ jsx(
|
|
1721
|
+
Avatar,
|
|
1722
|
+
{
|
|
1723
|
+
fallback: m.pending ? /* @__PURE__ */ jsx(Icon, { name: "mail", className: "size-5" }) : m.initials,
|
|
1724
|
+
status: m.lastActive === "Online now" ? /* @__PURE__ */ jsx(StatusDot, { tone: "success" }) : void 0,
|
|
1725
|
+
"aria-hidden": true
|
|
1726
|
+
}
|
|
1727
|
+
),
|
|
1728
|
+
title: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1729
|
+
m.name,
|
|
1730
|
+
isSelected ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: " (selected)" }) : null
|
|
1731
|
+
] }),
|
|
1732
|
+
subtitle: m.pending ? `${roleLabel[m.role]} \xB7 invite sent ${m.sent?.toLowerCase()}` : `${roleLabel[m.role]} \xB7 ${m.handle}`,
|
|
1733
|
+
action: m.pending ? /* @__PURE__ */ jsx(Badge, { tone: "info", children: "Invited" }) : void 0
|
|
1734
|
+
},
|
|
1735
|
+
m.id
|
|
1736
|
+
);
|
|
1737
|
+
}) }) : /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(
|
|
1738
|
+
EmptyState,
|
|
1739
|
+
{
|
|
1740
|
+
icon: /* @__PURE__ */ jsx(Icon, { name: "search" }),
|
|
1741
|
+
title: "No members found",
|
|
1742
|
+
description: `Nobody matches \u201C${query.trim()}\u201D. Try a name, email or role.`,
|
|
1743
|
+
action: /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", onClick: () => {
|
|
1744
|
+
setQuery("");
|
|
1745
|
+
searchRef.current?.focus();
|
|
1746
|
+
}, children: "Clear search" })
|
|
1747
|
+
}
|
|
1748
|
+
) }) })
|
|
814
1749
|
] }) }),
|
|
815
|
-
/* @__PURE__ */ jsx(Col, { span: { base: 1, lg:
|
|
816
|
-
/* @__PURE__ */ jsx(
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1750
|
+
/* @__PURE__ */ jsx(Col, { span: { base: 1, lg: 7 }, className: showDetail ? void 0 : "max-lg:hidden", children: /* @__PURE__ */ jsxs(Stack, { role: "region", "aria-label": "Member details", gap: 3, className: "lg:sticky lg:top-6", children: [
|
|
1751
|
+
/* @__PURE__ */ jsx(
|
|
1752
|
+
Button,
|
|
1753
|
+
{
|
|
1754
|
+
ref: backRef,
|
|
1755
|
+
variant: "ghost",
|
|
1756
|
+
size: "sm",
|
|
1757
|
+
iconStart: /* @__PURE__ */ jsx(Icon, { name: "chevron-left" }),
|
|
1758
|
+
onClick: backToList,
|
|
1759
|
+
className: "self-start lg:hidden",
|
|
1760
|
+
children: "All members"
|
|
1761
|
+
}
|
|
1762
|
+
),
|
|
1763
|
+
selected === null ? /* @__PURE__ */ jsx(
|
|
1764
|
+
EmptyState,
|
|
1765
|
+
{
|
|
1766
|
+
surface: "card",
|
|
1767
|
+
icon: /* @__PURE__ */ jsx(Icon, { name: "user" }),
|
|
1768
|
+
title: "No member selected",
|
|
1769
|
+
description: "Choose someone from the list to see their role and activity."
|
|
1770
|
+
}
|
|
1771
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1772
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
1773
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
1774
|
+
/* @__PURE__ */ jsxs(Row, { gap: 2, children: [
|
|
1775
|
+
/* @__PURE__ */ jsx(
|
|
1776
|
+
Avatar,
|
|
1777
|
+
{
|
|
1778
|
+
size: "lg",
|
|
1779
|
+
fallback: selected.pending ? /* @__PURE__ */ jsx(Icon, { name: "mail", className: "size-6" }) : selected.initials,
|
|
1780
|
+
status: selected.lastActive === "Online now" ? /* @__PURE__ */ jsx(StatusDot, { tone: "success" }) : void 0,
|
|
1781
|
+
alt: selected.pending ? "Pending invite" : selected.name
|
|
1782
|
+
}
|
|
1783
|
+
),
|
|
1784
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 0, className: "min-w-0", children: [
|
|
1785
|
+
/* @__PURE__ */ jsx(CardTitle, { role: "heading", "aria-level": 2, className: "truncate", children: selected.name }),
|
|
1786
|
+
/* @__PURE__ */ jsx(CardDescription, { className: "truncate", children: selected.pending ? "Hasn't joined yet" : `${selected.handle} \xB7 ${selected.email}` })
|
|
1787
|
+
] })
|
|
1788
|
+
] }),
|
|
1789
|
+
selected.pending ? /* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Badge, { tone: "info", children: "Invited" }) }) : null
|
|
1790
|
+
] }),
|
|
1791
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Stack, { gap: 5, children: [
|
|
1792
|
+
/* @__PURE__ */ jsx(
|
|
1793
|
+
Field,
|
|
1794
|
+
{
|
|
1795
|
+
label: selected.pending ? "Role on joining" : "Role",
|
|
1796
|
+
description: roleSummary[selected.role],
|
|
1797
|
+
children: /* @__PURE__ */ jsxs(
|
|
1798
|
+
Select,
|
|
1799
|
+
{
|
|
1800
|
+
items: roles,
|
|
1801
|
+
value: selected.role,
|
|
1802
|
+
onValueChange: (value) => setRole(selected.id, value),
|
|
1803
|
+
disabled: selected.role === "owner",
|
|
1804
|
+
children: [
|
|
1805
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "max-w-72" }),
|
|
1806
|
+
/* @__PURE__ */ jsx(SelectContent, { children: roles.filter((r) => r.value !== "owner" || selected.role === "owner").map((r) => /* @__PURE__ */ jsx(SelectItem, { value: r.value, children: r.label }, r.value)) })
|
|
1807
|
+
]
|
|
1808
|
+
}
|
|
1809
|
+
)
|
|
1810
|
+
}
|
|
1811
|
+
),
|
|
1812
|
+
/* @__PURE__ */ jsx("div", { className: "@container", children: selected.pending ? /* @__PURE__ */ jsxs("dl", { className: "sable-list-dl sable-list-dl-2", children: [
|
|
1813
|
+
/* @__PURE__ */ jsx("dt", { children: "Sent" }),
|
|
1814
|
+
/* @__PURE__ */ jsx("dd", { children: selected.sent }),
|
|
1815
|
+
/* @__PURE__ */ jsx("dt", { children: "Expires" }),
|
|
1816
|
+
/* @__PURE__ */ jsx("dd", { children: selected.expires }),
|
|
1817
|
+
/* @__PURE__ */ jsx("dt", { children: "Invited by" }),
|
|
1818
|
+
/* @__PURE__ */ jsx("dd", { children: selected.invitedBy })
|
|
1819
|
+
] }) : /* @__PURE__ */ jsxs("dl", { className: "sable-list-dl sable-list-dl-2", children: [
|
|
1820
|
+
/* @__PURE__ */ jsx("dt", { children: "Email" }),
|
|
1821
|
+
/* @__PURE__ */ jsx("dd", { children: selected.email }),
|
|
1822
|
+
/* @__PURE__ */ jsx("dt", { children: "Joined" }),
|
|
1823
|
+
/* @__PURE__ */ jsx("dd", { children: selected.joined }),
|
|
1824
|
+
/* @__PURE__ */ jsx("dt", { children: "Last active" }),
|
|
1825
|
+
/* @__PURE__ */ jsx("dd", { className: selected.lastActive === "Online now" ? "text-success" : void 0, children: selected.lastActive }),
|
|
1826
|
+
/* @__PURE__ */ jsx("dt", { children: "Editions released" }),
|
|
1827
|
+
/* @__PURE__ */ jsx("dd", { children: selected.editions })
|
|
1828
|
+
] }) })
|
|
1829
|
+
] }) }),
|
|
1830
|
+
selected.role !== "owner" ? /* @__PURE__ */ jsx(CardFooter, { children: /* @__PURE__ */ jsxs(Row, { gap: 2, wrap: true, children: [
|
|
1831
|
+
selected.pending ? /* @__PURE__ */ jsx(
|
|
1832
|
+
Button,
|
|
1833
|
+
{
|
|
1834
|
+
variant: "secondary",
|
|
1835
|
+
size: "sm",
|
|
1836
|
+
iconStart: /* @__PURE__ */ jsx(Icon, { name: selected.resent ? "check" : "send" }),
|
|
1837
|
+
disabled: selected.resent,
|
|
1838
|
+
onClick: () => resend(selected.id),
|
|
1839
|
+
children: selected.resent ? "Invite resent" : "Resend invite"
|
|
1840
|
+
}
|
|
1841
|
+
) : null,
|
|
1842
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirming(true), children: selected.pending ? "Revoke invite" : "Remove from studio" })
|
|
1843
|
+
] }) }) : null
|
|
822
1844
|
] }),
|
|
823
|
-
/* @__PURE__ */
|
|
1845
|
+
selected.activity.length > 0 ? /* @__PURE__ */ jsxs(Card, { children: [
|
|
1846
|
+
/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { role: "heading", "aria-level": 3, children: "Recent activity" }) }),
|
|
1847
|
+
/* @__PURE__ */ jsx(CardList, { children: selected.activity.map((a) => /* @__PURE__ */ jsx(
|
|
1848
|
+
Entity,
|
|
1849
|
+
{
|
|
1850
|
+
leading: /* @__PURE__ */ jsx(Icon, { name: a.icon, className: "text-muted-foreground" }),
|
|
1851
|
+
title: a.text,
|
|
1852
|
+
subtitle: a.when
|
|
1853
|
+
},
|
|
1854
|
+
a.id
|
|
1855
|
+
)) })
|
|
1856
|
+
] }) : null,
|
|
1857
|
+
/* @__PURE__ */ jsx(
|
|
1858
|
+
DestructiveActionModal,
|
|
1859
|
+
{
|
|
1860
|
+
open: confirming,
|
|
1861
|
+
onOpenChange: setConfirming,
|
|
1862
|
+
title: selected.pending ? "Revoke this invite?" : `Remove ${selected.name}?`,
|
|
1863
|
+
description: selected.pending ? `${selected.email} won't be able to join with the link they were sent.` : "They lose access to the studio right away. Editions they released stay on sale.",
|
|
1864
|
+
confirmLabel: selected.pending ? "Revoke invite" : "Remove member",
|
|
1865
|
+
onConfirm: () => remove(selected.id)
|
|
1866
|
+
}
|
|
1867
|
+
)
|
|
824
1868
|
] })
|
|
825
|
-
] }) })
|
|
1869
|
+
] }) })
|
|
826
1870
|
] })
|
|
827
1871
|
] });
|
|
828
1872
|
}
|
|
@@ -830,25 +1874,65 @@ function ListDetailPage() {
|
|
|
830
1874
|
// src/patterns/list-detail-page.meta.ts
|
|
831
1875
|
var listDetailPageMeta = {
|
|
832
1876
|
name: "ListDetailPage",
|
|
833
|
-
summary: "
|
|
834
|
-
intent: "Reach for this when a route lists records AND
|
|
1877
|
+
summary: "Searchable list beside the selected record's detail: interactive Entity rows in a CardList (5 of 12 columns) and a sticky detail pane (7) with an editable field, a label/value list, record actions with a destructive confirmation, and recent activity. Below lg it becomes a drill-in.",
|
|
1878
|
+
intent: "Reach for this when a route lists records AND works on one at a time (members + member, inbox + message, orders + order). For pure browsing with no selection, a single-column FeedPage or a DashboardPage table is simpler.",
|
|
835
1879
|
notes: [
|
|
836
|
-
"Region cap is `wide`;
|
|
837
|
-
"
|
|
838
|
-
"
|
|
839
|
-
"
|
|
840
|
-
"The
|
|
1880
|
+
"Region cap is `wide`; Columns 12 \u2192 Col 5 (list) + Col 7 (detail). Below lg the list and detail share one column as a drill-in: selecting a row shows the detail with an `All members` back button (lg:hidden), and the back button returns to the list. The panes swap with `max-lg:hidden`, so desktop always shows both.",
|
|
1881
|
+
"Rows are interactive Entity inside one Card + CardList. Selection is `data-selected` on the row painted by `sable-surface-interactive`; `sable-surface` on the list card makes that step 12% ink off the card, so a selected row stays distinct from its muted avatar.",
|
|
1882
|
+
"Search filters the list in place (name, email, role). No matches swap the CardList for an EmptyState with a Clear search action; the selected detail stays open.",
|
|
1883
|
+
"A pending invite lives in the same list: a mail glyph in the Avatar, an `Invited` Badge, and an invite-specific detail (role on joining, sent, expires, invited by; Resend and Revoke actions).",
|
|
1884
|
+
"The role Select edits the record in place; its Field description states what the role can do. The owner's role is read-only and the owner has no remove action.",
|
|
1885
|
+
'Read-only facts are a native `<dl className="sable-list-dl sable-list-dl-2">` inside an `@container`, so pairs go two-up only when the pane is wide enough.',
|
|
1886
|
+
"Remove / revoke run through DestructiveActionModal; on confirm the next row is selected. Page-level counts (members, pending, seats) live in the PageHeader description and update with the list.",
|
|
1887
|
+
"The detail pane is sticky (lg:top-6) so it stays in view while a long list scrolls."
|
|
841
1888
|
],
|
|
842
1889
|
a11y: [
|
|
843
|
-
"
|
|
844
|
-
"The
|
|
1890
|
+
"Each row is a real button (Entity `interactive` + `onSelect`): Tab moves through rows, Enter/Space selects. The selected row carries visually hidden `(selected)` text in its name; list avatars are aria-hidden so initials are not read before the name.",
|
|
1891
|
+
"The detail pane is a labelled region (`Member details`) whose title is a level-2 heading.",
|
|
1892
|
+
"On a phone, drilling in moves focus to the back button; going back returns focus to the row that was open. After a remove, focus lands on the newly selected row; after Clear search, on the search field."
|
|
845
1893
|
]
|
|
846
1894
|
};
|
|
1895
|
+
var sections = [
|
|
1896
|
+
{ id: "profile", label: "Profile" },
|
|
1897
|
+
{ id: "notifications", label: "Notifications" },
|
|
1898
|
+
{ id: "payouts", label: "Payouts" },
|
|
1899
|
+
{ id: "account", label: "Account" }
|
|
1900
|
+
];
|
|
1901
|
+
var timezones = [
|
|
1902
|
+
{ value: "europe-london", label: "London (GMT+1)" },
|
|
1903
|
+
{ value: "europe-paris", label: "Paris (GMT+2)" },
|
|
1904
|
+
{ value: "america-new-york", label: "New York (GMT\u22124)" },
|
|
1905
|
+
{ value: "america-los-angeles", label: "Los Angeles (GMT\u22127)" },
|
|
1906
|
+
{ value: "asia-tokyo", label: "Tokyo (GMT+9)" }
|
|
1907
|
+
];
|
|
1908
|
+
var notifications = [
|
|
1909
|
+
{ name: "notifyCollects", title: "New collects", description: "When someone collects your work", on: true },
|
|
1910
|
+
{ name: "notifySoldOut", title: "Sold out", description: "When an edition sells its last copy", on: true },
|
|
1911
|
+
{ name: "notifyFollowers", title: "New followers", description: "When a collector follows you", on: false },
|
|
1912
|
+
{ name: "notifyDigest", title: "Weekly digest", description: "Sales and collectors, every Monday", on: true },
|
|
1913
|
+
{ name: "notifyProduct", title: "Product updates", description: "New Atelier features", on: false }
|
|
1914
|
+
];
|
|
1915
|
+
var payouts = [
|
|
1916
|
+
{ label: "Next payout", value: "$1,284.60", note: "Friday, Oct 16" },
|
|
1917
|
+
{ label: "Pending", value: "$412.00", note: "Clears in 7 days" },
|
|
1918
|
+
{ label: "Paid this year", value: "$18,920.00", note: "Across 214 collects" }
|
|
1919
|
+
];
|
|
847
1920
|
function SettingsPage() {
|
|
848
|
-
|
|
1921
|
+
const [activeSection, setActiveSection] = useState("profile");
|
|
1922
|
+
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
1923
|
+
return /* @__PURE__ */ jsxs(
|
|
849
1924
|
AppShell,
|
|
850
1925
|
{
|
|
851
|
-
header: /* @__PURE__ */ jsx(
|
|
1926
|
+
header: /* @__PURE__ */ jsx(
|
|
1927
|
+
AppHeader,
|
|
1928
|
+
{
|
|
1929
|
+
title: /* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", children: [
|
|
1930
|
+
/* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-xl" }),
|
|
1931
|
+
/* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" })
|
|
1932
|
+
] }),
|
|
1933
|
+
actions: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: "AL", alt: "Ada Lovelace" })
|
|
1934
|
+
}
|
|
1935
|
+
),
|
|
852
1936
|
bottomNav: /* @__PURE__ */ jsxs(BottomNav, { "aria-label": "Primary", children: [
|
|
853
1937
|
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "grid" }), label: "Overview", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/" }) }),
|
|
854
1938
|
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }), label: "Editions", showLabel: true, render: /* @__PURE__ */ jsx("a", { href: "/editions" }) }),
|
|
@@ -857,55 +1941,181 @@ function SettingsPage() {
|
|
|
857
1941
|
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), label: "Settings", showLabel: true, active: true, render: /* @__PURE__ */ jsx("a", { href: "/settings" }) })
|
|
858
1942
|
] }),
|
|
859
1943
|
sidebar: /* @__PURE__ */ jsxs(Sidebar, { children: [
|
|
860
|
-
/* @__PURE__ */ jsx(SidebarHeader, { children: /* @__PURE__ */
|
|
1944
|
+
/* @__PURE__ */ jsx(SidebarHeader, { children: /* @__PURE__ */ jsxs(Row, { gap: 1, align: "center", className: "px-3", children: [
|
|
1945
|
+
/* @__PURE__ */ jsx(Icon, { name: "gem", variant: "solid", className: "text-xl" }),
|
|
1946
|
+
/* @__PURE__ */ jsx("span", { className: "text-title-sm", children: "Atelier" })
|
|
1947
|
+
] }) }),
|
|
861
1948
|
/* @__PURE__ */ jsxs(SidebarNav, { children: [
|
|
862
1949
|
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "grid" }), label: "Overview", render: /* @__PURE__ */ jsx("a", { href: "/" }) }),
|
|
863
1950
|
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "image-stack" }), label: "Editions", render: /* @__PURE__ */ jsx("a", { href: "/editions" }) }),
|
|
864
1951
|
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "user" }), label: "Collectors", render: /* @__PURE__ */ jsx("a", { href: "/collectors" }) }),
|
|
865
|
-
/* @__PURE__ */ jsx(
|
|
1952
|
+
/* @__PURE__ */ jsx(
|
|
1953
|
+
SidebarItem,
|
|
1954
|
+
{
|
|
1955
|
+
icon: /* @__PURE__ */ jsx(Icon, { name: "bell" }),
|
|
1956
|
+
label: "Activity",
|
|
1957
|
+
badge: /* @__PURE__ */ jsx(NotificationBadge, { count: 4, tone: "neutral" }),
|
|
1958
|
+
render: /* @__PURE__ */ jsx("a", { href: "/activity" })
|
|
1959
|
+
}
|
|
1960
|
+
),
|
|
866
1961
|
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), label: "Settings", active: true, render: /* @__PURE__ */ jsx("a", { href: "/settings" }) })
|
|
867
|
-
] })
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
/* @__PURE__ */ jsx(
|
|
871
|
-
PageHeader,
|
|
1962
|
+
] }),
|
|
1963
|
+
/* @__PURE__ */ jsx(SidebarFooter, { className: "[--foreground:var(--sidebar-foreground)]", children: /* @__PURE__ */ jsx(
|
|
1964
|
+
Entity,
|
|
872
1965
|
{
|
|
873
|
-
|
|
874
|
-
|
|
1966
|
+
className: "[--entity-py:0]",
|
|
1967
|
+
leading: /* @__PURE__ */ jsx(Avatar, { size: "sm", fallback: "AL", alt: "" }),
|
|
1968
|
+
title: "Ada Lovelace",
|
|
1969
|
+
action: /* @__PURE__ */ jsxs(DotsMenu, { label: "Account", size: "icon-sm", children: [
|
|
1970
|
+
/* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/@ada" }), children: "View profile" }),
|
|
1971
|
+
/* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/settings" }), children: "Settings" }),
|
|
1972
|
+
/* @__PURE__ */ jsx(MenuSeparator, {}),
|
|
1973
|
+
/* @__PURE__ */ jsx(MenuItem, { children: "Sign out" })
|
|
1974
|
+
] })
|
|
875
1975
|
}
|
|
876
|
-
)
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
/* @__PURE__ */
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1976
|
+
) })
|
|
1977
|
+
] }),
|
|
1978
|
+
children: [
|
|
1979
|
+
/* @__PURE__ */ jsxs(Page, { max: "wide", gap: 4, className: "py-6", children: [
|
|
1980
|
+
/* @__PURE__ */ jsx(
|
|
1981
|
+
PageHeader,
|
|
1982
|
+
{
|
|
1983
|
+
title: "Settings",
|
|
1984
|
+
description: "Manage your profile, notifications, and payouts."
|
|
1985
|
+
}
|
|
1986
|
+
),
|
|
1987
|
+
/* @__PURE__ */ jsxs(Columns, { gap: 4, children: [
|
|
1988
|
+
/* @__PURE__ */ jsx(Col, { span: { base: 12, lg: 3 }, className: "hidden lg:block", children: /* @__PURE__ */ jsx(SideNav, { "aria-label": "Settings sections", className: "sticky top-6", children: sections.map((section) => /* @__PURE__ */ jsx(
|
|
1989
|
+
SideNavItem,
|
|
1990
|
+
{
|
|
1991
|
+
label: section.label,
|
|
1992
|
+
active: activeSection === section.id,
|
|
1993
|
+
onClick: () => setActiveSection(section.id),
|
|
1994
|
+
render: /* @__PURE__ */ jsx("a", { href: `#${section.id}` })
|
|
1995
|
+
},
|
|
1996
|
+
section.id
|
|
1997
|
+
)) }) }),
|
|
1998
|
+
/* @__PURE__ */ jsx(Col, { span: { base: 12, lg: 9 }, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1999
|
+
/* @__PURE__ */ jsxs(Card, { id: "profile", className: "scroll-mt-6", children: [
|
|
2000
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
2001
|
+
/* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h2", { children: "Profile" }) }),
|
|
2002
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "How collectors see you on Atelier." })
|
|
2003
|
+
] }),
|
|
2004
|
+
/* @__PURE__ */ jsxs(Form, { onSubmit: (event) => event.preventDefault(), className: "gap-0", children: [
|
|
2005
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
2006
|
+
/* @__PURE__ */ jsxs(Row, { gap: 2, children: [
|
|
2007
|
+
/* @__PURE__ */ jsx(Avatar, { size: "lg", fallback: "AL", alt: "Ada Lovelace" }),
|
|
2008
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 0.5, align: "start", children: [
|
|
2009
|
+
/* @__PURE__ */ jsxs(Row, { gap: 1, children: [
|
|
2010
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", children: "Upload photo" }),
|
|
2011
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", children: "Remove" })
|
|
2012
|
+
] }),
|
|
2013
|
+
/* @__PURE__ */ jsx("p", { className: "text-caption text-muted-foreground", children: "Square PNG or JPG, at least 400 px." })
|
|
2014
|
+
] })
|
|
2015
|
+
] }),
|
|
2016
|
+
/* @__PURE__ */ jsxs(Grid, { cols: { base: 1, sm: 2 }, gap: 3, children: [
|
|
2017
|
+
/* @__PURE__ */ jsx(Field, { label: "Display name", name: "displayName", required: true, children: /* @__PURE__ */ jsx(Input, { defaultValue: "Ada Lovelace", autoComplete: "name" }) }),
|
|
2018
|
+
/* @__PURE__ */ jsx(Field, { label: "Handle", name: "handle", description: "atelier.com/@ada", required: true, children: /* @__PURE__ */ jsx(Input, { defaultValue: "ada", autoComplete: "username", spellCheck: false }) })
|
|
2019
|
+
] }),
|
|
2020
|
+
/* @__PURE__ */ jsx(Field, { label: "Bio", name: "bio", description: "Shown on your profile and edition pages.", children: /* @__PURE__ */ jsx(
|
|
2021
|
+
Textarea,
|
|
2022
|
+
{
|
|
2023
|
+
rows: 3,
|
|
2024
|
+
defaultValue: "Generative artist working in plotted ink and code. New editions of 25 to 100 every month."
|
|
2025
|
+
}
|
|
2026
|
+
) }),
|
|
2027
|
+
/* @__PURE__ */ jsx(Grid, { cols: { base: 1, sm: 2 }, gap: 3, children: /* @__PURE__ */ jsx(Field, { label: "Timezone", name: "timezone", description: "Release times use this zone.", children: /* @__PURE__ */ jsxs(Select, { defaultValue: "europe-london", items: timezones, children: [
|
|
2028
|
+
/* @__PURE__ */ jsx(SelectTrigger, {}),
|
|
2029
|
+
/* @__PURE__ */ jsx(SelectContent, { children: timezones.map((zone) => /* @__PURE__ */ jsx(SelectItem, { value: zone.value, children: zone.label }, zone.value)) })
|
|
2030
|
+
] }) }) })
|
|
2031
|
+
] }) }),
|
|
2032
|
+
/* @__PURE__ */ jsx(CardFooter, { className: "justify-end", children: /* @__PURE__ */ jsx(Button, { type: "submit", children: "Save profile" }) })
|
|
2033
|
+
] })
|
|
2034
|
+
] }),
|
|
2035
|
+
/* @__PURE__ */ jsxs(Card, { id: "notifications", className: "scroll-mt-6", children: [
|
|
2036
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
2037
|
+
/* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h2", { children: "Notifications" }) }),
|
|
2038
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "Email updates about your editions. Changes save automatically." })
|
|
2039
|
+
] }),
|
|
2040
|
+
/* @__PURE__ */ jsx(CardList, { children: notifications.map((item) => /* @__PURE__ */ jsx(
|
|
2041
|
+
Entity,
|
|
2042
|
+
{
|
|
2043
|
+
title: item.title,
|
|
2044
|
+
subtitle: item.description,
|
|
2045
|
+
action: /* @__PURE__ */ jsx(
|
|
2046
|
+
Switch,
|
|
2047
|
+
{
|
|
2048
|
+
name: item.name,
|
|
2049
|
+
defaultChecked: item.on,
|
|
2050
|
+
"aria-label": item.title
|
|
2051
|
+
}
|
|
2052
|
+
)
|
|
2053
|
+
},
|
|
2054
|
+
item.name
|
|
2055
|
+
)) })
|
|
2056
|
+
] }),
|
|
2057
|
+
/* @__PURE__ */ jsxs(Card, { id: "payouts", className: "scroll-mt-6", children: [
|
|
2058
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
2059
|
+
/* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h2", { children: "Payouts" }) }),
|
|
2060
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "Paid in USD every Friday to Chase checking \u2022\u20224821." }),
|
|
2061
|
+
/* @__PURE__ */ jsx(CardAction, { children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", render: /* @__PURE__ */ jsx("a", { href: "/settings/payouts" }), children: "Manage payouts" }) })
|
|
2062
|
+
] }),
|
|
2063
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(Grid, { cols: { base: 1, sm: 3 }, gap: 3, children: payouts.map((stat) => /* @__PURE__ */ jsxs(Stack, { gap: 0.5, children: [
|
|
2064
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-sm text-muted-foreground", children: stat.label }),
|
|
2065
|
+
/* @__PURE__ */ jsx("p", { className: "text-mono-lg text-foreground", children: stat.value }),
|
|
2066
|
+
/* @__PURE__ */ jsx("p", { className: "text-caption text-muted-foreground", children: stat.note })
|
|
2067
|
+
] }, stat.label)) }) })
|
|
2068
|
+
] }),
|
|
2069
|
+
/* @__PURE__ */ jsxs(Card, { id: "account", className: "scroll-mt-6", children: [
|
|
2070
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
2071
|
+
/* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h2", { children: "Account" }) }),
|
|
2072
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "Sign-in details and your data." })
|
|
2073
|
+
] }),
|
|
2074
|
+
/* @__PURE__ */ jsxs(CardList, { children: [
|
|
2075
|
+
/* @__PURE__ */ jsx(
|
|
2076
|
+
Entity,
|
|
2077
|
+
{
|
|
2078
|
+
leading: /* @__PURE__ */ jsx(Icon, { name: "mail", className: "text-lg text-muted-foreground" }),
|
|
2079
|
+
title: "Email",
|
|
2080
|
+
subtitle: "ada@atelier.art",
|
|
2081
|
+
action: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: "Change" })
|
|
2082
|
+
}
|
|
2083
|
+
),
|
|
2084
|
+
/* @__PURE__ */ jsx(
|
|
2085
|
+
Entity,
|
|
2086
|
+
{
|
|
2087
|
+
leading: /* @__PURE__ */ jsx(Icon, { name: "download", className: "text-lg text-muted-foreground" }),
|
|
2088
|
+
title: "Export data",
|
|
2089
|
+
subtitle: "Editions, sales, and collectors",
|
|
2090
|
+
action: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: "Export" })
|
|
2091
|
+
}
|
|
2092
|
+
)
|
|
2093
|
+
] })
|
|
2094
|
+
] }),
|
|
2095
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
2096
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
2097
|
+
/* @__PURE__ */ jsx(CardTitle, { children: /* @__PURE__ */ jsx("h2", { children: "Delete account" }) }),
|
|
2098
|
+
/* @__PURE__ */ jsx(CardDescription, { children: "Permanently remove your profile, editions, and sales history. Collectors keep the editions they own." })
|
|
2099
|
+
] }),
|
|
2100
|
+
/* @__PURE__ */ jsx(CardFooter, { className: "justify-end", children: /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => setDeleteOpen(true), children: "Delete account" }) })
|
|
889
2101
|
] })
|
|
890
2102
|
] }) })
|
|
891
|
-
] })
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
] })
|
|
908
|
-
] })
|
|
2103
|
+
] })
|
|
2104
|
+
] }),
|
|
2105
|
+
/* @__PURE__ */ jsx(
|
|
2106
|
+
DestructiveActionModal,
|
|
2107
|
+
{
|
|
2108
|
+
open: deleteOpen,
|
|
2109
|
+
onOpenChange: setDeleteOpen,
|
|
2110
|
+
title: "Delete your account?",
|
|
2111
|
+
description: "This permanently removes your profile, 12 editions, and sales history. Pending payouts are still sent. This can't be undone.",
|
|
2112
|
+
confirmLabel: "Delete account",
|
|
2113
|
+
confirmingLabel: "Deleting\u2026",
|
|
2114
|
+
confirmText: "ada",
|
|
2115
|
+
onConfirm: () => setDeleteOpen(false)
|
|
2116
|
+
}
|
|
2117
|
+
)
|
|
2118
|
+
]
|
|
909
2119
|
}
|
|
910
2120
|
);
|
|
911
2121
|
}
|
|
@@ -913,21 +2123,25 @@ function SettingsPage() {
|
|
|
913
2123
|
// src/patterns/settings-page.meta.ts
|
|
914
2124
|
var settingsPageMeta = {
|
|
915
2125
|
name: "SettingsPage",
|
|
916
|
-
summary: "
|
|
2126
|
+
summary: "Account settings inside the app shell: a sticky SideNav section rail beside a column of section Cards \u2014 a Profile form with the page's one explicit save, Card + CardList + Entity rows with Switches that apply instantly, a read-only payouts summary, account rows, and a delete card confirmed through DestructiveActionModal.",
|
|
917
2127
|
intent: "Reach for this for account/profile/workspace settings and any 'edit a handful of grouped preferences' route. Not for a single short form (use AuthPage's centered form shape) or bulk record editing (use DashboardPage + DataTable).",
|
|
918
2128
|
notes: [
|
|
919
|
-
"Region cap is `
|
|
920
|
-
"
|
|
921
|
-
"
|
|
922
|
-
"
|
|
923
|
-
|
|
2129
|
+
"Region cap is `wide`, split on a 12-column track: a 3-column SideNav rail and a 9-column section column. The rail fills the space a lone content-width column would leave empty and gives the page its table of contents.",
|
|
2130
|
+
"The rail is `SideNav` (the documented settings sub-nav), sticky, with in-page anchor items; it hides below lg, where the sections simply stack and the shell's BottomNav carries app navigation.",
|
|
2131
|
+
"One save model, stated in the copy: drafted text (the Profile form) commits through its single footer save; toggles apply the moment they change, and the Notifications description says so. Never pair a Switch with a Save button.",
|
|
2132
|
+
"Settings rows are Card + CardList + Entity with the Switch in `action` \u2014 the row owns its title and one-line description; keep descriptions short, because Entity truncates its subtitle.",
|
|
2133
|
+
"Each section is a Card whose CardTitle holds an h2, so the page has a real heading outline under PageHeader's h1; Cards carry the anchor id and `scroll-mt-*` so anchor jumps clear the top edge.",
|
|
2134
|
+
"Field wires label/description/error a11y automatically; never hand-roll <label> + <input> pairs. Pair fields side by side with Grid cols={{ base: 1, sm: 2 }}.",
|
|
2135
|
+
"The destructive action is last and isolated in its own Card, and it never acts directly: it opens DestructiveActionModal with type-to-confirm on the handle.",
|
|
924
2136
|
"Lives in the same AppShell as DashboardPage: Sidebar from lg up, AppHeader + BottomNav below it, with Settings marked active in both."
|
|
925
2137
|
],
|
|
926
2138
|
a11y: [
|
|
927
2139
|
'AppShell provides the skip link and <main id="main-content"> landmark; the active SidebarItem and BottomNavItem carry aria-current.',
|
|
928
|
-
|
|
2140
|
+
'The section rail is a labelled <nav> ("Settings sections") distinct from the primary navigation; its active item carries aria-current.',
|
|
2141
|
+
"Every section has an h2, so screen-reader heading navigation mirrors the rail.",
|
|
2142
|
+
"Each Switch takes its row title as aria-label, since the Entity title is not a <label>.",
|
|
929
2143
|
"Field auto-wires htmlFor/id, aria-describedby (description + error), aria-invalid, and required.",
|
|
930
|
-
"
|
|
2144
|
+
"DestructiveActionModal is an alertdialog: focus opens on Cancel, the backdrop does not dismiss, and the confirm stays disabled until the handle is typed."
|
|
931
2145
|
]
|
|
932
2146
|
};
|
|
933
2147
|
|