@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,237 @@
|
|
|
1
|
+
# Badges
|
|
2
|
+
|
|
3
|
+
Two families on one Badge component, picked by boolean props. The metadata badge is a rectangular pill for labels like schema, role, or tag (tones: default, secondary, outline, destructive; add `mono` for token names). The status badge (`status`) is a rounded pill with a leading dot for live state like active, pending, or failed (tones: success, warning, error, info, neutral).
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Badge secondary>admin</Badge>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Variants
|
|
12
|
+
|
|
13
|
+
### Type - status
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<Badge status success>admin</Badge>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Type - identity
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 8 }}>
|
|
23
|
+
<Text style={{ fontSize: 15, fontWeight: "600", color: tokens.foreground }}>Rachel Chen</Text>
|
|
24
|
+
<Badge status success>active</Badge>
|
|
25
|
+
<Badge status info>Verified</Badge>
|
|
26
|
+
<Badge secondary>employee</Badge>
|
|
27
|
+
</View>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Type - grants
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 4 }}>
|
|
34
|
+
<Badge secondary mono>authorization_code</Badge>
|
|
35
|
+
<Badge secondary mono>refresh_token</Badge>
|
|
36
|
+
<Badge secondary mono>client_credentials</Badge>
|
|
37
|
+
</View>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Badge variant - default
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<Badge default>admin</Badge>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Badge variant - outline
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<Badge outline>admin</Badge>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Badge variant - destructive
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
<Badge destructive>admin</Badge>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Mono (token / event names)
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<Badge secondary mono>admin</Badge>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Do & Don't
|
|
65
|
+
|
|
66
|
+
### Metadata badge
|
|
67
|
+
|
|
68
|
+
**Do** — Neutral tags for metadata; reserve color and the status-badge dot for live state.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
72
|
+
<Badge secondary>employee</Badge>
|
|
73
|
+
<Badge secondary>engineering</Badge>
|
|
74
|
+
<Badge secondary>remote</Badge>
|
|
75
|
+
<Badge status success>active</Badge>
|
|
76
|
+
</View>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Don't** — Borrowing status colors for plain metadata reads as severity that isn't there; a red tag looks like an error.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
83
|
+
<Badge default>employee</Badge>
|
|
84
|
+
<Badge destructive>engineering</Badge>
|
|
85
|
+
<Badge default>remote</Badge>
|
|
86
|
+
<Badge destructive>active</Badge>
|
|
87
|
+
</View>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Status badge
|
|
91
|
+
|
|
92
|
+
**Do** — Always pair the dot with a word: active, pending, failed.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<Badge status error>Failed</Badge>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Don't** — A bare colored dot isn't a label and fails for color-blind users.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
<Badge status error />
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Identity row
|
|
105
|
+
|
|
106
|
+
**Do** — Show only the one or two badges relevant to this view.
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 8 }}>
|
|
110
|
+
<Text style={{ fontSize: 15, fontWeight: "600", color: tokens.foreground }}>Rachel Chen</Text>
|
|
111
|
+
<Badge status success>active</Badge>
|
|
112
|
+
<Badge secondary>employee</Badge>
|
|
113
|
+
</View>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Don't** — A wall of badges after a name buries the one that matters.
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 8 }}>
|
|
120
|
+
<Text style={{ fontSize: 15, fontWeight: "600", color: tokens.foreground }}>Rachel Chen</Text>
|
|
121
|
+
<Badge status success>active</Badge>
|
|
122
|
+
<Badge status info>Verified</Badge>
|
|
123
|
+
<Badge secondary>employee</Badge>
|
|
124
|
+
<Badge secondary>engineering</Badge>
|
|
125
|
+
<Badge secondary>remote</Badge>
|
|
126
|
+
<Badge secondary>admin</Badge>
|
|
127
|
+
</View>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Token / code badge
|
|
131
|
+
|
|
132
|
+
**Do** — Use the mono variant for tokens, scopes, and event names.
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 4 }}>
|
|
136
|
+
<Badge secondary mono>authorization_code</Badge>
|
|
137
|
+
<Badge secondary mono>refresh_token</Badge>
|
|
138
|
+
<Badge secondary mono>client_credentials</Badge>
|
|
139
|
+
</View>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Don't** — Proportional type makes identifiers hard to scan and compare.
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 4 }}>
|
|
146
|
+
<Badge secondary>authorization_code</Badge>
|
|
147
|
+
<Badge secondary>refresh_token</Badge>
|
|
148
|
+
<Badge secondary>client_credentials</Badge>
|
|
149
|
+
</View>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Default variant
|
|
153
|
+
|
|
154
|
+
**Do** — Reserve the default fill for the single tag you want noticed first; keep the rest secondary.
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
158
|
+
<Badge default>admin</Badge>
|
|
159
|
+
<Badge secondary>engineering</Badge>
|
|
160
|
+
<Badge secondary>remote</Badge>
|
|
161
|
+
</View>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Don't** — The solid primary fill is the loudest badge; using it for every tag makes the whole row shout and nothing leads.
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
168
|
+
<Badge default>employee</Badge>
|
|
169
|
+
<Badge default>engineering</Badge>
|
|
170
|
+
<Badge default>remote</Badge>
|
|
171
|
+
<Badge default>admin</Badge>
|
|
172
|
+
</View>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Secondary variant
|
|
176
|
+
|
|
177
|
+
**Do** — Keep secondary for static metadata (role, team) and switch to the status-badge for anything live.
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
181
|
+
<Badge secondary>employee</Badge>
|
|
182
|
+
<Badge secondary>engineering</Badge>
|
|
183
|
+
<Badge status success>active</Badge>
|
|
184
|
+
</View>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Don't** — A muted gray pill reads as static metadata, so live state shown as a secondary badge looks inert and goes unnoticed.
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
191
|
+
<Badge secondary>active</Badge>
|
|
192
|
+
<Badge secondary>pending</Badge>
|
|
193
|
+
<Badge secondary>failed</Badge>
|
|
194
|
+
</View>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Outline variant
|
|
198
|
+
|
|
199
|
+
**Do** — Use outline on a plain surface where the quiet border has contrast, for low-priority secondary tags.
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6, borderRadius: 6, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, padding: 12 }}>
|
|
203
|
+
<Badge outline>draft</Badge>
|
|
204
|
+
<Badge outline>internal</Badge>
|
|
205
|
+
</View>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Don't** — The thin border is the whole badge; on a colored or busy surface it disappears and the label floats unboxed.
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6, borderRadius: 6, backgroundColor: tokens.primary, padding: 12 }}>
|
|
212
|
+
<Badge outline>draft</Badge>
|
|
213
|
+
<Badge outline>internal</Badge>
|
|
214
|
+
</View>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Destructive variant
|
|
218
|
+
|
|
219
|
+
**Do** — Reserve destructive for genuinely destructive or error semantics like revoked or banned.
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
223
|
+
<Badge destructive>Revoked</Badge>
|
|
224
|
+
<Badge destructive>Banned</Badge>
|
|
225
|
+
<Badge secondary>marketing</Badge>
|
|
226
|
+
</View>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Don't** — Solid red signals error or danger, so using it to color-code neutral categories raises a false alarm.
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
233
|
+
<Badge destructive>marketing</Badge>
|
|
234
|
+
<Badge destructive>finance</Badge>
|
|
235
|
+
<Badge destructive>legal</Badge>
|
|
236
|
+
</View>
|
|
237
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type ViewStyle, type TextStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens, palette } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located Badge styles. The metadata badge uses semantic tokens; the status
|
|
5
|
+
// badge uses the Tailwind palette (a soft 50/200/700 surface in light, a
|
|
6
|
+
// 950/800/400 surface in dark) with a saturated 500 dot, and neutral stays on
|
|
7
|
+
// the semantic muted token.
|
|
8
|
+
|
|
9
|
+
export type Tone = "default" | "secondary" | "outline" | "destructive";
|
|
10
|
+
export type Status = "success" | "warning" | "error" | "info" | "neutral";
|
|
11
|
+
|
|
12
|
+
export const metaBase: ViewStyle = {
|
|
13
|
+
flexDirection: "row",
|
|
14
|
+
alignItems: "center",
|
|
15
|
+
alignSelf: "flex-start",
|
|
16
|
+
borderRadius: 6,
|
|
17
|
+
borderWidth: 1,
|
|
18
|
+
paddingHorizontal: 8,
|
|
19
|
+
paddingVertical: 2,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const statusBase: ViewStyle = {
|
|
23
|
+
flexDirection: "row",
|
|
24
|
+
alignItems: "center",
|
|
25
|
+
alignSelf: "flex-start",
|
|
26
|
+
gap: 6,
|
|
27
|
+
borderRadius: 9999,
|
|
28
|
+
borderWidth: 1,
|
|
29
|
+
paddingHorizontal: 8,
|
|
30
|
+
paddingVertical: 2,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const labelType: TextStyle = { fontSize: 12, lineHeight: 16, fontWeight: "500" };
|
|
34
|
+
|
|
35
|
+
export function metaContainer(tokens: ColorTokens, tone: Tone): ViewStyle {
|
|
36
|
+
switch (tone) {
|
|
37
|
+
case "default": return { borderColor: "transparent", backgroundColor: tokens.primary };
|
|
38
|
+
case "secondary": return { borderColor: "transparent", backgroundColor: tokens.secondary };
|
|
39
|
+
case "outline": return { borderColor: tokens.border, backgroundColor: "transparent" };
|
|
40
|
+
case "destructive": return { borderColor: "transparent", backgroundColor: tokens.destructive };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function metaLabel(tokens: ColorTokens, tone: Tone): TextStyle {
|
|
45
|
+
switch (tone) {
|
|
46
|
+
case "default": return { color: tokens["primary-foreground"] };
|
|
47
|
+
case "secondary": return { color: tokens["secondary-foreground"] };
|
|
48
|
+
case "outline": return { color: tokens.foreground };
|
|
49
|
+
case "destructive": return { color: tokens["destructive-foreground"] };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The palette hue per status tone (neutral is handled on the semantic tokens).
|
|
54
|
+
const STATUS_HUE: Record<Exclude<Status, "neutral">, string> = {
|
|
55
|
+
success: "green",
|
|
56
|
+
warning: "amber",
|
|
57
|
+
error: "red",
|
|
58
|
+
info: "blue",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export function statusContainer(tokens: ColorTokens, dark: boolean, status: Status): ViewStyle {
|
|
62
|
+
if (status === "neutral") return { borderColor: tokens.border, backgroundColor: tokens.muted };
|
|
63
|
+
const hue = STATUS_HUE[status];
|
|
64
|
+
return dark
|
|
65
|
+
? { borderColor: palette[`${hue}-800`], backgroundColor: palette[`${hue}-950`] }
|
|
66
|
+
: { borderColor: palette[`${hue}-200`], backgroundColor: palette[`${hue}-50`] };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function statusLabel(tokens: ColorTokens, dark: boolean, status: Status): TextStyle {
|
|
70
|
+
if (status === "neutral") return { color: tokens["muted-foreground"] };
|
|
71
|
+
const hue = STATUS_HUE[status];
|
|
72
|
+
return { color: dark ? palette[`${hue}-400`] : palette[`${hue}-700`] };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function statusDot(tokens: ColorTokens, status: Status): ViewStyle {
|
|
76
|
+
const base: ViewStyle = { height: 6, width: 6, borderRadius: 9999 };
|
|
77
|
+
if (status === "neutral") return { ...base, backgroundColor: tokens["muted-foreground"] };
|
|
78
|
+
return { ...base, backgroundColor: palette[`${STATUS_HUE[status]}-500`] };
|
|
79
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { View, Text, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
|
|
3
|
+
import * as s from "./badge.styles.js";
|
|
4
|
+
import { type Tone, type Status } from "./badge.styles.js";
|
|
5
|
+
|
|
6
|
+
// Two families of badge.
|
|
7
|
+
//
|
|
8
|
+
// 1. The metadata badge: a rectangular pill (rounded-md) for static labels like
|
|
9
|
+
// schema, role, or tag. Configured by a tone axis (default / secondary /
|
|
10
|
+
// outline / destructive) plus a `mono` modifier for token / event names.
|
|
11
|
+
// 2. The status badge (`status`): a fully rounded pill carrying a leading dot,
|
|
12
|
+
// for live state like active / pending / failed. Configured by a status-tone
|
|
13
|
+
// axis (success / warning / error / info / neutral).
|
|
14
|
+
//
|
|
15
|
+
// Boolean-prop API: one boolean per option, grouped by axis, first-match
|
|
16
|
+
// precedence within an axis (mirrors Button's intentOf). The `status` boolean
|
|
17
|
+
// switches families; the status-tone booleans only apply in that family.
|
|
18
|
+
|
|
19
|
+
export interface BadgeProps {
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
// Family: metadata badge (default) vs. status badge (with a dot).
|
|
22
|
+
status?: boolean;
|
|
23
|
+
// Metadata tone (pick one; default is the solid primary fill).
|
|
24
|
+
default?: boolean;
|
|
25
|
+
secondary?: boolean;
|
|
26
|
+
outline?: boolean;
|
|
27
|
+
destructive?: boolean;
|
|
28
|
+
// Metadata modifier: monospace face for tokens, scopes, event names.
|
|
29
|
+
mono?: boolean;
|
|
30
|
+
// Status tone (pick one; only applies when `status`).
|
|
31
|
+
success?: boolean;
|
|
32
|
+
warning?: boolean;
|
|
33
|
+
error?: boolean;
|
|
34
|
+
info?: boolean;
|
|
35
|
+
neutral?: boolean;
|
|
36
|
+
/** Escape hatch for layout/positioning composition. */
|
|
37
|
+
style?: StyleProp<ViewStyle>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Tone precedence when more than one is passed: first match wins.
|
|
41
|
+
function toneOf(p: BadgeProps): Tone {
|
|
42
|
+
if (p.default) return "default";
|
|
43
|
+
if (p.destructive) return "destructive";
|
|
44
|
+
if (p.secondary) return "secondary";
|
|
45
|
+
if (p.outline) return "outline";
|
|
46
|
+
return "secondary";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function statusOf(p: BadgeProps): Status {
|
|
50
|
+
if (p.success) return "success";
|
|
51
|
+
if (p.error) return "error";
|
|
52
|
+
if (p.warning) return "warning";
|
|
53
|
+
if (p.info) return "info";
|
|
54
|
+
if (p.neutral) return "neutral";
|
|
55
|
+
return "neutral";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function Badge(props: BadgeProps) {
|
|
59
|
+
const { children, mono, style } = props;
|
|
60
|
+
const { tokens, dark } = useTheme();
|
|
61
|
+
|
|
62
|
+
if (props.status) {
|
|
63
|
+
const tone = statusOf(props);
|
|
64
|
+
return (
|
|
65
|
+
<View style={[s.statusBase, s.statusContainer(tokens, dark, tone), style]}>
|
|
66
|
+
<View style={s.statusDot(tokens, tone)} />
|
|
67
|
+
{children != null ? (
|
|
68
|
+
<Text style={[s.labelType, s.statusLabel(tokens, dark, tone)]}>{children}</Text>
|
|
69
|
+
) : null}
|
|
70
|
+
</View>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const tone = toneOf(props);
|
|
75
|
+
// The mono modifier asks for a monospace face; RN has no font-family utility,
|
|
76
|
+
// so request the cross-platform monospace alias via inline style.
|
|
77
|
+
const monoStyle = mono ? { fontFamily: "monospace" as const } : null;
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<View style={[s.metaBase, s.metaContainer(tokens, tone), style]}>
|
|
81
|
+
{children != null ? (
|
|
82
|
+
<Text style={[s.labelType, s.metaLabel(tokens, tone), monoStyle]}>{children}</Text>
|
|
83
|
+
) : null}
|
|
84
|
+
</View>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# Breadcrumbs
|
|
2
|
+
|
|
3
|
+
Hierarchical navigation showing where you are.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Breadcrumb
|
|
9
|
+
items={["Projects", "Identity Platform", "Settings", "Profile"]}
|
|
10
|
+
chevron
|
|
11
|
+
/>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Variants
|
|
15
|
+
|
|
16
|
+
### Separator - slash
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<Breadcrumb
|
|
20
|
+
items={["Projects", "Identity Platform", "Settings", "Profile"]}
|
|
21
|
+
slash
|
|
22
|
+
/>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Separator - dot
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
<Breadcrumb items={["Projects", "Identity Platform", "Settings", "Profile"]} dot />
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Leading home icon
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<Breadcrumb
|
|
35
|
+
items={["Projects", "Identity Platform", "Settings", "Profile"]}
|
|
36
|
+
chevron
|
|
37
|
+
homeIcon
|
|
38
|
+
/>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### In a page header
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "flex-start", justifyContent: "space-between", gap: 16 }}>
|
|
45
|
+
<View>
|
|
46
|
+
<View style={{ marginBottom: 8 }}>
|
|
47
|
+
<Breadcrumb items={["Users", "Rachel Chen"]} />
|
|
48
|
+
</View>
|
|
49
|
+
<Text style={{ fontSize: 24, lineHeight: 32, fontWeight: "600", letterSpacing: -0.4, color: tokens.foreground }}>Rachel Chen</Text>
|
|
50
|
+
</View>
|
|
51
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
|
52
|
+
<Button outline small>Edit</Button>
|
|
53
|
+
<Button primary small>Save</Button>
|
|
54
|
+
</View>
|
|
55
|
+
</View>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Do & Don't
|
|
59
|
+
|
|
60
|
+
### Current page
|
|
61
|
+
|
|
62
|
+
**Do** — Ancestors are links; the page you're on is plain text at the end of the trail.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<Breadcrumb items={["Projects", "Identity Platform", "Settings"]} />
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Don't** — Linking the current page implies there's somewhere to go; it's a dead link to itself.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
72
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
73
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Projects</Text>
|
|
74
|
+
</Pressable>
|
|
75
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>/</Text>
|
|
76
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
77
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Identity Platform</Text>
|
|
78
|
+
</Pressable>
|
|
79
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>/</Text>
|
|
80
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
81
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Settings</Text>
|
|
82
|
+
</Pressable>
|
|
83
|
+
</View>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Deep paths
|
|
87
|
+
|
|
88
|
+
**Do** — Collapse the middle to an ellipsis; keep the root and the last couple of levels.
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
92
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
93
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Projects</Text>
|
|
94
|
+
</Pressable>
|
|
95
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>›</Text>
|
|
96
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"], paddingHorizontal: 4 }}>…</Text>
|
|
97
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>›</Text>
|
|
98
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
99
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Avatar</Text>
|
|
100
|
+
</Pressable>
|
|
101
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>›</Text>
|
|
102
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Edit</Text>
|
|
103
|
+
</View>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Don't** — A fully expanded deep path wraps and competes with the page.
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
<Breadcrumb items={[
|
|
110
|
+
"Projects",
|
|
111
|
+
"Identity Platform",
|
|
112
|
+
"Settings",
|
|
113
|
+
"Profile",
|
|
114
|
+
"Avatar",
|
|
115
|
+
"Edit"
|
|
116
|
+
]} />
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Separator
|
|
120
|
+
|
|
121
|
+
**Do** — Pick one separator and use it the whole way.
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<Breadcrumb items={["Projects", "Identity Platform", "Settings"]} />
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Don't** — Mixing separators in one trail looks broken.
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
131
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
132
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Projects</Text>
|
|
133
|
+
</Pressable>
|
|
134
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>/</Text>
|
|
135
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
136
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Identity Platform</Text>
|
|
137
|
+
</Pressable>
|
|
138
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>›</Text>
|
|
139
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Settings</Text>
|
|
140
|
+
</View>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Home root
|
|
144
|
+
|
|
145
|
+
**Do** — Give the home icon an aria-label so the root is announced.
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
<Breadcrumb homeIcon items={["Settings"]} />
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Don't** — An icon-only root with no label is unclear to screen readers.
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
155
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
156
|
+
<Icon home muted size={14} />
|
|
157
|
+
</Pressable>
|
|
158
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>/</Text>
|
|
159
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Settings</Text>
|
|
160
|
+
</View>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Chevron
|
|
164
|
+
|
|
165
|
+
**Do** — Point the chevron in the reading direction (right in LTR) so each one means 'drill into the next level'.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<Breadcrumb chevron items={["Projects", "Identity Platform", "Settings"]} />
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Don't** — A down (or back) chevron reads as a dropdown or a back affordance, not progression down the hierarchy.
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
175
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
176
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Projects</Text>
|
|
177
|
+
</Pressable>
|
|
178
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>⌄</Text>
|
|
179
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
180
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Identity Platform</Text>
|
|
181
|
+
</Pressable>
|
|
182
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>⌄</Text>
|
|
183
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Settings</Text>
|
|
184
|
+
</View>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Slash
|
|
188
|
+
|
|
189
|
+
**Do** — Keep the slash muted and lighter than the text so it reads as a quiet path divider.
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
<Breadcrumb slash items={["Projects", "Identity Platform", "Settings"]} />
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Don't** — A full-weight, foreground slash competes with the labels and can read as part of a link.
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
199
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
200
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Projects</Text>
|
|
201
|
+
</Pressable>
|
|
202
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground, paddingHorizontal: 4 }}>/</Text>
|
|
203
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
204
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Identity Platform</Text>
|
|
205
|
+
</Pressable>
|
|
206
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground, paddingHorizontal: 4 }}>/</Text>
|
|
207
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Settings</Text>
|
|
208
|
+
</View>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Dot
|
|
212
|
+
|
|
213
|
+
**Do** — Use a centered middot (·) so the dot sits between the crumbs and clearly divides them.
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
<Breadcrumb dot items={["Projects", "Identity Platform", "Settings"]} />
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Don't** — A baseline period looks like a typo or end-of-sentence, not a separator between crumbs.
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 6 }}>
|
|
223
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
224
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Projects</Text>
|
|
225
|
+
</Pressable>
|
|
226
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>.</Text>
|
|
227
|
+
<Pressable accessibilityRole="link" style={({ pressed }) => (pressed ? { opacity: 0.7 } : null)}>
|
|
228
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Identity Platform</Text>
|
|
229
|
+
</Pressable>
|
|
230
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) }}>.</Text>
|
|
231
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Settings</Text>
|
|
232
|
+
</View>
|
|
233
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type ViewStyle, type TextStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens, alpha } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located Breadcrumb styles. Layout-only fragments are static objects; the
|
|
5
|
+
// label/divider colors read the active tokens (so the trail follows light/dark
|
|
6
|
+
// and the glass surface). The press-dim for links is applied by the component's
|
|
7
|
+
// Pressable, matching the old `active:opacity-70`.
|
|
8
|
+
|
|
9
|
+
// The nav row: wrapping horizontal trail, centered, with a tight gap (gap-1.5).
|
|
10
|
+
export const nav: ViewStyle = {
|
|
11
|
+
flexDirection: "row",
|
|
12
|
+
flexWrap: "wrap",
|
|
13
|
+
alignItems: "center",
|
|
14
|
+
gap: 6,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// A crumb cell (and the homeIcon cell): the icon/label sits next to its divider.
|
|
18
|
+
export const crumb: ViewStyle = {
|
|
19
|
+
flexDirection: "row",
|
|
20
|
+
alignItems: "center",
|
|
21
|
+
gap: 6,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// A muted ancestor link (text-sm text-muted-foreground). Press feedback (the old
|
|
25
|
+
// `active:opacity-70`) is applied by the component's Pressable.
|
|
26
|
+
export function link(tokens: ColorTokens): TextStyle {
|
|
27
|
+
return { fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// The current page: emphasized foreground text, not a link
|
|
31
|
+
// (text-sm font-medium text-foreground).
|
|
32
|
+
export function current(tokens: ColorTokens): TextStyle {
|
|
33
|
+
return { fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The divider glyph: quieter than the labels so it reads as a path divider
|
|
37
|
+
// (text-sm text-muted-foreground/60).
|
|
38
|
+
export function separator(tokens: ColorTokens): TextStyle {
|
|
39
|
+
return { fontSize: 14, lineHeight: 20, color: alpha(tokens["muted-foreground"], 0.6) };
|
|
40
|
+
}
|