@sableui/react 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +95 -0
- package/CONTRIBUTING.md +2 -2
- package/DESIGN.md +55 -16
- package/README.md +12 -0
- package/dist/{_contract-C2M1SjBf.d.ts → _contract-CkqXDVww.d.ts} +25 -2
- package/dist/blocks/danger-zone-block.js +3 -3
- package/dist/blocks/danger-zone-block.meta.js +1 -1
- package/dist/blocks/faq-block.js +3 -3
- package/dist/blocks/goal-progress-block.js +1 -1
- package/dist/blocks/notification-list-block.js +1 -1
- package/dist/blocks/pricing-table-block.js +12 -12
- package/dist/blocks/product-card-block.js +3 -3
- package/dist/blocks/product-card-block.meta.js +3 -3
- package/dist/blocks/profile-card-block.js +1 -1
- package/dist/blocks/profile-form-block.js +3 -3
- package/dist/blocks/revenue-breakdown-block.js +5 -5
- package/dist/blocks/stat-row-block.js +3 -3
- package/dist/blocks/task-list-block.js +2 -2
- package/dist/blocks/team-members-block.js +1 -1
- package/dist/blocks/wallet-block.js +4 -4
- package/dist/blocks/wallet-block.meta.js +3 -3
- package/dist/components/accordion/accordion.meta.js +2 -2
- package/dist/components/app-header/app-header.meta.js +1 -1
- package/dist/components/app-shell/app-shell.meta.js +2 -2
- package/dist/components/area-chart/area-chart.meta.js +3 -3
- package/dist/components/badge/badge.js +13 -4
- package/dist/components/badge/badge.meta.js +24 -13
- package/dist/components/badge/badge.variants.js +37 -23
- package/dist/components/banner/banner.meta.js +8 -8
- package/dist/components/banner/banner.variants.js +3 -3
- package/dist/components/bar-chart/bar-chart.meta.js +4 -4
- package/dist/components/breadcrumbs/breadcrumbs.meta.js +1 -1
- package/dist/components/button/button.meta.js +3 -3
- package/dist/components/card/card.js +16 -7
- package/dist/components/card/card.meta.js +4 -4
- package/dist/components/checkbox-card/checkbox-card.meta.js +10 -10
- package/dist/components/checkbox-card/checkbox-card.variants.js +15 -15
- package/dist/components/code-block/code-block.meta.js +1 -1
- package/dist/components/command-menu/command-menu.meta.js +1 -1
- package/dist/components/context-card/context-card.meta.js +2 -2
- package/dist/components/destructive-action-modal/destructive-action-modal.meta.js +1 -1
- package/dist/components/dialog/dialog.meta.js +2 -2
- package/dist/components/disclosure-picker/disclosure-picker.js +48 -2
- package/dist/components/disclosure-picker/disclosure-picker.meta.js +3 -2
- package/dist/components/empty-state/empty-state.meta.js +4 -4
- package/dist/components/gradient-art/gradient-art.meta.js +3 -3
- package/dist/components/label/label.meta.js +1 -1
- package/dist/components/line-chart/line-chart.meta.js +3 -3
- package/dist/components/meter/meter.js +1 -1
- package/dist/components/meter/meter.meta.js +2 -2
- package/dist/components/meter/meter.variants.js +1 -1
- package/dist/components/middle-truncate/middle-truncate.meta.js +4 -4
- package/dist/components/note/note.meta.js +3 -3
- package/dist/components/page-header/page-header.js +1 -1
- package/dist/components/pagination/pagination.meta.js +2 -2
- package/dist/components/phone/phone.meta.js +1 -1
- package/dist/components/pie-chart/pie-chart.meta.js +3 -3
- package/dist/components/pill/pill.js +4 -2
- package/dist/components/pill/pill.meta.js +19 -2
- package/dist/components/pill/pill.variants.js +23 -2
- package/dist/components/popover/popover.meta.js +2 -2
- package/dist/components/radar-chart/radar-chart.meta.js +3 -3
- package/dist/components/radial-chart/radial-chart.meta.js +3 -3
- package/dist/components/radio/radio.meta.js +2 -2
- package/dist/components/radio-card/radio-card.meta.js +6 -6
- package/dist/components/radio-card/radio-card.variants.js +15 -15
- package/dist/components/scroller/scroller.meta.js +1 -1
- package/dist/components/search-input/search-input.meta.js +1 -1
- package/dist/components/section-header/section-header.js +1 -1
- package/dist/components/segmented-bar/segmented-bar.js +1 -1
- package/dist/components/segmented-bar/segmented-bar.meta.js +6 -6
- package/dist/components/segmented-bar/segmented-bar.variants.js +4 -4
- package/dist/components/select/select.meta.js +2 -2
- package/dist/components/sheet/sheet.meta.js +1 -1
- package/dist/components/side-nav/side-nav.meta.js +1 -1
- package/dist/components/sidebar/sidebar.meta.js +1 -1
- package/dist/components/sparkline/sparkline.meta.js +4 -4
- package/dist/components/spinner/spinner.meta.js +1 -1
- package/dist/components/status-dot/status-dot.js +4 -1
- package/dist/components/status-dot/status-dot.meta.js +8 -16
- package/dist/components/status-dot/status-dot.variants.js +13 -4
- package/dist/components/table/table.meta.js +4 -4
- package/dist/components/tabs/tabs.meta.js +1 -1
- package/dist/components/textarea/textarea.meta.js +2 -2
- package/dist/components/toast/toast.meta.js +1 -1
- package/dist/components/tooltip/tooltip.meta.js +2 -2
- package/dist/file-hashes.json +120 -118
- package/dist/foundations/spacing.js +8 -8
- package/dist/index.d.ts +137 -72
- package/dist/index.js +12 -1
- package/dist/lib/chart/types.js +3 -3
- package/dist/lib/palette.js +103 -0
- package/dist/lib/selection-card.js +4 -4
- package/dist/palette.css +669 -0
- package/dist/patterns/auth-page.js +6 -6
- package/dist/patterns/auth-page.meta.js +3 -3
- package/dist/patterns/brand-landing-page.js +2 -2
- package/dist/patterns/chat-panel.js +2 -2
- package/dist/patterns/dashboard-page.js +71 -71
- package/dist/patterns/dashboard-page.meta.js +5 -5
- package/dist/patterns/feed-page.js +25 -25
- package/dist/patterns/feed-page.meta.js +3 -3
- package/dist/patterns/landing-page.js +1 -1
- package/dist/patterns/list-detail-page.js +17 -17
- package/dist/patterns/settings-page.js +17 -17
- package/dist/presets/index.d.ts +1 -1
- package/dist/presets/index.js +17 -2
- package/dist/styles.css +115 -50
- package/dist/{theme-provider-S0NVJs2Y.d.ts → theme-provider-CdSicAad.d.ts} +1 -1
- package/dist/themes/_contract.js +22 -8
- package/dist/themes/atelier/atelier.css +13 -13
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/ember/ember.css +13 -13
- 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 +13 -13
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/poppy/poppy.css +19 -16
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/prism/prism.css +18 -15
- package/dist/themes/sable/index.d.ts +1 -1
- package/dist/themes/sable/sable.css +18 -22
- package/dist/themes/verdant/index.d.ts +1 -1
- package/dist/themes/verdant/verdant.css +15 -15
- package/llms/blocks/danger-zone-block.md +4 -4
- package/llms/blocks/faq-block.md +3 -3
- package/llms/blocks/goal-progress-block.md +1 -1
- package/llms/blocks/notification-list-block.md +1 -1
- package/llms/blocks/pricing-table-block.md +12 -12
- package/llms/blocks/product-card-block.md +6 -6
- package/llms/blocks/profile-card-block.md +1 -1
- package/llms/blocks/profile-form-block.md +3 -3
- package/llms/blocks/revenue-breakdown-block.md +5 -5
- package/llms/blocks/stat-row-block.md +3 -3
- package/llms/blocks/task-list-block.md +2 -2
- package/llms/blocks/team-members-block.md +1 -1
- package/llms/blocks/wallet-block.md +7 -7
- package/llms/components/accordion.md +2 -2
- package/llms/components/app-header.md +1 -1
- package/llms/components/app-shell.md +2 -2
- package/llms/components/area-chart.md +1 -1
- package/llms/components/badge.md +15 -7
- package/llms/components/banner.md +6 -6
- package/llms/components/bar-chart.md +2 -2
- package/llms/components/breadcrumbs.md +1 -1
- package/llms/components/button.md +3 -3
- package/llms/components/card.md +4 -4
- package/llms/components/checkbox-card.md +5 -5
- package/llms/components/code-block.md +1 -1
- package/llms/components/command-menu.md +1 -1
- package/llms/components/context-card.md +2 -2
- package/llms/components/destructive-action-modal.md +1 -1
- package/llms/components/dialog.md +2 -2
- package/llms/components/disclosure-picker.md +2 -1
- package/llms/components/empty-state.md +4 -4
- package/llms/components/gradient-art.md +3 -3
- package/llms/components/label.md +1 -1
- package/llms/components/line-chart.md +1 -1
- package/llms/components/meter.md +2 -2
- package/llms/components/middle-truncate.md +5 -5
- package/llms/components/note.md +3 -3
- package/llms/components/pagination.md +2 -2
- package/llms/components/phone.md +1 -1
- package/llms/components/pie-chart.md +1 -1
- package/llms/components/pill.md +12 -2
- package/llms/components/popover.md +2 -2
- package/llms/components/radar-chart.md +1 -1
- package/llms/components/radial-chart.md +1 -1
- package/llms/components/radio-card.md +4 -4
- package/llms/components/radio.md +2 -2
- package/llms/components/scroller.md +1 -1
- package/llms/components/search-input.md +1 -1
- package/llms/components/segmented-bar.md +7 -7
- package/llms/components/select.md +2 -2
- package/llms/components/sheet.md +1 -1
- package/llms/components/side-nav.md +1 -1
- package/llms/components/sidebar.md +1 -1
- package/llms/components/sparkline.md +2 -2
- package/llms/components/spinner.md +1 -1
- package/llms/components/status-dot.md +4 -4
- package/llms/components/table.md +8 -8
- package/llms/components/tabs.md +1 -1
- package/llms/components/textarea.md +2 -2
- package/llms/components/toast.md +1 -1
- package/llms/components/tooltip.md +2 -2
- package/llms/foundations/spacing.md +7 -7
- package/llms/layout/grid.md +1 -1
- package/llms/layout/page.md +1 -1
- package/llms/layout/region.md +1 -1
- package/llms/layout/split-pane.md +1 -1
- package/llms/patterns/auth-page.md +11 -11
- package/llms/patterns/brand-landing-page.md +2 -2
- package/llms/patterns/chat-panel.md +2 -2
- package/llms/patterns/dashboard-page.md +78 -78
- package/llms/patterns/feed-page.md +31 -31
- package/llms/patterns/landing-page.md +1 -1
- package/llms/patterns/list-detail-page.md +18 -18
- package/llms/patterns/settings-page.md +18 -18
- package/llms/reference/token-intent.md +25 -11
- package/llms/reference/utilities.md +1 -1
- package/llms/tasks/adding-a-component.md +4 -1
- package/llms/tasks/building-a-page.md +40 -0
- package/llms/tasks/converting-an-app.md +3 -1
- package/llms/tasks/using-presets.md +4 -0
- package/llms-full.txt +429 -347
- package/llms.txt +7 -7
- package/package.json +4 -4
- package/registry.json +369 -233
- package/scripts/lint-rules.mjs +26 -5
- package/skills/sable/SKILL.md +14 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AuthPage
|
|
2
2
|
|
|
3
|
-
Split sign-in page: a form column (brand mark, heading, email + password, primary sign-in, passkey and
|
|
3
|
+
Split sign-in page: a form column (brand mark, heading, email + password, primary sign-in, passkey and email-link alternatives, sign-up switch, terms) beside a brand panel painted from the theme's tones with a customer story card. Below lg the panel drops away and the form is the whole screen.
|
|
4
4
|
|
|
5
5
|
**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).
|
|
6
6
|
|
|
@@ -11,14 +11,14 @@ Split sign-in page: a form column (brand mark, heading, email + password, primar
|
|
|
11
11
|
- The heading is `text-heading-1` in an h1 — it is the page outline, so it takes a heading tier, not a card title.
|
|
12
12
|
- 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.
|
|
13
13
|
- Forgot password sits under the password field, right-aligned, before the submit button — so it comes right after the field in tab order. Field has no label-row action slot yet.
|
|
14
|
-
- One primary action. Passkey and
|
|
14
|
+
- One primary action. Passkey and email link are outline buttons under an `or` Separator; switching to sign-up is a quiet `link-underline` link.
|
|
15
15
|
- 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.
|
|
16
16
|
- 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-*) …)` lift needs an `inline-color` disable with a reason.
|
|
17
17
|
- The panel is `hidden lg:flex`: on a phone the form is the whole screen, with no decorative scroll above it.
|
|
18
18
|
|
|
19
19
|
## Accessibility
|
|
20
20
|
|
|
21
|
-
- The route renders inside <main> with one h1; the brand panel is an <aside> labelled "
|
|
21
|
+
- The route renders inside <main> with one h1; the brand panel is an <aside> labelled "Customer story".
|
|
22
22
|
- Field auto-wires labels, describedby, and invalid state onto the inputs; on submit, the first invalid field is focused.
|
|
23
23
|
- Autocomplete attributes (`email webauthn`, `current-password`) keep password managers and passkey autofill working.
|
|
24
24
|
- The Forgot password and Create one links use `hit-target` to meet the 44px touch floor without changing layout.
|
|
@@ -49,7 +49,7 @@ export function AuthPage() {
|
|
|
49
49
|
<Stack gap={1.5}>
|
|
50
50
|
<h1 className="text-heading-1">Welcome back</h1>
|
|
51
51
|
<p className="text-body-lg text-muted-foreground">
|
|
52
|
-
Sign in to
|
|
52
|
+
Sign in to pick up where you and your team left off.
|
|
53
53
|
</p>
|
|
54
54
|
</Stack>
|
|
55
55
|
|
|
@@ -87,8 +87,8 @@ export function AuthPage() {
|
|
|
87
87
|
<Button variant="outline" size="cta" className="w-full" iconStart={<Icon name="key" />}>
|
|
88
88
|
Sign in with a passkey
|
|
89
89
|
</Button>
|
|
90
|
-
<Button variant="outline" size="cta" className="w-full" iconStart={<Icon name="
|
|
91
|
-
|
|
90
|
+
<Button variant="outline" size="cta" className="w-full" iconStart={<Icon name="mail" />}>
|
|
91
|
+
Email me a sign-in link
|
|
92
92
|
</Button>
|
|
93
93
|
</Stack>
|
|
94
94
|
|
|
@@ -115,23 +115,23 @@ export function AuthPage() {
|
|
|
115
115
|
</Region>
|
|
116
116
|
|
|
117
117
|
{/* The brand panel is decoration: on a phone the form is the whole screen. */}
|
|
118
|
-
<aside aria-label="
|
|
118
|
+
<aside aria-label="Customer story" className="hidden p-3 lg:flex">
|
|
119
119
|
{/* Art painted from the theme's tones over its brand, so it follows every look. */}
|
|
120
120
|
<GradientArt seed="Untitled (gold rush)" className="flex flex-1 flex-col justify-end rounded-xl p-8">
|
|
121
121
|
<Card className="max-w-md">
|
|
122
122
|
<CardContent>
|
|
123
123
|
<Stack gap={3}>
|
|
124
|
-
<Badge tone="
|
|
125
|
-
|
|
124
|
+
<Badge tone="1" className="self-start">
|
|
125
|
+
Customer story
|
|
126
126
|
</Badge>
|
|
127
127
|
<p className="text-title-lg">
|
|
128
|
-
“
|
|
128
|
+
“We moved three teams over in a week, and nobody asked for the old tool back.”
|
|
129
129
|
</p>
|
|
130
130
|
<Entity
|
|
131
131
|
className="p-0"
|
|
132
132
|
leading={<Avatar variant="art" fallback="MO" />}
|
|
133
133
|
title="Mara Okafor"
|
|
134
|
-
subtitle="
|
|
134
|
+
subtitle="Head of Design, Northwind"
|
|
135
135
|
/>
|
|
136
136
|
</Stack>
|
|
137
137
|
</CardContent>
|
|
@@ -45,7 +45,7 @@ const spaces = [
|
|
|
45
45
|
|
|
46
46
|
const routes = [
|
|
47
47
|
{ label: "Intake", state: "Open", tone: "info" as const },
|
|
48
|
-
{ label: "Review", state: "Live", tone: "
|
|
48
|
+
{ label: "Review", state: "Live", tone: "1" as const },
|
|
49
49
|
{ label: "Archive", state: "Safe", tone: "success" as const },
|
|
50
50
|
]
|
|
51
51
|
|
|
@@ -223,7 +223,7 @@ export function BrandLandingPage() {
|
|
|
223
223
|
<div className="flex items-center justify-between border-b border-border px-4 py-3">
|
|
224
224
|
<Row gap={1}>
|
|
225
225
|
<StatusDot tone="warning" />
|
|
226
|
-
<StatusDot tone="
|
|
226
|
+
<StatusDot tone="1" />
|
|
227
227
|
<StatusDot tone="success" />
|
|
228
228
|
</Row>
|
|
229
229
|
<span className="text-label-sm text-muted-foreground">Live workspace</span>
|
|
@@ -95,7 +95,7 @@ export function ChatPanel() {
|
|
|
95
95
|
render={<section aria-labelledby="chat-title" />}
|
|
96
96
|
className="flex h-[calc(100dvh_-_--spacing(12))] flex-col overflow-hidden"
|
|
97
97
|
>
|
|
98
|
-
<CardHeader className="
|
|
98
|
+
<CardHeader className="pb-(--card-p) shadow-[inset_0_-1px_0_var(--border)]">
|
|
99
99
|
{/* CardTitle carries the type; the h1 inside it is the page heading. */}
|
|
100
100
|
<CardTitle>
|
|
101
101
|
<h1 id="chat-title">Scroll behaviour</h1>
|
|
@@ -234,7 +234,7 @@ export function ChatPanel() {
|
|
|
234
234
|
</Message>
|
|
235
235
|
</MessageScroller>
|
|
236
236
|
|
|
237
|
-
<CardFooter className="
|
|
237
|
+
<CardFooter className="pt-(--card-p) shadow-[inset_0_1px_0_var(--border)]">
|
|
238
238
|
<ChatComposer
|
|
239
239
|
className="flex-1"
|
|
240
240
|
placeholder="Ask a follow-up…"
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# DashboardPage
|
|
2
2
|
|
|
3
|
-
App-shell
|
|
3
|
+
App-shell admin dashboard: a branded Sidebar with an account footer (AppHeader + BottomNav below lg), a KPI row, an edge-to-edge accounts table with seat-usage Meters, and supporting payout, top-customer, release and activity cards — the default shape for an operator or analytics home route.
|
|
4
4
|
|
|
5
|
-
**Intent:** Reach for this when a route's job is monitoring or managing many records (sales, orders,
|
|
5
|
+
**Intent:** Reach for this when a route's job is monitoring or managing many records (sales, orders, accounts, posts) with a few headline numbers. Not for reading-focused routes (use FeedPage) or single-record routes (use ListDetailPage's detail side).
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
|
|
9
9
|
- Region cap is `wide` (1280px): dashboards hold tables and multi-column grids, never a reading column.
|
|
10
|
-
- Hierarchy runs headline → records → context: KPI row, then the primary table beside its two closest companions (payout, top
|
|
10
|
+
- Hierarchy runs headline → records → context: KPI row, then the primary table beside its two closest companions (payout, top customers), then the lower-priority lists (upcoming, activity). Every row of the grid is filled so the route ends on content, not empty canvas.
|
|
11
11
|
- Stat cards: CardHeader carries the label (CardDescription) and the value; KPI values are `text-mono-xl` — the metric mono tier, tabular figures — 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×2 on a phone.
|
|
12
12
|
- Stat cards use an explicit cols map (2 → 4) — a 4-stat row must fold to 2×2, never strand an orphan the way auto-fit can.
|
|
13
13
|
- 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 — never hand-set pl-6/pr-6. Numeric columns align end in mono; Table scrolls horizontally inside the card on narrow screens.
|
|
14
|
-
-
|
|
14
|
+
- Seats used / seats 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.
|
|
15
15
|
- Lists are Entity rows in CardList, inset to the card padding and divided by hairlines — 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`.
|
|
16
16
|
- Brand and account live in the Sidebar: SidebarHeader holds the mark + wordmark, SidebarFooter holds the signed-in Entity with an OverflowMenu. Sidebar rebinds the page ink tokens to its own, so the Entity and OverflowMenu read correctly even when a theme paints the rail dark on a light page.
|
|
17
17
|
- The page has exactly one heading-1 (PageHeader); card titles are Title tier, not headings.
|
|
@@ -23,7 +23,7 @@ App-shell creator dashboard: a branded Sidebar with an account footer (AppHeader
|
|
|
23
23
|
- AppShell provides the skip link and <main id="main-content"> landmark.
|
|
24
24
|
- The active SidebarItem and BottomNavItem carry aria-current via `active`.
|
|
25
25
|
- DataTable headers are real <th>; sortable columns are announced.
|
|
26
|
-
- Each
|
|
26
|
+
- Each seat-usage Meter has an aria-label naming the account and aria-valuetext reading "184 of 250 seats used".
|
|
27
27
|
- Avatars beside a visible name use an empty alt so the name is not read twice.
|
|
28
28
|
|
|
29
29
|
## Source
|
|
@@ -31,11 +31,11 @@ App-shell creator dashboard: a branded Sidebar with an account footer (AppHeader
|
|
|
31
31
|
```tsx
|
|
32
32
|
import { AppHeader, AppShell, Avatar, Badge, BottomNav, BottomNavItem, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, DataTable, Entity, Grid, Icon, MenuItem, MenuSeparator, Meter, NotificationBadge, OverflowMenu, Page, PageHeader, RelativeTime, Row, Sidebar, SidebarBrand, SidebarFooter, SidebarHeader, SidebarItem, SidebarNav, Stack } from "@sableui/react"
|
|
33
33
|
|
|
34
|
-
//
|
|
34
|
+
// Account status → Badge tone. Sable has no status vocabulary of its own;
|
|
35
35
|
// an app maps ITS states onto tones once, here, and every badge follows.
|
|
36
|
-
const STATUS_TONE = {
|
|
37
|
-
const STATUS_LABEL = {
|
|
38
|
-
type
|
|
36
|
+
const STATUS_TONE = { active: "success", onboarding: "info", full: "1", cancelled: "neutral" } as const
|
|
37
|
+
const STATUS_LABEL = { active: "Active", onboarding: "Onboarding", full: "Seats full", cancelled: "Cancelled" } as const
|
|
38
|
+
type AccountStatus = keyof typeof STATUS_TONE
|
|
39
39
|
|
|
40
40
|
const MIN = 60 * 1000
|
|
41
41
|
const HOUR = 60 * MIN
|
|
@@ -48,54 +48,54 @@ const shortDate = (offset: number) =>
|
|
|
48
48
|
|
|
49
49
|
const stats = [
|
|
50
50
|
{ label: "Revenue", value: "$18,240", delta: "+8.2%", up: true, note: "vs. $16,857" },
|
|
51
|
-
{ label: "
|
|
52
|
-
{ label: "
|
|
53
|
-
{ label: "
|
|
51
|
+
{ label: "Active seats", value: "1,284", delta: "+3.1%", up: true, note: "182 added" },
|
|
52
|
+
{ label: "Accounts", value: "312", delta: "+12.4%", up: true, note: "Best month yet" },
|
|
53
|
+
{ label: "Seat usage", value: "64%", delta: "−1.8%", up: false, note: "6 near their limit" },
|
|
54
54
|
]
|
|
55
55
|
|
|
56
|
-
const
|
|
57
|
-
{ id: "
|
|
58
|
-
{ id: "
|
|
59
|
-
{ id: "
|
|
60
|
-
{ id: "
|
|
61
|
-
{ id: "
|
|
62
|
-
{ id: "
|
|
63
|
-
{ id: "
|
|
64
|
-
{ id: "
|
|
65
|
-
{ id: "
|
|
56
|
+
const accounts = [
|
|
57
|
+
{ id: "ac_01", title: "Northwind", status: "active" as const, used: 184, seats: 250, price: 16, started: -6 * DAY },
|
|
58
|
+
{ id: "ac_02", title: "Harbor Labs", status: "full" as const, used: 100, seats: 100, price: 20, started: -19 * DAY },
|
|
59
|
+
{ id: "ac_03", title: "Brightline", status: "active" as const, used: 61, seats: 150, price: 16, started: -2 * DAY },
|
|
60
|
+
{ id: "ac_04", title: "Tidewater Co.", status: "active" as const, used: 212, seats: 300, price: 12, started: -27 * DAY },
|
|
61
|
+
{ id: "ac_05", title: "Saltworks", status: "cancelled" as const, used: 38, seats: 75, price: 20, started: -44 * DAY },
|
|
62
|
+
{ id: "ac_06", title: "Paper Moon", status: "full" as const, used: 50, seats: 50, price: 24, started: -58 * DAY },
|
|
63
|
+
{ id: "ac_07", title: "Signal Studio", status: "onboarding" as const, used: 0, seats: 200, price: 16, started: 3 * DAY },
|
|
64
|
+
{ id: "ac_08", title: "Bluewater", status: "cancelled" as const, used: 72, seats: 120, price: 12, started: -71 * DAY },
|
|
65
|
+
{ id: "ac_09", title: "Ember & Field", status: "full" as const, used: 80, seats: 80, price: 20, started: -86 * DAY },
|
|
66
66
|
]
|
|
67
|
-
const
|
|
67
|
+
const accountCount = 312
|
|
68
68
|
|
|
69
69
|
const payout = {
|
|
70
|
-
amount:
|
|
70
|
+
amount: 8862.64,
|
|
71
71
|
date: 5 * DAY,
|
|
72
72
|
lines: [
|
|
73
|
-
{ label: "
|
|
74
|
-
{ label: "
|
|
73
|
+
{ label: "Subscriptions", value: 9127.33 },
|
|
74
|
+
{ label: "Processing (2.9%)", value: -264.69 },
|
|
75
75
|
{ label: "Deposits to", value: "Checking ••4821" },
|
|
76
76
|
],
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
const
|
|
80
|
-
{ id: "
|
|
81
|
-
{ id: "
|
|
82
|
-
{ id: "
|
|
83
|
-
{ id: "
|
|
79
|
+
const customers = [
|
|
80
|
+
{ id: "cu_01", name: "Northwind", handle: "northwind.io", initials: "NW", seats: 184, spent: 2944 },
|
|
81
|
+
{ id: "cu_02", name: "Tidewater Co.", handle: "tidewater.co", initials: "TC", seats: 212, spent: 2544 },
|
|
82
|
+
{ id: "cu_03", name: "Harbor Labs", handle: "harborlabs.com", initials: "HL", seats: 100, spent: 2000 },
|
|
83
|
+
{ id: "cu_04", name: "Ember & Field", handle: "emberfield.com", initials: "EF", seats: 80, spent: 1600 },
|
|
84
84
|
]
|
|
85
85
|
|
|
86
86
|
const upcoming = [
|
|
87
|
-
{ id: "up_01", title: "
|
|
88
|
-
{ id: "up_02", title: "
|
|
89
|
-
{ id: "up_03", title: "
|
|
90
|
-
{ id: "up_04", title: "
|
|
87
|
+
{ id: "up_01", title: "Version 2.4", detail: "Shared reports · All plans", at: 3 * DAY },
|
|
88
|
+
{ id: "up_02", title: "Audit log export", detail: "Business plan · Beta", at: 12 * DAY },
|
|
89
|
+
{ id: "up_03", title: "Version 2.5", detail: "Offline mode · All plans", at: 26 * DAY },
|
|
90
|
+
{ id: "up_04", title: "Usage-based billing", detail: "Studio and Business · Beta", at: 34 * DAY },
|
|
91
91
|
]
|
|
92
92
|
|
|
93
93
|
const activity = [
|
|
94
|
-
{ id: "
|
|
95
|
-
{ id: "
|
|
96
|
-
{ id: "
|
|
97
|
-
{ id: "
|
|
98
|
-
{ id: "
|
|
94
|
+
{ id: "ev_01", who: "Nina Simone", initials: "NS", what: "added 12 seats to Northwind · 184 of 250", when: 2 * MIN },
|
|
95
|
+
{ id: "ev_02", who: "Grace Hopper", initials: "GH", what: "upgraded Brightline to Studio", when: 26 * MIN },
|
|
96
|
+
{ id: "ev_03", who: "Frida Kahlo", initials: "FK", what: "paid invoice INV-0042 for Tidewater Co.", when: 1 * HOUR },
|
|
97
|
+
{ id: "ev_04", who: "Mary Shelley", initials: "MS", what: "joined the Harbor Labs workspace", when: 3 * HOUR },
|
|
98
|
+
{ id: "ev_05", who: "Nina Simone", initials: "NS", what: "booked onboarding for Signal Studio", when: 6 * HOUR },
|
|
99
99
|
]
|
|
100
100
|
|
|
101
101
|
export function DashboardPage() {
|
|
@@ -117,8 +117,8 @@ export function DashboardPage() {
|
|
|
117
117
|
bottomNav={
|
|
118
118
|
<BottomNav aria-label="Primary">
|
|
119
119
|
<BottomNavItem icon={<Icon name="grid" />} label="Overview" showLabel active render={<a href="/" />} />
|
|
120
|
-
<BottomNavItem icon={<Icon name="
|
|
121
|
-
<BottomNavItem icon={<Icon name="user" />} label="
|
|
120
|
+
<BottomNavItem icon={<Icon name="folder" />} label="Accounts" showLabel render={<a href="/accounts" />} />
|
|
121
|
+
<BottomNavItem icon={<Icon name="user" />} label="Customers" showLabel render={<a href="/customers" />} />
|
|
122
122
|
<BottomNavItem icon={<Icon name="bell" />} label="Activity" showLabel render={<a href="/activity" />} />
|
|
123
123
|
<BottomNavItem icon={<Icon name="gear" />} label="Settings" showLabel render={<a href="/settings" />} />
|
|
124
124
|
</BottomNav>
|
|
@@ -130,8 +130,8 @@ export function DashboardPage() {
|
|
|
130
130
|
</SidebarHeader>
|
|
131
131
|
<SidebarNav>
|
|
132
132
|
<SidebarItem icon={<Icon name="grid" />} label="Overview" active render={<a href="/" />} />
|
|
133
|
-
<SidebarItem icon={<Icon name="
|
|
134
|
-
<SidebarItem icon={<Icon name="user" />} label="
|
|
133
|
+
<SidebarItem icon={<Icon name="folder" />} label="Accounts" render={<a href="/accounts" />} />
|
|
134
|
+
<SidebarItem icon={<Icon name="user" />} label="Customers" render={<a href="/customers" />} />
|
|
135
135
|
<SidebarItem
|
|
136
136
|
icon={<Icon name="bell" />}
|
|
137
137
|
label="Activity"
|
|
@@ -162,7 +162,7 @@ export function DashboardPage() {
|
|
|
162
162
|
<Page max="wide" gap={4} className="py-6">
|
|
163
163
|
<PageHeader
|
|
164
164
|
title="Overview"
|
|
165
|
-
description="
|
|
165
|
+
description="Revenue and account activity for the last 30 days."
|
|
166
166
|
actions={
|
|
167
167
|
<Row gap={2}>
|
|
168
168
|
<Button variant="outline">
|
|
@@ -171,7 +171,7 @@ export function DashboardPage() {
|
|
|
171
171
|
</Button>
|
|
172
172
|
<Button>
|
|
173
173
|
<Icon name="plus" />
|
|
174
|
-
New
|
|
174
|
+
New account
|
|
175
175
|
</Button>
|
|
176
176
|
</Row>
|
|
177
177
|
}
|
|
@@ -201,43 +201,43 @@ export function DashboardPage() {
|
|
|
201
201
|
<Grid cols={{ base: 1, xl: 3 }} gap={3}>
|
|
202
202
|
<Card className="flex flex-col xl:col-span-2">
|
|
203
203
|
<CardHeader>
|
|
204
|
-
<CardTitle>
|
|
205
|
-
<CardDescription>
|
|
204
|
+
<CardTitle>Accounts</CardTitle>
|
|
205
|
+
<CardDescription>Seats across your customers, newest first.</CardDescription>
|
|
206
206
|
</CardHeader>
|
|
207
207
|
<CardContent flush className="mt-4 flex-1">
|
|
208
208
|
<DataTable
|
|
209
|
-
data={
|
|
209
|
+
data={accounts}
|
|
210
210
|
rowKey={(row) => row.id}
|
|
211
|
-
defaultSort={{ key: "
|
|
211
|
+
defaultSort={{ key: "started", direction: "desc" }}
|
|
212
212
|
columns={[
|
|
213
|
-
{ key: "title", header: "
|
|
213
|
+
{ key: "title", header: "Account", sortable: true, className: "text-label-md whitespace-nowrap" },
|
|
214
214
|
{
|
|
215
215
|
key: "status",
|
|
216
216
|
header: "Status",
|
|
217
217
|
cell: (row) => (
|
|
218
|
-
<Badge appearance="plain" tone={STATUS_TONE[row.status as
|
|
219
|
-
{STATUS_LABEL[row.status as
|
|
218
|
+
<Badge appearance="plain" tone={STATUS_TONE[row.status as AccountStatus]} dot>
|
|
219
|
+
{STATUS_LABEL[row.status as AccountStatus]}
|
|
220
220
|
</Badge>
|
|
221
221
|
),
|
|
222
222
|
},
|
|
223
223
|
{
|
|
224
|
-
key: "
|
|
225
|
-
header: "
|
|
224
|
+
key: "used",
|
|
225
|
+
header: "Seats",
|
|
226
226
|
sortable: true,
|
|
227
|
-
sortValue: (row) => row.
|
|
227
|
+
sortValue: (row) => row.used / row.seats,
|
|
228
228
|
cell: (row) => (
|
|
229
229
|
<Row gap={3} align="center" className="min-w-40">
|
|
230
230
|
<Meter
|
|
231
|
-
value={row.
|
|
232
|
-
max={row.
|
|
231
|
+
value={row.used}
|
|
232
|
+
max={row.seats}
|
|
233
233
|
size="sm"
|
|
234
|
-
tone={row.
|
|
235
|
-
aria-label={`${row.title}
|
|
236
|
-
getAriaValueText={(_, v) => `${v} of ${row.
|
|
234
|
+
tone={row.used === row.seats ? "success" : "primary"}
|
|
235
|
+
aria-label={`${row.title} seats used`}
|
|
236
|
+
getAriaValueText={(_, v) => `${v} of ${row.seats} seats used`}
|
|
237
237
|
className="flex-1"
|
|
238
238
|
/>
|
|
239
239
|
<span className="text-mono-caption whitespace-nowrap text-muted-foreground">
|
|
240
|
-
{row.
|
|
240
|
+
{row.used}/{row.seats}
|
|
241
241
|
</span>
|
|
242
242
|
</Row>
|
|
243
243
|
),
|
|
@@ -247,21 +247,21 @@ export function DashboardPage() {
|
|
|
247
247
|
header: "Revenue",
|
|
248
248
|
align: "end",
|
|
249
249
|
sortable: true,
|
|
250
|
-
sortValue: (row) => row.
|
|
250
|
+
sortValue: (row) => row.used * row.price,
|
|
251
251
|
cell: (row) =>
|
|
252
|
-
row.
|
|
253
|
-
<span className="text-mono-sm">{usd.format(row.
|
|
252
|
+
row.used > 0 ? (
|
|
253
|
+
<span className="text-mono-sm">{usd.format(row.used * row.price)}</span>
|
|
254
254
|
) : (
|
|
255
255
|
<span className="text-mono-sm text-muted-foreground">—</span>
|
|
256
256
|
),
|
|
257
257
|
},
|
|
258
258
|
{
|
|
259
|
-
key: "
|
|
260
|
-
header: "
|
|
259
|
+
key: "started",
|
|
260
|
+
header: "Started",
|
|
261
261
|
align: "end",
|
|
262
262
|
sortable: true,
|
|
263
263
|
cell: (row) => (
|
|
264
|
-
<span className="text-body-sm whitespace-nowrap text-muted-foreground">{shortDate(row.
|
|
264
|
+
<span className="text-body-sm whitespace-nowrap text-muted-foreground">{shortDate(row.started)}</span>
|
|
265
265
|
),
|
|
266
266
|
},
|
|
267
267
|
]}
|
|
@@ -269,10 +269,10 @@ export function DashboardPage() {
|
|
|
269
269
|
</CardContent>
|
|
270
270
|
<CardFooter className="justify-between">
|
|
271
271
|
<span className="text-caption text-muted-foreground">
|
|
272
|
-
Showing {
|
|
272
|
+
Showing {accounts.length} of {accountCount} accounts
|
|
273
273
|
</span>
|
|
274
|
-
<Button variant="ghost" size="sm" bleed="end" render={<a href="/
|
|
275
|
-
View all
|
|
274
|
+
<Button variant="ghost" size="sm" bleed="end" render={<a href="/accounts" />}>
|
|
275
|
+
View all accounts
|
|
276
276
|
<Icon name="arrow-right" />
|
|
277
277
|
</Button>
|
|
278
278
|
</CardFooter>
|
|
@@ -303,21 +303,21 @@ export function DashboardPage() {
|
|
|
303
303
|
</Card>
|
|
304
304
|
<Card>
|
|
305
305
|
<CardHeader>
|
|
306
|
-
<CardTitle>Top
|
|
306
|
+
<CardTitle>Top customers</CardTitle>
|
|
307
307
|
<CardDescription>By spend this month.</CardDescription>
|
|
308
308
|
<CardAction>
|
|
309
|
-
<Button variant="ghost" size="sm" render={<a href="/
|
|
309
|
+
<Button variant="ghost" size="sm" render={<a href="/customers" />}>
|
|
310
310
|
View all
|
|
311
311
|
</Button>
|
|
312
312
|
</CardAction>
|
|
313
313
|
</CardHeader>
|
|
314
314
|
<CardList>
|
|
315
|
-
{
|
|
315
|
+
{customers.map((person) => (
|
|
316
316
|
<Entity
|
|
317
317
|
key={person.id}
|
|
318
318
|
leading={<Avatar variant="art" size="sm" fallback={person.initials} alt="" />}
|
|
319
319
|
title={person.name}
|
|
320
|
-
subtitle={`${person.handle} · ${person.
|
|
320
|
+
subtitle={`${person.handle} · ${person.seats} seats`}
|
|
321
321
|
action={<span className="text-mono-sm">{usd.format(person.spent)}</span>}
|
|
322
322
|
/>
|
|
323
323
|
))}
|
|
@@ -329,7 +329,7 @@ export function DashboardPage() {
|
|
|
329
329
|
<Card className="flex flex-col">
|
|
330
330
|
<CardHeader>
|
|
331
331
|
<CardTitle>Upcoming releases</CardTitle>
|
|
332
|
-
<CardDescription>Scheduled to
|
|
332
|
+
<CardDescription>Scheduled to ship to customers.</CardDescription>
|
|
333
333
|
</CardHeader>
|
|
334
334
|
<CardList>
|
|
335
335
|
{upcoming.map((release) => (
|
|
@@ -342,7 +342,7 @@ export function DashboardPage() {
|
|
|
342
342
|
))}
|
|
343
343
|
</CardList>
|
|
344
344
|
<CardFooter className="mt-auto">
|
|
345
|
-
<Button variant="outline" size="sm" render={<a href="/
|
|
345
|
+
<Button variant="outline" size="sm" render={<a href="/releases/new" />}>
|
|
346
346
|
<Icon name="calendar" />
|
|
347
347
|
Schedule a release
|
|
348
348
|
</Button>
|
|
@@ -351,7 +351,7 @@ export function DashboardPage() {
|
|
|
351
351
|
<Card className="xl:col-span-2">
|
|
352
352
|
<CardHeader>
|
|
353
353
|
<CardTitle>Activity</CardTitle>
|
|
354
|
-
<CardDescription>
|
|
354
|
+
<CardDescription>Seats, upgrades, and invoices.</CardDescription>
|
|
355
355
|
<CardAction>
|
|
356
356
|
<Button variant="ghost" size="sm" render={<a href="/activity" />}>
|
|
357
357
|
View all
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# FeedPage
|
|
2
2
|
|
|
3
|
-
Social reading feed with a side rail: a feed-width main column (composer, For you / Following Tabs, one Card per post, an
|
|
3
|
+
Social reading feed with a side rail: a feed-width main column (composer, For you / Following Tabs, one Card per post, an event post with price, capacity Meter and Register, a Skeleton for the next page) beside a sticky rail of who-to-follow and upcoming events that drops away below lg.
|
|
4
4
|
|
|
5
5
|
**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).
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ Social reading feed with a side rail: a feed-width main column (composer, For yo
|
|
|
10
10
|
- 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.
|
|
11
11
|
- The composer is a Card holding `Textarea autoResize` plus ghost icon Buttons and a secondary `Post` — not ChatComposer, which submits on Enter and is for conversations.
|
|
12
12
|
- Each post is a Card: an Entity byline in a CardList (with an OverflowMenu 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.
|
|
13
|
-
- An
|
|
13
|
+
- An event post adds a `Badge appearance="overlay"` on the image, title and price, the one primary Button on the page (`Register`), and a `Meter` for capacity — seats taken against a known maximum is a level, not task progress.
|
|
14
14
|
- 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.
|
|
15
15
|
- 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 — predictable for keyboard and AT users, unlike infinite scroll.
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ Social reading feed with a side rail: a feed-width main column (composer, For yo
|
|
|
19
19
|
- Each post is an <article>, so AT users can jump between posts; the rail is a labelled <aside> landmark.
|
|
20
20
|
- Rail card titles render as h2 (CardTitle `render={<h2 />}`) under the page's one h1.
|
|
21
21
|
- Icon-only buttons and every Follow / Remind button carry aria-labels naming their target; counts stay in the accessible name.
|
|
22
|
-
- The
|
|
22
|
+
- The capacity Meter is role=meter with a getAriaValueText reading ("28 of 40 seats taken").
|
|
23
23
|
- The Skeleton card is aria-busy; the loading Button keeps its label ("Loading posts").
|
|
24
24
|
|
|
25
25
|
## Source
|
|
@@ -36,7 +36,7 @@ type Post = {
|
|
|
36
36
|
following: boolean
|
|
37
37
|
body: string
|
|
38
38
|
media?: { src: string; alt: string }
|
|
39
|
-
|
|
39
|
+
event?: { title: string; price: string; registered: number; capacity: number }
|
|
40
40
|
likes: number
|
|
41
41
|
comments: number
|
|
42
42
|
}
|
|
@@ -49,9 +49,9 @@ const posts: Post[] = [
|
|
|
49
49
|
initials: "AL",
|
|
50
50
|
time: "2h",
|
|
51
51
|
following: true,
|
|
52
|
-
body: "
|
|
53
|
-
media: { src: "/media/edition-cover.svg", alt: "
|
|
54
|
-
|
|
52
|
+
body: "Registration for the spring workshop is open. Two days of plotter drawing in a small group. See you there.",
|
|
53
|
+
media: { src: "/media/edition-cover.svg", alt: "Workshop poster, a gold-leaf study in overlapping arcs" },
|
|
54
|
+
event: { title: "Spring plotter workshop", price: "$120", registered: 28, capacity: 40 },
|
|
55
55
|
likes: 128,
|
|
56
56
|
comments: 24,
|
|
57
57
|
},
|
|
@@ -74,7 +74,7 @@ const posts: Post[] = [
|
|
|
74
74
|
initials: "NS",
|
|
75
75
|
time: "8h",
|
|
76
76
|
following: false,
|
|
77
|
-
body: "
|
|
77
|
+
body: "Thanks to everyone at the Dusk listening session — the full recording is now in your library. Headphones on.",
|
|
78
78
|
likes: 214,
|
|
79
79
|
comments: 31,
|
|
80
80
|
},
|
|
@@ -86,8 +86,8 @@ const suggestions = [
|
|
|
86
86
|
]
|
|
87
87
|
|
|
88
88
|
const upcoming = [
|
|
89
|
-
{ id: "
|
|
90
|
-
{ id: "
|
|
89
|
+
{ id: "e_01", title: "Quiet Harbor talk", host: "Mary Shelley", initials: "MS", date: "Sat, Oct 12" },
|
|
90
|
+
{ id: "e_02", title: "Dusk, live", host: "Nina Simone", initials: "NS", date: "Tue, Oct 15" },
|
|
91
91
|
]
|
|
92
92
|
|
|
93
93
|
const renderPost = (post: Post) => (
|
|
@@ -113,35 +113,35 @@ const renderPost = (post: Post) => (
|
|
|
113
113
|
{post.media ? (
|
|
114
114
|
<AspectBox ratio={16 / 9} className="overflow-hidden rounded-concentric-lg/6 bg-muted">
|
|
115
115
|
<img src={post.media.src} alt={post.media.alt} className="size-full object-cover" />
|
|
116
|
-
{post.
|
|
116
|
+
{post.event ? (
|
|
117
117
|
<div className="absolute start-3 top-3">
|
|
118
118
|
<Badge appearance="overlay" size="sm" dot>
|
|
119
|
-
|
|
119
|
+
Open now
|
|
120
120
|
</Badge>
|
|
121
121
|
</div>
|
|
122
122
|
) : null}
|
|
123
123
|
</AspectBox>
|
|
124
124
|
) : null}
|
|
125
|
-
{post.
|
|
125
|
+
{post.event ? (
|
|
126
126
|
<Stack gap={1.5}>
|
|
127
127
|
<Row gap={2} align="center" justify="between">
|
|
128
128
|
<div className="min-w-0">
|
|
129
|
-
<p className="text-title-md text-foreground">{post.
|
|
129
|
+
<p className="text-title-md text-foreground">{post.event.title}</p>
|
|
130
130
|
<p className="text-body-sm text-muted-foreground">
|
|
131
|
-
{`${post.
|
|
131
|
+
{`${post.event.price} · ${post.event.capacity} seats`}
|
|
132
132
|
</p>
|
|
133
133
|
</div>
|
|
134
|
-
<Button>{`
|
|
134
|
+
<Button>{`Register for ${post.event.price}`}</Button>
|
|
135
135
|
</Row>
|
|
136
136
|
<Meter
|
|
137
|
-
value={post.
|
|
138
|
-
max={post.
|
|
137
|
+
value={post.event.registered}
|
|
138
|
+
max={post.event.capacity}
|
|
139
139
|
size="sm"
|
|
140
|
-
getAriaValueText={(_, value) => `${value} of ${post.
|
|
140
|
+
getAriaValueText={(_, value) => `${value} of ${post.event?.capacity} seats taken`}
|
|
141
141
|
>
|
|
142
142
|
<Row gap={2} align="center" justify="between" className="mb-2">
|
|
143
|
-
<MeterLabel>{`${post.
|
|
144
|
-
<MeterValue>{(_, value) => `${(post.
|
|
143
|
+
<MeterLabel>{`${post.event.registered} registered`}</MeterLabel>
|
|
144
|
+
<MeterValue>{(_, value) => `${(post.event?.capacity ?? 0) - value} left`}</MeterValue>
|
|
145
145
|
</Row>
|
|
146
146
|
<MeterTrack />
|
|
147
147
|
</Meter>
|
|
@@ -182,15 +182,15 @@ export function FeedPage() {
|
|
|
182
182
|
autoResize
|
|
183
183
|
rows={2}
|
|
184
184
|
aria-label="New post"
|
|
185
|
-
placeholder="Share a process shot or an upcoming
|
|
185
|
+
placeholder="Share a process shot or an upcoming event"
|
|
186
186
|
/>
|
|
187
187
|
<Row gap={1} align="center" justify="between">
|
|
188
188
|
<Row gap={0.5}>
|
|
189
189
|
<Button variant="ghost" size="icon-sm" aria-label="Add images">
|
|
190
190
|
<Icon name="image-stack" />
|
|
191
191
|
</Button>
|
|
192
|
-
<Button variant="ghost" size="icon-sm" aria-label="
|
|
193
|
-
<Icon name="
|
|
192
|
+
<Button variant="ghost" size="icon-sm" aria-label="Add an event">
|
|
193
|
+
<Icon name="calendar" />
|
|
194
194
|
</Button>
|
|
195
195
|
</Row>
|
|
196
196
|
<Button variant="secondary" size="sm">
|
|
@@ -257,17 +257,17 @@ export function FeedPage() {
|
|
|
257
257
|
</Card>
|
|
258
258
|
<Card>
|
|
259
259
|
<CardHeader>
|
|
260
|
-
<CardTitle render={<h2 />}>Upcoming
|
|
260
|
+
<CardTitle render={<h2 />}>Upcoming events</CardTitle>
|
|
261
261
|
</CardHeader>
|
|
262
262
|
<CardList>
|
|
263
|
-
{upcoming.map((
|
|
263
|
+
{upcoming.map((event) => (
|
|
264
264
|
<Entity
|
|
265
|
-
key={
|
|
266
|
-
leading={<Avatar variant="art" fallback={
|
|
267
|
-
title={
|
|
268
|
-
subtitle={`${
|
|
265
|
+
key={event.id}
|
|
266
|
+
leading={<Avatar variant="art" fallback={event.initials} alt={event.host} />}
|
|
267
|
+
title={event.title}
|
|
268
|
+
subtitle={`${event.host} · ${event.date}`}
|
|
269
269
|
action={
|
|
270
|
-
<Button variant="ghost" size="icon-sm" aria-label={`Remind me about ${
|
|
270
|
+
<Button variant="ghost" size="icon-sm" aria-label={`Remind me about ${event.title}`}>
|
|
271
271
|
<Icon name="bell" />
|
|
272
272
|
</Button>
|
|
273
273
|
}
|
|
@@ -285,7 +285,7 @@ export function LandingPage() {
|
|
|
285
285
|
<CardDescription>Team plan</CardDescription>
|
|
286
286
|
</CardHeader>
|
|
287
287
|
<CardContent>
|
|
288
|
-
<Meter value={64} tone="
|
|
288
|
+
<Meter value={64} tone="3">
|
|
289
289
|
<Row justify="between" wrap className="mb-2">
|
|
290
290
|
<MeterLabel>Used</MeterLabel>
|
|
291
291
|
<MeterValue>{(_, value) => `${(value * 30).toLocaleString("en-US")} of 3,000`}</MeterValue>
|