@sableui/react 0.1.1 → 0.3.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 +3 -1
- package/CHANGELOG.md +120 -0
- package/CONTRIBUTING.md +8 -0
- package/README.md +1 -0
- package/bin/sable-lint.mjs +0 -0
- package/bin/sable.mjs +68 -26
- package/dist/{_contract-CYScOdf5.d.ts → _contract-o3tUc8Ig.d.ts} +1 -1
- package/dist/blocks/index.d.ts +396 -0
- package/dist/blocks/index.js +1997 -0
- package/dist/chunk-6LCIBMMG.js +4908 -0
- package/dist/{chunk-J23ELQQZ.js → chunk-CKNM7KSB.js} +1664 -4270
- package/dist/chunk-K6LII3OE.js +5093 -0
- package/dist/{chunk-RWP4XBSK.js → chunk-KFZ43RKU.js} +1 -1
- package/dist/{chunk-PAOZBZME.js → chunk-NGBDP3BM.js} +1 -1
- package/dist/{chunk-5Z5KLOAU.js → chunk-TH563WLW.js} +14 -1
- package/dist/{chunk-JI7WJP2L.js → chunk-X276MF2W.js} +5 -0
- package/dist/foundations/index.js +1 -1
- package/dist/icon/index.js +2 -2
- package/dist/icon-sets/reicon.js +1 -1
- package/dist/index.d.ts +624 -133
- package/dist/index.js +767 -4964
- package/dist/ios/index.d.ts +14 -2
- package/dist/ios/index.js +10 -9
- package/dist/ios/preview.d.ts +11 -1
- package/dist/ios/preview.js +14 -9
- package/dist/ios/styles.css +26 -6
- package/dist/{meta-6dUzgC6M.d.ts → meta-CcE_SeFu.d.ts} +37 -1
- package/dist/patterns/index.d.ts +1 -1
- package/dist/patterns/index.js +1684 -471
- 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 +34 -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/index.js +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/blocks/api-keys-block.md +79 -0
- package/llms/blocks/chat-thread-block.md +51 -0
- package/llms/blocks/command-palette-block.md +48 -0
- package/llms/blocks/cta-band-block.md +62 -0
- package/llms/blocks/danger-zone-block.md +69 -0
- package/llms/blocks/device-comparison-block.md +91 -0
- package/llms/blocks/event-card-block.md +84 -0
- package/llms/blocks/faq-block.md +72 -0
- package/llms/blocks/feature-grid-block.md +75 -0
- package/llms/blocks/feedback-form-block.md +66 -0
- package/llms/blocks/goal-progress-block.md +54 -0
- package/llms/blocks/hero-block.md +65 -0
- package/llms/blocks/invite-team-block.md +61 -0
- package/llms/blocks/invoice-history-block.md +84 -0
- package/llms/blocks/kpi-sparkline-block.md +57 -0
- package/llms/blocks/media-player-block.md +100 -0
- package/llms/blocks/newsletter-block.md +64 -0
- package/llms/blocks/not-found-block.md +56 -0
- package/llms/blocks/notification-list-block.md +63 -0
- package/llms/blocks/notification-settings-block.md +54 -0
- package/llms/blocks/order-tracking-block.md +62 -0
- package/llms/blocks/pricing-card-block.md +82 -0
- package/llms/blocks/pricing-table-block.md +129 -0
- package/llms/blocks/product-card-block.md +69 -0
- package/llms/blocks/profile-card-block.md +103 -0
- package/llms/blocks/profile-form-block.md +114 -0
- package/llms/blocks/revenue-breakdown-block.md +70 -0
- package/llms/blocks/sign-in-block.md +64 -0
- package/llms/blocks/sign-up-block.md +78 -0
- package/llms/blocks/site-footer-block.md +96 -0
- package/llms/blocks/stat-row-block.md +59 -0
- package/llms/blocks/steps-block.md +73 -0
- package/llms/blocks/task-list-block.md +81 -0
- package/llms/blocks/team-members-block.md +91 -0
- package/llms/blocks/testimonial-block.md +78 -0
- package/llms/blocks/traffic-sources-block.md +63 -0
- package/llms/blocks/traffic-trend-block.md +95 -0
- package/llms/blocks/upload-list-block.md +66 -0
- package/llms/blocks/verify-code-block.md +59 -0
- package/llms/blocks/wallet-block.md +84 -0
- package/llms/components/area-chart.md +112 -0
- package/llms/components/bar-chart.md +131 -0
- package/llms/components/bubble.md +5 -5
- package/llms/components/button.md +1 -1
- package/llms/components/card.md +2 -2
- package/llms/components/checkbox-card.md +1 -1
- package/llms/components/context-card.md +2 -2
- package/llms/components/context-menu.md +1 -1
- package/llms/components/date-tile.md +79 -0
- package/llms/components/dialog.md +1 -1
- package/llms/components/empty-state.md +4 -4
- package/llms/components/entity.md +5 -3
- package/llms/components/fieldset.md +1 -1
- package/llms/components/kbd.md +18 -6
- package/llms/components/line-chart.md +101 -0
- package/llms/components/menu.md +1 -1
- package/llms/components/{dots-menu.md → overflow-menu.md} +9 -9
- package/llms/components/pie-chart.md +103 -0
- package/llms/components/pill.md +1 -1
- package/llms/components/radar-chart.md +106 -0
- package/llms/components/radial-chart.md +102 -0
- package/llms/components/segmented-bar.md +102 -0
- package/llms/components/settings-menu.md +2 -2
- package/llms/components/side-nav.md +1 -1
- package/llms/components/skeleton.md +1 -1
- package/llms/components/sparkline.md +80 -0
- package/llms/components/table.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 +276 -65
- package/llms/patterns/feed-page.md +245 -64
- package/llms/patterns/landing-page.md +473 -75
- package/llms/patterns/list-detail-page.md +377 -68
- package/llms/patterns/settings-page.md +268 -67
- package/llms/reference/token-intent.md +4 -2
- package/llms/reference/utilities.md +20 -5
- package/llms/tasks/building-an-ios-prototype.md +4 -1
- package/llms/tasks/choosing-a-component.md +2 -2
- package/llms-full.txt +6428 -892
- package/llms.txt +67 -16
- package/package.json +5 -1
- package/registry.json +3217 -552
- package/targets/ios.json +88 -18
- package/llms/components/description.md +0 -96
- package/dist/{chunk-L6Y46C62.js → chunk-MXJX3NZI.js} +1 -1
|
@@ -1,102 +1,283 @@
|
|
|
1
1
|
# FeedPage
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
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.
|
|
4
4
|
|
|
5
|
-
**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).
|
|
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
|
|
|
7
7
|
## Notes
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
- The
|
|
12
|
-
-
|
|
13
|
-
-
|
|
9
|
+
- 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 — its content is supplementary, so nothing is lost on mobile.
|
|
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
|
+
- 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
|
+
- 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 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 — collected against a known maximum is a level, not task progress.
|
|
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
|
+
- 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.
|
|
14
16
|
|
|
15
17
|
## Accessibility
|
|
16
18
|
|
|
17
|
-
- Each post is an <article>, so AT users can jump between posts.
|
|
18
|
-
-
|
|
19
|
-
-
|
|
19
|
+
- Each post is an <article>, so AT users can jump between posts; the rail is a labelled <aside> landmark.
|
|
20
|
+
- Rail card titles render as h2 (CardTitle `render={<h2 />}`) under the page's one h1.
|
|
21
|
+
- Icon-only buttons and every Follow / Remind button carry aria-labels naming their target; counts stay in the accessible name.
|
|
22
|
+
- The supply Meter is role=meter with a getAriaValueText reading ("62 of 100 collected").
|
|
23
|
+
- The Skeleton card is aria-busy; the loading Button keeps its label ("Loading posts").
|
|
20
24
|
|
|
21
25
|
## Source
|
|
22
26
|
|
|
23
27
|
```tsx
|
|
24
|
-
import { AspectBox, Avatar, Button, Card, CardContent, CardList, Entity, Icon, Page, PageHeader, Row, Stack } from "@sableui/react"
|
|
28
|
+
import { AspectBox, Avatar, Badge, Button, Card, CardContent, CardHeader, CardList, CardTitle, Entity, Icon, MenuItem, MenuSeparator, Meter, MeterLabel, MeterTrack, MeterValue, OverflowMenu, Page, PageHeader, Row, Skeleton, Stack, Tabs, TabsList, TabsPanel, TabsTrigger, Textarea } from "@sableui/react"
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
type Post = {
|
|
31
|
+
id: string
|
|
32
|
+
author: string
|
|
33
|
+
handle: string
|
|
34
|
+
initials: string
|
|
35
|
+
time: string
|
|
36
|
+
following: boolean
|
|
37
|
+
body: string
|
|
38
|
+
media?: { src: string; alt: string }
|
|
39
|
+
drop?: { title: string; price: string; collected: number; supply: number }
|
|
40
|
+
likes: number
|
|
41
|
+
comments: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const posts: Post[] = [
|
|
27
45
|
{
|
|
28
46
|
id: "p_01",
|
|
29
47
|
author: "Ada Lovelace",
|
|
30
|
-
handle: "@ada
|
|
48
|
+
handle: "@ada",
|
|
31
49
|
initials: "AL",
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
50
|
+
time: "2h",
|
|
51
|
+
following: true,
|
|
52
|
+
body: "Gold Rush is live. 100 signed prints, each paired with the on-chain original. Thank you for waiting.",
|
|
53
|
+
media: { src: "/media/edition-cover.svg", alt: "Gold Rush, a gold-leaf study in overlapping arcs" },
|
|
54
|
+
drop: { title: "Gold Rush", price: "$120", collected: 62, supply: 100 },
|
|
35
55
|
likes: 128,
|
|
36
56
|
comments: 24,
|
|
37
57
|
},
|
|
38
58
|
{
|
|
39
59
|
id: "p_02",
|
|
40
60
|
author: "Grace Hopper",
|
|
41
|
-
handle: "@grace
|
|
61
|
+
handle: "@grace",
|
|
42
62
|
initials: "GH",
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
time: "5h",
|
|
64
|
+
following: true,
|
|
65
|
+
body: "Process shot from this week. Compilers and copper ink — the plotter ran for eleven hours on this one.",
|
|
66
|
+
media: { src: "/media/process-shot.svg", alt: "Work-in-progress plotter drawing in copper ink" },
|
|
46
67
|
likes: 86,
|
|
47
68
|
comments: 9,
|
|
48
69
|
},
|
|
70
|
+
{
|
|
71
|
+
id: "p_03",
|
|
72
|
+
author: "Nina Simone",
|
|
73
|
+
handle: "@nina",
|
|
74
|
+
initials: "NS",
|
|
75
|
+
time: "8h",
|
|
76
|
+
following: false,
|
|
77
|
+
body: "Collectors of Dusk, no. 3 — your unlockable score is now in your library. Headphones on for this one.",
|
|
78
|
+
likes: 214,
|
|
79
|
+
comments: 31,
|
|
80
|
+
},
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
const suggestions = [
|
|
84
|
+
{ id: "u_01", name: "Mary Shelley", handle: "@mary", initials: "MS" },
|
|
85
|
+
{ id: "u_02", name: "Nina Simone", handle: "@nina", initials: "NS" },
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
const upcoming = [
|
|
89
|
+
{ id: "d_01", title: "Quiet Harbor", artist: "Mary Shelley", initials: "MS", date: "Sat, Oct 12" },
|
|
90
|
+
{ id: "d_02", title: "Dusk, no. 4", artist: "Nina Simone", initials: "NS", date: "Tue, Oct 15" },
|
|
49
91
|
]
|
|
50
92
|
|
|
93
|
+
const renderPost = (post: Post) => (
|
|
94
|
+
<article key={post.id}>
|
|
95
|
+
<Card>
|
|
96
|
+
<CardList>
|
|
97
|
+
<Entity
|
|
98
|
+
leading={<Avatar fallback={post.initials} alt={post.author} />}
|
|
99
|
+
title={post.author}
|
|
100
|
+
subtitle={`${post.handle} · ${post.time}`}
|
|
101
|
+
action={
|
|
102
|
+
<OverflowMenu label={`Post options for ${post.author}`} size="icon-sm">
|
|
103
|
+
<MenuItem>Copy link</MenuItem>
|
|
104
|
+
<MenuItem>Mute {post.handle}</MenuItem>
|
|
105
|
+
<MenuSeparator />
|
|
106
|
+
<MenuItem variant="destructive">Report post</MenuItem>
|
|
107
|
+
</OverflowMenu>
|
|
108
|
+
}
|
|
109
|
+
/>
|
|
110
|
+
</CardList>
|
|
111
|
+
<CardContent className="flex flex-col gap-4 pt-0">
|
|
112
|
+
<p className="text-body-md text-foreground">{post.body}</p>
|
|
113
|
+
{post.media ? (
|
|
114
|
+
<AspectBox ratio={16 / 9} className="overflow-hidden rounded-concentric-lg/6 bg-muted">
|
|
115
|
+
<img src={post.media.src} alt={post.media.alt} className="size-full object-cover" />
|
|
116
|
+
{post.drop ? (
|
|
117
|
+
<div className="absolute start-3 top-3">
|
|
118
|
+
<Badge appearance="overlay" size="sm" dot>
|
|
119
|
+
Live drop
|
|
120
|
+
</Badge>
|
|
121
|
+
</div>
|
|
122
|
+
) : null}
|
|
123
|
+
</AspectBox>
|
|
124
|
+
) : null}
|
|
125
|
+
{post.drop ? (
|
|
126
|
+
<Stack gap={1.5}>
|
|
127
|
+
<Row gap={2} align="center" justify="between">
|
|
128
|
+
<div className="min-w-0">
|
|
129
|
+
<p className="text-title-md text-foreground">{post.drop.title}</p>
|
|
130
|
+
<p className="text-body-sm text-muted-foreground">
|
|
131
|
+
{`${post.drop.price} · Edition of ${post.drop.supply}`}
|
|
132
|
+
</p>
|
|
133
|
+
</div>
|
|
134
|
+
<Button>{`Collect for ${post.drop.price}`}</Button>
|
|
135
|
+
</Row>
|
|
136
|
+
<Meter
|
|
137
|
+
value={post.drop.collected}
|
|
138
|
+
max={post.drop.supply}
|
|
139
|
+
size="sm"
|
|
140
|
+
getAriaValueText={(_, value) => `${value} of ${post.drop?.supply} collected`}
|
|
141
|
+
>
|
|
142
|
+
<Row gap={2} align="center" justify="between" className="mb-2">
|
|
143
|
+
<MeterLabel>{`${post.drop.collected} collected`}</MeterLabel>
|
|
144
|
+
<MeterValue>{(_, value) => `${(post.drop?.supply ?? 0) - value} left`}</MeterValue>
|
|
145
|
+
</Row>
|
|
146
|
+
<MeterTrack />
|
|
147
|
+
</Meter>
|
|
148
|
+
</Stack>
|
|
149
|
+
) : null}
|
|
150
|
+
{/* Pulled out by exactly a sm Button's own padding (px-3), so the
|
|
151
|
+
first icon lands on the text edge above it. */}
|
|
152
|
+
<Row gap={0.5} className="-ms-3">
|
|
153
|
+
<Button variant="ghost" size="sm" aria-label={`Like, ${post.likes} likes`}>
|
|
154
|
+
<Icon name="heart" />
|
|
155
|
+
{post.likes}
|
|
156
|
+
</Button>
|
|
157
|
+
<Button variant="ghost" size="sm" aria-label={`Comment, ${post.comments} comments`}>
|
|
158
|
+
<Icon name="comment" />
|
|
159
|
+
{post.comments}
|
|
160
|
+
</Button>
|
|
161
|
+
<Button variant="ghost" size="sm" aria-label="Share">
|
|
162
|
+
<Icon name="share" />
|
|
163
|
+
</Button>
|
|
164
|
+
</Row>
|
|
165
|
+
</CardContent>
|
|
166
|
+
</Card>
|
|
167
|
+
</article>
|
|
168
|
+
)
|
|
169
|
+
|
|
51
170
|
export function FeedPage() {
|
|
52
171
|
return (
|
|
53
|
-
<Page as="main" max="
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<
|
|
58
|
-
<
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
172
|
+
<Page as="main" max="wide" gap={4} className="py-6">
|
|
173
|
+
<Row gap={4} align="start" justify="center">
|
|
174
|
+
<Stack gap={3} className="min-w-0 max-w-(--region-feed) flex-1">
|
|
175
|
+
<PageHeader title="Home" />
|
|
176
|
+
<Card>
|
|
177
|
+
<CardContent>
|
|
178
|
+
<Row gap={1.5} align="start">
|
|
179
|
+
<Avatar fallback="AL" alt="Ada Lovelace" />
|
|
180
|
+
<Stack gap={1.5} className="min-w-0 flex-1">
|
|
181
|
+
<Textarea
|
|
182
|
+
autoResize
|
|
183
|
+
rows={2}
|
|
184
|
+
aria-label="New post"
|
|
185
|
+
placeholder="Share a process shot or an upcoming drop"
|
|
186
|
+
/>
|
|
187
|
+
<Row gap={1} align="center" justify="between">
|
|
188
|
+
<Row gap={0.5}>
|
|
189
|
+
<Button variant="ghost" size="icon-sm" aria-label="Add images">
|
|
190
|
+
<Icon name="image-stack" />
|
|
191
|
+
</Button>
|
|
192
|
+
<Button variant="ghost" size="icon-sm" aria-label="Attach an edition">
|
|
193
|
+
<Icon name="gem" />
|
|
194
|
+
</Button>
|
|
195
|
+
</Row>
|
|
196
|
+
<Button variant="secondary" size="sm">
|
|
197
|
+
Post
|
|
69
198
|
</Button>
|
|
70
|
-
|
|
71
|
-
|
|
199
|
+
</Row>
|
|
200
|
+
</Stack>
|
|
201
|
+
</Row>
|
|
202
|
+
</CardContent>
|
|
203
|
+
</Card>
|
|
204
|
+
<Tabs defaultValue="for-you">
|
|
205
|
+
<TabsList className="flex">
|
|
206
|
+
<TabsTrigger value="for-you">For you</TabsTrigger>
|
|
207
|
+
<TabsTrigger value="following">Following</TabsTrigger>
|
|
208
|
+
</TabsList>
|
|
209
|
+
<TabsPanel value="for-you">
|
|
210
|
+
<Stack gap={3}>
|
|
211
|
+
{posts.map(renderPost)}
|
|
212
|
+
{/* The next page, in the shape it will land in, while it fetches. */}
|
|
213
|
+
<Card aria-busy="true">
|
|
214
|
+
<CardContent className="flex flex-col gap-4">
|
|
215
|
+
<Row gap={1.5} align="center">
|
|
216
|
+
<Skeleton className="size-10 rounded-full" />
|
|
217
|
+
<Stack gap={1}>
|
|
218
|
+
<Skeleton className="h-3 w-32" />
|
|
219
|
+
<Skeleton className="h-3 w-20" />
|
|
220
|
+
</Stack>
|
|
221
|
+
</Row>
|
|
222
|
+
<Skeleton className="h-3 w-full" />
|
|
223
|
+
<Skeleton className="h-3 w-2/3" />
|
|
224
|
+
</CardContent>
|
|
225
|
+
</Card>
|
|
226
|
+
<Button variant="secondary" loading loadingLabel="Loading posts">
|
|
227
|
+
Load more posts
|
|
228
|
+
</Button>
|
|
229
|
+
</Stack>
|
|
230
|
+
</TabsPanel>
|
|
231
|
+
<TabsPanel value="following">
|
|
232
|
+
<Stack gap={3}>{posts.filter((post) => post.following).map(renderPost)}</Stack>
|
|
233
|
+
</TabsPanel>
|
|
234
|
+
</Tabs>
|
|
235
|
+
</Stack>
|
|
236
|
+
<aside aria-label="Suggestions" className="sticky top-6 hidden w-80 shrink-0 lg:block">
|
|
237
|
+
<Stack gap={3}>
|
|
238
|
+
<Card>
|
|
239
|
+
<CardHeader>
|
|
240
|
+
<CardTitle render={<h2 />}>Who to follow</CardTitle>
|
|
241
|
+
</CardHeader>
|
|
242
|
+
<CardList>
|
|
243
|
+
{suggestions.map((person) => (
|
|
244
|
+
<Entity
|
|
245
|
+
key={person.id}
|
|
246
|
+
leading={<Avatar fallback={person.initials} alt={person.name} />}
|
|
247
|
+
title={person.name}
|
|
248
|
+
subtitle={person.handle}
|
|
249
|
+
action={
|
|
250
|
+
<Button variant="secondary" size="sm" aria-label={`Follow ${person.name}`}>
|
|
251
|
+
Follow
|
|
252
|
+
</Button>
|
|
253
|
+
}
|
|
254
|
+
/>
|
|
255
|
+
))}
|
|
256
|
+
</CardList>
|
|
257
|
+
</Card>
|
|
258
|
+
<Card>
|
|
259
|
+
<CardHeader>
|
|
260
|
+
<CardTitle render={<h2 />}>Upcoming drops</CardTitle>
|
|
261
|
+
</CardHeader>
|
|
262
|
+
<CardList>
|
|
263
|
+
{upcoming.map((drop) => (
|
|
264
|
+
<Entity
|
|
265
|
+
key={drop.id}
|
|
266
|
+
leading={<Avatar fallback={drop.initials} alt={drop.artist} />}
|
|
267
|
+
title={drop.title}
|
|
268
|
+
subtitle={`${drop.artist} · ${drop.date}`}
|
|
269
|
+
action={
|
|
270
|
+
<Button variant="ghost" size="icon-sm" aria-label={`Remind me about ${drop.title}`}>
|
|
271
|
+
<Icon name="bell" />
|
|
272
|
+
</Button>
|
|
273
|
+
}
|
|
274
|
+
/>
|
|
275
|
+
))}
|
|
72
276
|
</CardList>
|
|
73
|
-
<AspectBox ratio={16 / 9}>
|
|
74
|
-
<img src={post.image} alt={post.alt} className="size-full object-cover" />
|
|
75
|
-
</AspectBox>
|
|
76
|
-
<CardContent className="flex flex-col gap-2">
|
|
77
|
-
<p className="text-body-md text-foreground">{post.body}</p>
|
|
78
|
-
{/* Pulled out by exactly a sm Button's own padding (px-3), so the
|
|
79
|
-
first icon lands on the text edge above it. Both are on the
|
|
80
|
-
spacing scale, so they stay aligned at every density. */}
|
|
81
|
-
<Row gap={1} className="-ms-3">
|
|
82
|
-
<Button variant="ghost" size="sm" aria-label={`Like — ${post.likes}`}>
|
|
83
|
-
<Icon name="heart" />
|
|
84
|
-
{post.likes}
|
|
85
|
-
</Button>
|
|
86
|
-
<Button variant="ghost" size="sm" aria-label={`Comments — ${post.comments}`}>
|
|
87
|
-
<Icon name="comment" />
|
|
88
|
-
{post.comments}
|
|
89
|
-
</Button>
|
|
90
|
-
<Button variant="ghost" size="sm" aria-label="Share">
|
|
91
|
-
<Icon name="share" />
|
|
92
|
-
</Button>
|
|
93
|
-
</Row>
|
|
94
|
-
</CardContent>
|
|
95
277
|
</Card>
|
|
96
|
-
</
|
|
97
|
-
|
|
98
|
-
</
|
|
99
|
-
<Button variant="secondary" loadingLabel="Loading…">Load more posts</Button>
|
|
278
|
+
</Stack>
|
|
279
|
+
</aside>
|
|
280
|
+
</Row>
|
|
100
281
|
</Page>
|
|
101
282
|
)
|
|
102
283
|
}
|