@olympusoss/canvas 4.0.0 → 5.0.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/README.md +108 -0
- package/package.json +14 -3
- package/src/atoms/avatar/avatar.md +185 -0
- package/src/atoms/avatar/avatar.styles.ts +48 -0
- package/src/atoms/avatar/avatar.tsx +99 -0
- package/src/atoms/badge/badge.md +237 -0
- package/src/atoms/badge/badge.styles.ts +79 -0
- package/src/atoms/badge/badge.tsx +86 -0
- package/src/atoms/breadcrumb/breadcrumb.md +233 -0
- package/src/atoms/breadcrumb/breadcrumb.styles.ts +40 -0
- package/src/atoms/breadcrumb/breadcrumb.tsx +130 -0
- package/src/atoms/button/button.android.tsx +6 -0
- package/src/atoms/button/button.ios.tsx +6 -0
- package/src/atoms/button/button.md +184 -0
- package/src/atoms/button/button.shared.tsx +79 -0
- package/src/atoms/button/button.styles.ts +152 -0
- package/src/atoms/button/button.tsx +6 -0
- package/src/atoms/button-group/button-group.android.tsx +6 -0
- package/src/atoms/button-group/button-group.ios.tsx +6 -0
- package/src/atoms/button-group/button-group.md +120 -0
- package/src/atoms/button-group/button-group.shared.tsx +398 -0
- package/src/atoms/button-group/button-group.styles.ts +483 -0
- package/src/atoms/button-group/button-group.tsx +6 -0
- package/src/atoms/checkbox/checkbox.android.tsx +6 -0
- package/src/atoms/checkbox/checkbox.ios.tsx +6 -0
- package/src/atoms/checkbox/checkbox.md +150 -0
- package/src/atoms/checkbox/checkbox.shared.tsx +103 -0
- package/src/atoms/checkbox/checkbox.styles.ts +106 -0
- package/src/atoms/checkbox/checkbox.tsx +6 -0
- package/src/atoms/combobox/combobox.android.tsx +6 -0
- package/src/atoms/combobox/combobox.ios.tsx +6 -0
- package/src/atoms/combobox/combobox.md +213 -0
- package/src/atoms/combobox/combobox.shared.tsx +160 -0
- package/src/atoms/combobox/combobox.styles.ts +270 -0
- package/src/atoms/combobox/combobox.tsx +6 -0
- package/src/atoms/divider/divider.md +140 -0
- package/src/atoms/divider/divider.styles.ts +35 -0
- package/src/atoms/divider/divider.tsx +67 -0
- package/src/atoms/dropdown/dropdown.android.tsx +6 -0
- package/src/atoms/dropdown/dropdown.ios.tsx +6 -0
- package/src/atoms/dropdown/dropdown.md +221 -0
- package/src/atoms/dropdown/dropdown.shared.tsx +190 -0
- package/src/atoms/dropdown/dropdown.styles.ts +233 -0
- package/src/atoms/dropdown/dropdown.tsx +6 -0
- package/src/atoms/icon/icon.md +131 -0
- package/src/atoms/icon/icon.styles.ts +30 -0
- package/src/atoms/icon/icon.tsx +328 -0
- package/src/atoms/index.ts +24 -0
- package/src/atoms/input/input.android.tsx +6 -0
- package/src/atoms/input/input.ios.tsx +6 -0
- package/src/atoms/input/input.md +118 -0
- package/src/atoms/input/input.shared.tsx +203 -0
- package/src/atoms/input/input.styles.ts +286 -0
- package/src/atoms/input/input.tsx +6 -0
- package/src/atoms/kbd/kbd.md +91 -0
- package/src/atoms/kbd/kbd.styles.ts +33 -0
- package/src/atoms/kbd/kbd.tsx +27 -0
- package/src/atoms/listbox/listbox.md +177 -0
- package/src/atoms/listbox/listbox.styles.ts +60 -0
- package/src/atoms/listbox/listbox.tsx +113 -0
- package/src/atoms/pagination/pagination.android.tsx +6 -0
- package/src/atoms/pagination/pagination.ios.tsx +6 -0
- package/src/atoms/pagination/pagination.md +133 -0
- package/src/atoms/pagination/pagination.shared.tsx +289 -0
- package/src/atoms/pagination/pagination.styles.ts +245 -0
- package/src/atoms/pagination/pagination.tsx +6 -0
- package/src/atoms/popover/popover.android.tsx +8 -0
- package/src/atoms/popover/popover.ios.tsx +6 -0
- package/src/atoms/popover/popover.md +87 -0
- package/src/atoms/popover/popover.shared.tsx +124 -0
- package/src/atoms/popover/popover.styles.ts +144 -0
- package/src/atoms/popover/popover.tsx +6 -0
- package/src/atoms/radio/radio.android.tsx +6 -0
- package/src/atoms/radio/radio.ios.tsx +6 -0
- package/src/atoms/radio/radio.md +173 -0
- package/src/atoms/radio/radio.shared.tsx +98 -0
- package/src/atoms/radio/radio.styles.ts +109 -0
- package/src/atoms/radio/radio.tsx +6 -0
- package/src/atoms/select/select.android.tsx +6 -0
- package/src/atoms/select/select.ios.tsx +6 -0
- package/src/atoms/select/select.md +156 -0
- package/src/atoms/select/select.shared.tsx +143 -0
- package/src/atoms/select/select.styles.ts +310 -0
- package/src/atoms/select/select.tsx +6 -0
- package/src/atoms/skeleton/skeleton.md +135 -0
- package/src/atoms/skeleton/skeleton.styles.ts +117 -0
- package/src/atoms/skeleton/skeleton.tsx +145 -0
- package/src/atoms/spinner/spinner.android.tsx +7 -0
- package/src/atoms/spinner/spinner.ios.tsx +7 -0
- package/src/atoms/spinner/spinner.md +94 -0
- package/src/atoms/spinner/spinner.shared.tsx +92 -0
- package/src/atoms/spinner/spinner.styles.tsx +115 -0
- package/src/atoms/spinner/spinner.tsx +7 -0
- package/src/atoms/switch/switch.android.tsx +6 -0
- package/src/atoms/switch/switch.ios.tsx +6 -0
- package/src/atoms/switch/switch.md +91 -0
- package/src/atoms/switch/switch.shared.tsx +97 -0
- package/src/atoms/switch/switch.styles.ts +79 -0
- package/src/atoms/switch/switch.tsx +6 -0
- package/src/atoms/textarea/textarea.android.tsx +6 -0
- package/src/atoms/textarea/textarea.ios.tsx +6 -0
- package/src/atoms/textarea/textarea.md +140 -0
- package/src/atoms/textarea/textarea.shared.tsx +74 -0
- package/src/atoms/textarea/textarea.styles.ts +116 -0
- package/src/atoms/textarea/textarea.tsx +6 -0
- package/src/atoms/tooltip/tooltip.android.tsx +6 -0
- package/src/atoms/tooltip/tooltip.ios.tsx +7 -0
- package/src/atoms/tooltip/tooltip.md +122 -0
- package/src/atoms/tooltip/tooltip.shared.tsx +113 -0
- package/src/atoms/tooltip/tooltip.styles.ts +113 -0
- package/src/atoms/tooltip/tooltip.tsx +6 -0
- package/src/atoms/typography/typography.md +330 -0
- package/src/atoms/typography/typography.styles.ts +95 -0
- package/src/atoms/typography/typography.tsx +76 -0
- package/src/index.ts +12 -2
- package/src/molecules/action-panels/action-panels.md +133 -0
- package/src/molecules/action-panels/action-panels.styles.ts +39 -0
- package/src/molecules/action-panels/action-panels.tsx +113 -0
- package/src/molecules/alert/alert.md +119 -0
- package/src/molecules/alert/alert.styles.ts +88 -0
- package/src/molecules/alert/alert.tsx +74 -0
- package/src/molecules/alert-dialog/alert-dialog.android.tsx +6 -0
- package/src/molecules/alert-dialog/alert-dialog.ios.tsx +6 -0
- package/src/molecules/alert-dialog/alert-dialog.md +177 -0
- package/src/molecules/alert-dialog/alert-dialog.shared.tsx +187 -0
- package/src/molecules/alert-dialog/alert-dialog.styles.ts +248 -0
- package/src/molecules/alert-dialog/alert-dialog.tsx +6 -0
- package/src/molecules/card/card.md +190 -0
- package/src/molecules/card/card.styles.ts +67 -0
- package/src/molecules/card/card.tsx +176 -0
- package/src/molecules/code-block/code-block.md +159 -0
- package/src/molecules/code-block/code-block.styles.ts +167 -0
- package/src/molecules/code-block/code-block.tsx +176 -0
- package/src/molecules/description-lists/description-lists.md +129 -0
- package/src/molecules/description-lists/description-lists.styles.ts +102 -0
- package/src/molecules/description-lists/description-lists.tsx +133 -0
- package/src/molecules/empty-state/empty-state.md +218 -0
- package/src/molecules/empty-state/empty-state.styles.ts +63 -0
- package/src/molecules/empty-state/empty-state.tsx +77 -0
- package/src/molecules/feeds/feeds.md +102 -0
- package/src/molecules/feeds/feeds.styles.ts +120 -0
- package/src/molecules/feeds/feeds.tsx +167 -0
- package/src/molecules/field/field.md +117 -0
- package/src/molecules/field/field.styles.ts +85 -0
- package/src/molecules/field/field.tsx +175 -0
- package/src/molecules/fieldset/fieldset.md +141 -0
- package/src/molecules/fieldset/fieldset.styles.ts +79 -0
- package/src/molecules/fieldset/fieldset.tsx +182 -0
- package/src/molecules/form/form.md +137 -0
- package/src/molecules/form/form.styles.ts +39 -0
- package/src/molecules/form/form.tsx +246 -0
- package/src/molecules/grid-lists/grid-lists.md +114 -0
- package/src/molecules/grid-lists/grid-lists.styles.ts +79 -0
- package/src/molecules/grid-lists/grid-lists.tsx +157 -0
- package/src/molecules/index.ts +16 -0
- package/src/molecules/media-objects/media-objects.md +87 -0
- package/src/molecules/media-objects/media-objects.styles.ts +94 -0
- package/src/molecules/media-objects/media-objects.tsx +128 -0
- package/src/molecules/stacked-lists/stacked-lists.md +116 -0
- package/src/molecules/stacked-lists/stacked-lists.styles.ts +111 -0
- package/src/molecules/stacked-lists/stacked-lists.tsx +195 -0
- package/src/molecules/stats/stats.md +166 -0
- package/src/molecules/stats/stats.styles.ts +91 -0
- package/src/molecules/stats/stats.tsx +88 -0
- package/src/organisms/calendar/calendar.android.tsx +6 -0
- package/src/organisms/calendar/calendar.ios.tsx +6 -0
- package/src/organisms/calendar/calendar.md +114 -0
- package/src/organisms/calendar/calendar.shared.tsx +146 -0
- package/src/organisms/calendar/calendar.styles.ts +315 -0
- package/src/organisms/calendar/calendar.tsx +6 -0
- package/src/organisms/charts/charts.md +326 -0
- package/src/organisms/charts/charts.styles.ts +135 -0
- package/src/organisms/charts/charts.tsx +124 -0
- package/src/organisms/command/command.md +117 -0
- package/src/organisms/command/command.styles.ts +179 -0
- package/src/organisms/command/command.tsx +164 -0
- package/src/organisms/data-table/data-table.md +182 -0
- package/src/organisms/data-table/data-table.styles.ts +103 -0
- package/src/organisms/data-table/data-table.tsx +105 -0
- package/src/organisms/dialog/dialog.android.tsx +6 -0
- package/src/organisms/dialog/dialog.ios.tsx +6 -0
- package/src/organisms/dialog/dialog.md +271 -0
- package/src/organisms/dialog/dialog.shared.tsx +230 -0
- package/src/organisms/dialog/dialog.styles.ts +272 -0
- package/src/organisms/dialog/dialog.tsx +6 -0
- package/src/organisms/filter-panel/filter-panel.md +116 -0
- package/src/organisms/filter-panel/filter-panel.styles.ts +83 -0
- package/src/organisms/filter-panel/filter-panel.tsx +91 -0
- package/src/organisms/index.ts +13 -0
- package/src/organisms/navbars/navbars.android.tsx +6 -0
- package/src/organisms/navbars/navbars.ios.tsx +6 -0
- package/src/organisms/navbars/navbars.md +144 -0
- package/src/organisms/navbars/navbars.shared.tsx +137 -0
- package/src/organisms/navbars/navbars.styles.ts +251 -0
- package/src/organisms/navbars/navbars.tsx +6 -0
- package/src/organisms/overlays/overlays.android.tsx +6 -0
- package/src/organisms/overlays/overlays.ios.tsx +6 -0
- package/src/organisms/overlays/overlays.md +123 -0
- package/src/organisms/overlays/overlays.shared.tsx +175 -0
- package/src/organisms/overlays/overlays.styles.ts +309 -0
- package/src/organisms/overlays/overlays.tsx +6 -0
- package/src/organisms/row-menu/row-menu.android.tsx +6 -0
- package/src/organisms/row-menu/row-menu.ios.tsx +6 -0
- package/src/organisms/row-menu/row-menu.md +102 -0
- package/src/organisms/row-menu/row-menu.shared.tsx +105 -0
- package/src/organisms/row-menu/row-menu.styles.ts +262 -0
- package/src/organisms/row-menu/row-menu.tsx +6 -0
- package/src/organisms/sidebar/sidebar.android.tsx +6 -0
- package/src/organisms/sidebar/sidebar.ios.tsx +6 -0
- package/src/organisms/sidebar/sidebar.md +188 -0
- package/src/organisms/sidebar/sidebar.shared.tsx +167 -0
- package/src/organisms/sidebar/sidebar.styles.ts +262 -0
- package/src/organisms/sidebar/sidebar.tsx +6 -0
- package/src/organisms/stepper/stepper.android.tsx +6 -0
- package/src/organisms/stepper/stepper.ios.tsx +6 -0
- package/src/organisms/stepper/stepper.md +150 -0
- package/src/organisms/stepper/stepper.shared.tsx +158 -0
- package/src/organisms/stepper/stepper.styles.ts +280 -0
- package/src/organisms/stepper/stepper.tsx +6 -0
- package/src/organisms/tabs/tabs.android.tsx +6 -0
- package/src/organisms/tabs/tabs.ios.tsx +6 -0
- package/src/organisms/tabs/tabs.md +127 -0
- package/src/organisms/tabs/tabs.shared.tsx +281 -0
- package/src/organisms/tabs/tabs.styles.ts +398 -0
- package/src/organisms/tabs/tabs.tsx +6 -0
- package/src/style/color.ts +17 -0
- package/src/style/index.ts +14 -0
- package/src/style/primitives.ts +26 -0
- package/src/style/responsive.ts +45 -0
- package/src/style/shadow.ts +21 -0
- package/src/style/theme.tsx +56 -0
- package/src/style/tokens.ts +487 -0
- package/src/theme.ts +21 -0
- package/styles/canvas.css +128 -67
- package/tsconfig.json +4 -2
- package/src/cn.ts +0 -3
- package/styles/base.css +0 -17
- package/styles/components/alert.css +0 -66
- package/styles/components/app-shell.css +0 -46
- package/styles/components/avatar.css +0 -15
- package/styles/components/badge.css +0 -83
- package/styles/components/breadcrumb.css +0 -35
- package/styles/components/button-group.css +0 -23
- package/styles/components/button.css +0 -107
- package/styles/components/calendar.css +0 -73
- package/styles/components/card.css +0 -58
- package/styles/components/checkbox.css +0 -55
- package/styles/components/code-block.css +0 -18
- package/styles/components/combobox.css +0 -75
- package/styles/components/command.css +0 -94
- package/styles/components/data-table.css +0 -142
- package/styles/components/dialog.css +0 -72
- package/styles/components/dropdown.css +0 -54
- package/styles/components/empty-state.css +0 -17
- package/styles/components/field.css +0 -27
- package/styles/components/filter-panel.css +0 -58
- package/styles/components/form.css +0 -27
- package/styles/components/icon.css +0 -8
- package/styles/components/input-group.css +0 -45
- package/styles/components/input.css +0 -56
- package/styles/components/kbd.css +0 -15
- package/styles/components/page-header.css +0 -52
- package/styles/components/pagination.css +0 -48
- package/styles/components/popover.css +0 -14
- package/styles/components/radio.css +0 -28
- package/styles/components/row-menu.css +0 -69
- package/styles/components/section-card.css +0 -49
- package/styles/components/select.css +0 -57
- package/styles/components/separator.css +0 -32
- package/styles/components/sheet.css +0 -70
- package/styles/components/sidebar.css +0 -146
- package/styles/components/skeleton.css +0 -32
- package/styles/components/spinner.css +0 -26
- package/styles/components/stat-card.css +0 -71
- package/styles/components/stepper.css +0 -63
- package/styles/components/switch.css +0 -45
- package/styles/components/tabs.css +0 -40
- package/styles/components/textarea.css +0 -31
- package/styles/components/toast.css +0 -95
- package/styles/components/tooltip.css +0 -53
- package/styles/components/topbar.css +0 -24
- package/styles/components/typography.css +0 -105
- package/styles/patterns/backdrops.css +0 -35
- package/styles/patterns/density.css +0 -66
- package/styles/patterns/focus.css +0 -38
- package/styles/patterns/glass.css +0 -85
- package/styles/patterns/high-contrast.css +0 -70
- package/styles/patterns/reduced-motion.css +0 -12
- package/styles/patterns/scrollbar.css +0 -10
- package/styles/reset.css +0 -89
- package/styles/tokens/colors.css +0 -106
- package/styles/tokens/motion.css +0 -33
- package/styles/tokens/radius.css +0 -10
- package/styles/tokens/shadows.css +0 -35
- package/styles/tokens/spacing.css +0 -19
- package/styles/tokens/typography.css +0 -6
- package/styles/tokens/z-index.css +0 -12
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Grid Lists
|
|
2
|
+
|
|
3
|
+
Tiled card grids for people directories, item collections, and image galleries.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<GridList
|
|
9
|
+
items={[
|
|
10
|
+
{ title: "Rachel Chen", subtitle: "Engineering Lead", avatar: "/rachel-chen.jpg", actions: [
|
|
11
|
+
{ label: "Message", outline: true },
|
|
12
|
+
{ label: "View", ghost: true }
|
|
13
|
+
] },
|
|
14
|
+
{ title: "Ada Lovelace", subtitle: "Staff Engineer", avatar: "/ada-lovelace.jpg", actions: [
|
|
15
|
+
{ label: "Message", outline: true },
|
|
16
|
+
{ label: "View", ghost: true }
|
|
17
|
+
] },
|
|
18
|
+
{ title: "Kevin Turner", subtitle: "Product Designer", avatar: "KT", actions: [
|
|
19
|
+
{ label: "Message", outline: true },
|
|
20
|
+
{ label: "View", ghost: true }
|
|
21
|
+
] }
|
|
22
|
+
]}
|
|
23
|
+
cols2
|
|
24
|
+
/>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Variants
|
|
28
|
+
|
|
29
|
+
### Variant - gallery
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
<GridList
|
|
33
|
+
items={[
|
|
34
|
+
{ title: "hero-banner.png", subtitle: "1.2 MB", color: "primary" },
|
|
35
|
+
{ title: "icon-set.svg", subtitle: "340 KB", color: "blue-500" },
|
|
36
|
+
{ title: "product-shot.jpg", subtitle: "2.8 MB", color: "emerald-500" },
|
|
37
|
+
{ title: "avatar-default.png", subtitle: "96 KB", color: "amber-500" }
|
|
38
|
+
]}
|
|
39
|
+
gallery
|
|
40
|
+
cols3
|
|
41
|
+
/>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Do & Don't
|
|
45
|
+
|
|
46
|
+
### People (card grid)
|
|
47
|
+
|
|
48
|
+
**Do** — Let auto-fill minmax columns size to the available width so cards wrap cleanly at any breakpoint.
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
<GridList cols2 items={[
|
|
52
|
+
{ title: "Rachel Chen", subtitle: "Engineering Lead", avatar: "RC" },
|
|
53
|
+
{ title: "Ada Lovelace", subtitle: "Staff Engineer", avatar: "AL" },
|
|
54
|
+
{ title: "Kevin Turner", subtitle: "Product Designer", avatar: "KT" }
|
|
55
|
+
]} />
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Don't** — A fixed column count with hard-width cards overflows the row on narrow viewports instead of reflowing.
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<View style={{ flexDirection: "row", gap: 14 }}>
|
|
62
|
+
<View style={{ width: 200, alignItems: "center", gap: 8, padding: 20, borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, ...shadow("sm") }}>
|
|
63
|
+
<Avatar large name="Rachel Chen">RC</Avatar>
|
|
64
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "600", color: tokens["card-foreground"] }}>Rachel Chen</Text>
|
|
65
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>Engineering Lead</Text>
|
|
66
|
+
</View>
|
|
67
|
+
<View style={{ width: 200, alignItems: "center", gap: 8, padding: 20, borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, ...shadow("sm") }}>
|
|
68
|
+
<Avatar large name="Ada Lovelace">AL</Avatar>
|
|
69
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "600", color: tokens["card-foreground"] }}>Ada Lovelace</Text>
|
|
70
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>Staff Engineer</Text>
|
|
71
|
+
</View>
|
|
72
|
+
<View style={{ width: 200, alignItems: "center", gap: 8, padding: 20, borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, ...shadow("sm") }}>
|
|
73
|
+
<Avatar large name="Kevin Turner">KT</Avatar>
|
|
74
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "600", color: tokens["card-foreground"] }}>Kevin Turner</Text>
|
|
75
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>Product Designer</Text>
|
|
76
|
+
</View>
|
|
77
|
+
</View>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Image gallery
|
|
81
|
+
|
|
82
|
+
**Do** — Lock a consistent aspect ratio so the grid stays even and nothing reflows once thumbnails load.
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
<GridList gallery cols3 items={[
|
|
86
|
+
{ title: "hero-banner.png", subtitle: "1.2 MB", color: "primary" },
|
|
87
|
+
{ title: "icon-set.svg", subtitle: "340 KB", color: "blue-500" },
|
|
88
|
+
{ title: "product-shot.jpg", subtitle: "2.8 MB", color: "emerald-500" },
|
|
89
|
+
{ title: "avatar-default.png", subtitle: "96 KB", color: "amber-500" }
|
|
90
|
+
]} />
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Don't** — Letting each thumbnail keep its intrinsic height makes a ragged grid and shifts the layout as images load.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 12 }}>
|
|
97
|
+
<View style={{ width: 140 }}>
|
|
98
|
+
<View style={{ width: "100%", height: 96, borderRadius: 6, backgroundColor: alpha(tokens.primary, 0.2) }} />
|
|
99
|
+
<Text style={{ marginTop: 8, fontSize: 12.5, fontWeight: "500", color: tokens["card-foreground"] }}>hero-banner.png</Text>
|
|
100
|
+
</View>
|
|
101
|
+
<View style={{ width: 140 }}>
|
|
102
|
+
<View style={{ width: "100%", height: 160, borderRadius: 6, backgroundColor: alpha(palette["blue-500"], 0.2) }} />
|
|
103
|
+
<Text style={{ marginTop: 8, fontSize: 12.5, fontWeight: "500", color: tokens["card-foreground"] }}>icon-set.svg</Text>
|
|
104
|
+
</View>
|
|
105
|
+
<View style={{ width: 140 }}>
|
|
106
|
+
<View style={{ width: "100%", height: 64, borderRadius: 6, backgroundColor: alpha(palette["emerald-500"], 0.2) }} />
|
|
107
|
+
<Text style={{ marginTop: 8, fontSize: 12.5, fontWeight: "500", color: tokens["card-foreground"] }}>product-shot.jpg</Text>
|
|
108
|
+
</View>
|
|
109
|
+
<View style={{ width: 140 }}>
|
|
110
|
+
<View style={{ width: "100%", height: 128, borderRadius: 6, backgroundColor: alpha(palette["amber-500"], 0.2) }} />
|
|
111
|
+
<Text style={{ marginTop: 8, fontSize: 12.5, fontWeight: "500", color: tokens["card-foreground"] }}>avatar-default.png</Text>
|
|
112
|
+
</View>
|
|
113
|
+
</View>
|
|
114
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type ViewStyle, type TextStyle, type DimensionValue } from "react-native";
|
|
2
|
+
import { type ColorTokens, palette, alpha } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located GridList styles. Layout-only fragments are static objects; anything
|
|
5
|
+
// that reads a color is a function of the active tokens, so the tiles follow
|
|
6
|
+
// light/dark and read as glass when the ThemeProvider's surface is "glass"
|
|
7
|
+
// (since tokens.card / tokens.muted swap at the theming level).
|
|
8
|
+
|
|
9
|
+
export type Columns = "cols2" | "cols3";
|
|
10
|
+
|
|
11
|
+
// Per-tile width share per column count (the desktop, `base` value). The
|
|
12
|
+
// fraction leaves room for the gap; `grow` (applied alongside) lets a trailing
|
|
13
|
+
// tile expand to fill, and the responsive `sm` value (`sm:w-full`) collapses
|
|
14
|
+
// each tile to a single column on phones and below (desktop-first).
|
|
15
|
+
export const TILE_WIDTH: Record<Columns, DimensionValue> = {
|
|
16
|
+
cols2: "48%",
|
|
17
|
+
cols3: "31%",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Container: a wrapping flex row. `compact` tightens the inter-tile gap
|
|
21
|
+
// (gap-2 -> 8) from the default (gap-3.5 -> 14).
|
|
22
|
+
export const container: ViewStyle = { flexDirection: "row", flexWrap: "wrap" };
|
|
23
|
+
export function containerGap(compact: boolean): ViewStyle {
|
|
24
|
+
return { gap: compact ? 8 : 14 };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Each tile grows to share the row; the width share is layered in by the tile.
|
|
28
|
+
export const tileGrow: ViewStyle = { flexGrow: 1 };
|
|
29
|
+
|
|
30
|
+
// --- gallery mode -----------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
// The square color block: full width, fixed height, rounded. The tint is the
|
|
33
|
+
// item color at 20% (bg-{color}/20), falling back to the muted surface.
|
|
34
|
+
export const galleryBlock: ViewStyle = { width: "100%", height: 128, borderRadius: 6 };
|
|
35
|
+
|
|
36
|
+
export function galleryBlockFill(tokens: ColorTokens, color?: string): ViewStyle {
|
|
37
|
+
return { backgroundColor: color != null ? alpha(resolveColor(tokens, color), 0.2) : tokens.muted };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const galleryMeta: ViewStyle = { marginTop: 8 };
|
|
41
|
+
|
|
42
|
+
export function galleryTitle(tokens: ColorTokens): TextStyle {
|
|
43
|
+
return { fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens["card-foreground"] };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function gallerySubtitle(tokens: ColorTokens): TextStyle {
|
|
47
|
+
return { fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- people / card mode -----------------------------------------------------
|
|
51
|
+
|
|
52
|
+
// Card tile surface composition: grow + center + width share + padding
|
|
53
|
+
// (p-4 -> 16 compact, p-5 -> 20). The width share is layered in by the tile.
|
|
54
|
+
export function tilePad(compact: boolean): ViewStyle {
|
|
55
|
+
return { flexGrow: 1, alignItems: "center", padding: compact ? 16 : 20 };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const cardInner: ViewStyle = { alignItems: "center", gap: 8 };
|
|
59
|
+
|
|
60
|
+
export function cardTitle(tokens: ColorTokens): TextStyle {
|
|
61
|
+
return { fontSize: 14, lineHeight: 20, fontWeight: "600", color: tokens["card-foreground"] };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function cardSubtitle(tokens: ColorTokens): TextStyle {
|
|
65
|
+
return { fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const badgeSpacing: ViewStyle = { marginTop: 4 };
|
|
69
|
+
|
|
70
|
+
export const actions: ViewStyle = { flexDirection: "row", gap: 8, marginTop: 8 };
|
|
71
|
+
|
|
72
|
+
// Resolve a color name (a semantic token key like "primary" or a Tailwind
|
|
73
|
+
// palette key like "blue-500") to its hex. Falls back to the muted-foreground
|
|
74
|
+
// token for an unknown name, so any tint composes safely.
|
|
75
|
+
function resolveColor(tokens: ColorTokens, color: string): string {
|
|
76
|
+
if (color in tokens) return tokens[color as keyof ColorTokens];
|
|
77
|
+
if (color in palette) return palette[color];
|
|
78
|
+
return tokens["muted-foreground"];
|
|
79
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { type DimensionValue } from "react-native";
|
|
2
|
+
import { View, Text, useTheme, useResponsive, type StyleProp, type ViewStyle } from "../../style/index.js";
|
|
3
|
+
import { Card } from "../card/card.js";
|
|
4
|
+
import { Avatar } from "../../atoms/avatar/avatar.js";
|
|
5
|
+
import { Badge } from "../../atoms/badge/badge.js";
|
|
6
|
+
import { Button } from "../../atoms/button/button.js";
|
|
7
|
+
import * as s from "./grid-lists.styles.js";
|
|
8
|
+
import { type Columns } from "./grid-lists.styles.js";
|
|
9
|
+
|
|
10
|
+
// GridList: a responsive grid of card tiles for a people directory, a project
|
|
11
|
+
// or file collection, or any tiled gallery. Each item renders as a bordered
|
|
12
|
+
// card tile with a leading avatar, a title, and supporting text or a badge.
|
|
13
|
+
//
|
|
14
|
+
// RN has no CSS grid, so the grid is built from a flex-row flex-wrap container
|
|
15
|
+
// whose tiles carry a fractional flex-basis (48% / 31% width) plus grow, so they
|
|
16
|
+
// share the row evenly and reflow as the row fills. Tiles are authored
|
|
17
|
+
// desktop-first: they take their column share on wide viewports and collapse to
|
|
18
|
+
// full width on phones and below (the `sm` responsive width).
|
|
19
|
+
//
|
|
20
|
+
// Boolean-prop API: one boolean per option, grouped by axis, first-match
|
|
21
|
+
// precedence within an axis (mirrors Button's intentOf).
|
|
22
|
+
//
|
|
23
|
+
// - Columns (pick one; default is two-up): `cols3` packs three tiles per row,
|
|
24
|
+
// `cols2` is the explicit two-up. With neither set the grid is two-up.
|
|
25
|
+
// - Density: `compact` tightens the gap and per-tile padding for denser grids.
|
|
26
|
+
|
|
27
|
+
/** A trailing tile action, rendered as a Button in people mode. */
|
|
28
|
+
export interface GridListAction {
|
|
29
|
+
/** Button label (e.g. "Message"). */
|
|
30
|
+
label: string;
|
|
31
|
+
/** Render as an outline button (the default look is solid). */
|
|
32
|
+
outline?: boolean;
|
|
33
|
+
/** Render as a ghost button. */
|
|
34
|
+
ghost?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface GridListItem {
|
|
38
|
+
/** Primary label for the tile (a name, project, or file). */
|
|
39
|
+
title: string;
|
|
40
|
+
/** Optional supporting line beneath the title (a role, path, or size). */
|
|
41
|
+
subtitle?: string;
|
|
42
|
+
/** Optional avatar photo URL or initials source for the leading avatar. */
|
|
43
|
+
avatar?: string;
|
|
44
|
+
/** Optional status word rendered as a trailing badge (e.g. "Active"). */
|
|
45
|
+
badge?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Gallery-tile block tint: a palette or token color name (e.g. "primary",
|
|
48
|
+
* "blue-500"). Painted at 20% behind the filename in gallery mode.
|
|
49
|
+
*/
|
|
50
|
+
color?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Trailing actions rendered as a button row beneath the title (people mode),
|
|
53
|
+
* e.g. an outline "Message" and a ghost "View".
|
|
54
|
+
*/
|
|
55
|
+
actions?: GridListAction[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface GridListProps {
|
|
59
|
+
/** The tiles to render, one card per entry. */
|
|
60
|
+
items: GridListItem[];
|
|
61
|
+
// Columns (pick one; default is two-up).
|
|
62
|
+
cols3?: boolean;
|
|
63
|
+
cols2?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Gallery mode: render each tile as a borderless thumbnail (a square color
|
|
66
|
+
* block from `item.color`, with a left-aligned filename and size below).
|
|
67
|
+
* Avatars, badges, and actions are not shown in this mode. Omit for the
|
|
68
|
+
* default people/card tile.
|
|
69
|
+
*/
|
|
70
|
+
gallery?: boolean;
|
|
71
|
+
// Density modifier: tighter gap and tile padding.
|
|
72
|
+
compact?: boolean;
|
|
73
|
+
/** Escape hatch for layout/positioning composition (mainly width). */
|
|
74
|
+
style?: StyleProp<ViewStyle>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Column precedence when more than one is passed: first match wins.
|
|
78
|
+
function columnsOf(p: GridListProps): Columns {
|
|
79
|
+
if (p.cols3) return "cols3";
|
|
80
|
+
if (p.cols2) return "cols2";
|
|
81
|
+
return "cols2";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// A borderless gallery thumbnail: a square color block with a filename and size
|
|
85
|
+
// below. Owns the responsive width so it collapses to full width on phones.
|
|
86
|
+
function GalleryTile({ item, columns }: { item: GridListItem; columns: Columns }) {
|
|
87
|
+
const { tokens } = useTheme();
|
|
88
|
+
const width = useResponsive<DimensionValue>({ base: s.TILE_WIDTH[columns], sm: "100%" });
|
|
89
|
+
return (
|
|
90
|
+
<View style={[s.tileGrow, { width }]}>
|
|
91
|
+
{/* Square color block. A single translucent tint stands in for the legacy
|
|
92
|
+
gradient swatch. */}
|
|
93
|
+
<View style={[s.galleryBlock, s.galleryBlockFill(tokens, item.color)]} />
|
|
94
|
+
<View style={s.galleryMeta}>
|
|
95
|
+
<Text style={s.galleryTitle(tokens)}>{item.title}</Text>
|
|
96
|
+
{item.subtitle != null ? <Text style={s.gallerySubtitle(tokens)}>{item.subtitle}</Text> : null}
|
|
97
|
+
</View>
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// A bordered people card tile: a leading avatar, a title, supporting text, an
|
|
103
|
+
// optional badge, and an optional action row. Owns the responsive width so it
|
|
104
|
+
// collapses to full width on phones.
|
|
105
|
+
function PeopleTile({ item, columns, compact }: { item: GridListItem; columns: Columns; compact: boolean }) {
|
|
106
|
+
const { tokens } = useTheme();
|
|
107
|
+
const width = useResponsive<DimensionValue>({ base: s.TILE_WIDTH[columns], sm: "100%" });
|
|
108
|
+
return (
|
|
109
|
+
<Card style={[s.tilePad(compact), { width }]}>
|
|
110
|
+
<View style={s.cardInner}>
|
|
111
|
+
<Avatar large src={isPhoto(item.avatar) ? item.avatar : undefined} name={item.title}>
|
|
112
|
+
{item.avatar && !isPhoto(item.avatar) ? item.avatar : undefined}
|
|
113
|
+
</Avatar>
|
|
114
|
+
<Text style={s.cardTitle(tokens)}>{item.title}</Text>
|
|
115
|
+
{item.subtitle != null ? <Text style={s.cardSubtitle(tokens)}>{item.subtitle}</Text> : null}
|
|
116
|
+
{item.badge != null ? (
|
|
117
|
+
<View style={s.badgeSpacing}>
|
|
118
|
+
<Badge secondary>{item.badge}</Badge>
|
|
119
|
+
</View>
|
|
120
|
+
) : null}
|
|
121
|
+
{item.actions != null && item.actions.length > 0 ? (
|
|
122
|
+
<View style={s.actions}>
|
|
123
|
+
{item.actions.map((action, i) => (
|
|
124
|
+
<Button key={`${action.label}-${i}`} small outline={action.outline} ghost={action.ghost}>
|
|
125
|
+
{action.label}
|
|
126
|
+
</Button>
|
|
127
|
+
))}
|
|
128
|
+
</View>
|
|
129
|
+
) : null}
|
|
130
|
+
</View>
|
|
131
|
+
</Card>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function GridList(props: GridListProps) {
|
|
136
|
+
const { items, gallery, compact, style } = props;
|
|
137
|
+
const columns = columnsOf(props);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<View style={[s.container, s.containerGap(!!compact), style]}>
|
|
141
|
+
{items.map((item, index) =>
|
|
142
|
+
gallery ? (
|
|
143
|
+
<GalleryTile key={`${item.title}-${index}`} item={item} columns={columns} />
|
|
144
|
+
) : (
|
|
145
|
+
<PeopleTile key={`${item.title}-${index}`} item={item} columns={columns} compact={!!compact} />
|
|
146
|
+
),
|
|
147
|
+
)}
|
|
148
|
+
</View>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Treat anything that looks like a URL or path as a photo source; otherwise the
|
|
153
|
+
// value is initials, handed to the Avatar fallback.
|
|
154
|
+
function isPhoto(value?: string): value is string {
|
|
155
|
+
if (!value) return false;
|
|
156
|
+
return value.startsWith("http") || value.startsWith("/") || value.startsWith("data:");
|
|
157
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Molecules: the React Native UI kit components at the molecules atomic level.
|
|
2
|
+
export * from "./action-panels/action-panels.js";
|
|
3
|
+
export * from "./alert/alert.js";
|
|
4
|
+
export * from "./alert-dialog/alert-dialog.js";
|
|
5
|
+
export * from "./card/card.js";
|
|
6
|
+
export * from "./code-block/code-block.js";
|
|
7
|
+
export * from "./description-lists/description-lists.js";
|
|
8
|
+
export * from "./empty-state/empty-state.js";
|
|
9
|
+
export * from "./feeds/feeds.js";
|
|
10
|
+
export * from "./field/field.js";
|
|
11
|
+
export * from "./fieldset/fieldset.js";
|
|
12
|
+
export * from "./form/form.js";
|
|
13
|
+
export * from "./grid-lists/grid-lists.js";
|
|
14
|
+
export * from "./media-objects/media-objects.js";
|
|
15
|
+
export * from "./stacked-lists/stacked-lists.js";
|
|
16
|
+
export * from "./stats/stats.js";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Media Objects
|
|
2
|
+
|
|
3
|
+
Image or icon paired with text content. The fundamental building block for list items, notifications, and comment layouts.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<MediaObject
|
|
9
|
+
avatar="RC"
|
|
10
|
+
title="Rachel Chen"
|
|
11
|
+
description="Engineering Lead"
|
|
12
|
+
body="Reviewed the latest pull request and left comments on the auth middleware changes."
|
|
13
|
+
start
|
|
14
|
+
bordered
|
|
15
|
+
/>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Variants
|
|
19
|
+
|
|
20
|
+
### Variant - icon
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
<View style={{ maxWidth: 560, gap: 12 }}>
|
|
24
|
+
<MediaObject bordered start title="Security first" description="End-to-end encryption with automatic key rotation." icon={<Icon shield primary size={18} />} />
|
|
25
|
+
<MediaObject bordered start title="Real-time analytics" description="Live dashboards with sub-second refresh latency." icon={<Icon activity primary size={18} />} />
|
|
26
|
+
</View>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Variant - action
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
<View style={{ maxWidth: 480 }}>
|
|
33
|
+
<MediaObject bordered center truncate src="/ada-lovelace.jpg" title="Ada Lovelace" description="ada@example.com" action={<Button outline small>Invite</Button>} />
|
|
34
|
+
</View>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Do & Don't
|
|
38
|
+
|
|
39
|
+
### Avatar
|
|
40
|
+
|
|
41
|
+
**Do** — Top-align with items-start so the avatar anchors to the first line of the title.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<MediaObject bordered start style={{ maxWidth: 480 }} src="/rachel-chen.jpg" title="Rachel Chen" description="Engineering Lead" body="Reviewed the latest pull request and left comments on the auth middleware changes. Need to discuss the token rotation approach before merging." />
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Don't** — Centering the avatar against a multi-line body leaves it floating beside the middle of the text.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<MediaObject bordered center style={{ maxWidth: 480 }} src="/rachel-chen.jpg" title="Rachel Chen" description="Engineering Lead" body="Reviewed the latest pull request and left comments on the auth middleware changes. Need to discuss the token rotation approach before merging." />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Icon
|
|
54
|
+
|
|
55
|
+
**Do** — Fix the icon box at h-9 w-9 with an 18px glyph so it reads as a tidy lead affordance.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
<MediaObject bordered start style={{ maxWidth: 480 }} title="Security first" description="End-to-end encryption with automatic key rotation." icon={<Icon shield primary size={18} />} />
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Don't** — An oversized icon box throws off the optical balance with the two-line text.
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
<View style={{ flexDirection: "row", alignItems: "flex-start", gap: 12, borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, padding: 16, maxWidth: 480 }}>
|
|
65
|
+
<View style={{ flexShrink: 0, alignItems: "center", justifyContent: "center", borderRadius: 6, backgroundColor: alpha(tokens.primary, 0.15), padding: 8 }}>
|
|
66
|
+
<Icon shield primary size={32} />
|
|
67
|
+
</View>
|
|
68
|
+
<View style={{ minWidth: 0, flexGrow: 1, flexShrink: 1, flexBasis: "0%", gap: 2 }}>
|
|
69
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "600", color: tokens.foreground }}>Security first</Text>
|
|
70
|
+
<Text style={{ fontSize: 12, lineHeight: 18, color: tokens["muted-foreground"] }}>End-to-end encryption with automatic key rotation.</Text>
|
|
71
|
+
</View>
|
|
72
|
+
</View>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Action
|
|
76
|
+
|
|
77
|
+
**Do** — Use min-w-0 + truncate on the text and shrink-0 on the button to keep the action pinned right.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<MediaObject bordered center truncate style={{ maxWidth: 480 }} src="/ada-lovelace.jpg" title="Ada Lovelace" description="ada.lovelace@analytical-engine.example.com" action={<Button outline small>Invite</Button>} />
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Don't** — Without truncation a long email wraps and pushes the trailing button out of alignment.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<MediaObject bordered center style={{ maxWidth: 480 }} src="/ada-lovelace.jpg" title="Ada Lovelace" description="ada.lovelace@analytical-engine.example.com" action={<Button outline small>Invite</Button>} />
|
|
87
|
+
```
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { type ViewStyle, type TextStyle, type ImageStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens, alpha } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located MediaObject styles. Layout-only fragments are static objects; parts
|
|
5
|
+
// that read a color are functions of the active tokens, so the bordered card
|
|
6
|
+
// surface follows light/dark (and reads as glass when the ThemeProvider's
|
|
7
|
+
// surface is "glass", since tokens.card is swapped translucent at the theming
|
|
8
|
+
// level) and the tinted icon box derives from the brand primary.
|
|
9
|
+
|
|
10
|
+
export type Align = "center" | "start";
|
|
11
|
+
export type Direction = "reversed" | "leading";
|
|
12
|
+
|
|
13
|
+
// flexDirection per direction (flex-row vs flex-row-reverse).
|
|
14
|
+
export const DIRECTION_ROW: Record<Direction, ViewStyle["flexDirection"]> = {
|
|
15
|
+
leading: "row",
|
|
16
|
+
reversed: "row-reverse",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// alignItems per alignment (items-center vs items-start).
|
|
20
|
+
export const ALIGN_ITEMS: Record<Align, ViewStyle["alignItems"]> = {
|
|
21
|
+
center: "center",
|
|
22
|
+
start: "flex-start",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// The bare row: flex-row(-reverse) + gap-3 + items-*. The base layout-only part;
|
|
26
|
+
// flexDirection/alignItems are composed on top from the axis records above.
|
|
27
|
+
export const containerBase: ViewStyle = { gap: 12 };
|
|
28
|
+
|
|
29
|
+
// bordered: wraps the row in the card surface (rounded-lg border border-border
|
|
30
|
+
// bg-card p-4). tokens.card goes translucent under glass.
|
|
31
|
+
export function borderedSurface(tokens: ColorTokens): ViewStyle {
|
|
32
|
+
return { borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, padding: 16 };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Leading photo wrapper: shrink-0 w-10 h-10 overflow-hidden rounded-full bg-muted.
|
|
36
|
+
export function photoBox(tokens: ColorTokens): ViewStyle {
|
|
37
|
+
return {
|
|
38
|
+
flexShrink: 0,
|
|
39
|
+
width: 40,
|
|
40
|
+
height: 40,
|
|
41
|
+
overflow: "hidden",
|
|
42
|
+
borderRadius: 9999,
|
|
43
|
+
backgroundColor: tokens.muted,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The photo itself: w-full h-full rounded-full.
|
|
48
|
+
export const photoImage: ImageStyle = { width: "100%", height: "100%", borderRadius: 9999 };
|
|
49
|
+
|
|
50
|
+
// The leading icon box: shrink-0 items-center justify-center w-9 h-9 rounded-md
|
|
51
|
+
// bg-primary/15 — a fixed tinted square with the glyph centered.
|
|
52
|
+
export function iconBox(tokens: ColorTokens): ViewStyle {
|
|
53
|
+
return {
|
|
54
|
+
flexShrink: 0,
|
|
55
|
+
alignItems: "center",
|
|
56
|
+
justifyContent: "center",
|
|
57
|
+
width: 36,
|
|
58
|
+
height: 36,
|
|
59
|
+
borderRadius: 6,
|
|
60
|
+
backgroundColor: alpha(tokens.primary, 0.15),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The glyph inside the icon box: text-primary text-base font-semibold.
|
|
65
|
+
export function iconGlyph(tokens: ColorTokens): TextStyle {
|
|
66
|
+
return { color: tokens.primary, fontSize: 16, lineHeight: 24, fontWeight: "600" };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Content column: min-w-0 flex-1 gap-0.5 — lets the text truncate instead of
|
|
70
|
+
// pushing a trailing action out of alignment.
|
|
71
|
+
export const content: ViewStyle = { minWidth: 0, flexGrow: 1, flexShrink: 1, flexBasis: "0%", gap: 2 };
|
|
72
|
+
|
|
73
|
+
// Title line: text-sm font-semibold text-foreground.
|
|
74
|
+
export function title(tokens: ColorTokens): TextStyle {
|
|
75
|
+
return { fontSize: 14, lineHeight: 20, fontWeight: "600", color: tokens.foreground };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Description line: text-xs text-muted-foreground.
|
|
79
|
+
export function description(tokens: ColorTokens): TextStyle {
|
|
80
|
+
return { fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Body paragraph: text-sm text-foreground leading-relaxed.
|
|
84
|
+
export function body(tokens: ColorTokens): TextStyle {
|
|
85
|
+
return { fontSize: 14, lineHeight: 28, color: tokens.foreground };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Trailing meta text: shrink-0 text-xs text-muted-foreground.
|
|
89
|
+
export function meta(tokens: ColorTokens): TextStyle {
|
|
90
|
+
return { flexShrink: 0, fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Trailing action wrapper: shrink-0.
|
|
94
|
+
export const actionBox: ViewStyle = { flexShrink: 0 };
|