@oxyhq/bloom 1.0.0 → 1.0.2

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.
Files changed (218) hide show
  1. package/README.md +16 -4
  2. package/docs/accordion.mdx +69 -0
  3. package/docs/activity-heatmap.mdx +71 -0
  4. package/docs/admonition.mdx +77 -0
  5. package/docs/alert-dialog.mdx +75 -0
  6. package/docs/alert.mdx +93 -0
  7. package/docs/animated-check.mdx +53 -0
  8. package/docs/aspect-ratio.mdx +45 -0
  9. package/docs/avatar-group.mdx +91 -0
  10. package/docs/avatar.mdx +126 -0
  11. package/docs/badge.mdx +74 -0
  12. package/docs/benefit-list.mdx +56 -0
  13. package/docs/bottom-sheet.mdx +103 -0
  14. package/docs/button.mdx +218 -0
  15. package/docs/card.mdx +106 -0
  16. package/docs/checkbox.mdx +59 -0
  17. package/docs/chip.mdx +53 -0
  18. package/docs/code.mdx +50 -0
  19. package/docs/combobox.mdx +64 -0
  20. package/docs/command.mdx +85 -0
  21. package/docs/composition-bar.mdx +63 -0
  22. package/docs/connection-dots.mdx +50 -0
  23. package/docs/connection-status.mdx +56 -0
  24. package/docs/content-panel.mdx +62 -0
  25. package/docs/context-menu.mdx +65 -0
  26. package/docs/design-tokens.mdx +254 -0
  27. package/docs/dialog.mdx +277 -0
  28. package/docs/divider.mdx +56 -0
  29. package/docs/dot-grid-meter.mdx +66 -0
  30. package/docs/dropdown-menu.mdx +161 -0
  31. package/docs/error-boundary.mdx +72 -0
  32. package/docs/fab.mdx +67 -0
  33. package/docs/field.mdx +70 -0
  34. package/docs/fill.mdx +58 -0
  35. package/docs/fonts.mdx +144 -0
  36. package/docs/frosted-icon-button.mdx +60 -0
  37. package/docs/getting-started.mdx +75 -0
  38. package/docs/grid.mdx +64 -0
  39. package/docs/hooks.mdx +81 -0
  40. package/docs/icon-circle.mdx +66 -0
  41. package/docs/icons.mdx +69 -0
  42. package/docs/image-aspect-ratio-cache.mdx +63 -0
  43. package/docs/image-resolver.mdx +66 -0
  44. package/docs/index.mdx +44 -0
  45. package/docs/input-group.mdx +58 -0
  46. package/docs/item.mdx +69 -0
  47. package/docs/kbd.mdx +57 -0
  48. package/docs/label.mdx +50 -0
  49. package/docs/link-preview.mdx +66 -0
  50. package/docs/list.mdx +72 -0
  51. package/docs/loading.mdx +129 -0
  52. package/docs/media-inset-border.mdx +52 -0
  53. package/docs/menubar.mdx +86 -0
  54. package/docs/migrating-to-1.0.mdx +434 -0
  55. package/docs/motion.mdx +83 -0
  56. package/docs/overlay.mdx +75 -0
  57. package/docs/popover.mdx +73 -0
  58. package/docs/portal.mdx +63 -0
  59. package/docs/pressable-scale.mdx +51 -0
  60. package/docs/profile-card.mdx +66 -0
  61. package/docs/progressive-blur.mdx +54 -0
  62. package/docs/prompt-input.mdx +183 -0
  63. package/docs/provider.mdx +64 -0
  64. package/docs/radio-indicator.mdx +60 -0
  65. package/docs/radio.mdx +65 -0
  66. package/docs/scroll.mdx +66 -0
  67. package/docs/search.mdx +67 -0
  68. package/docs/segmented-control.mdx +76 -0
  69. package/docs/select.mdx +93 -0
  70. package/docs/settings-list.mdx +186 -0
  71. package/docs/skeleton.mdx +66 -0
  72. package/docs/slider.mdx +73 -0
  73. package/docs/stat-bar.mdx +59 -0
  74. package/docs/stat-widgets.mdx +33 -0
  75. package/docs/styles.mdx +61 -0
  76. package/docs/subtle-hover.mdx +53 -0
  77. package/docs/surfaces.mdx +72 -0
  78. package/docs/switch.mdx +90 -0
  79. package/docs/tab-bar.mdx +386 -0
  80. package/docs/tabs.mdx +75 -0
  81. package/docs/text-field.mdx +187 -0
  82. package/docs/theme.mdx +67 -0
  83. package/docs/toast.mdx +285 -0
  84. package/docs/tooltip.mdx +67 -0
  85. package/docs/typography.mdx +88 -0
  86. package/docs/user-hover-card.mdx +140 -0
  87. package/docs/zoomable-image-gallery.mdx +66 -0
  88. package/lib/commonjs/design-tokens/color-roles.js +21 -1
  89. package/lib/commonjs/design-tokens/color-roles.js.map +1 -1
  90. package/lib/commonjs/design-tokens/index.js +6 -0
  91. package/lib/commonjs/design-tokens/index.js.map +1 -1
  92. package/lib/commonjs/design-tokens/tailwind-preset.js +3 -1
  93. package/lib/commonjs/design-tokens/tailwind-preset.js.map +1 -1
  94. package/lib/commonjs/design-tokens/theme-css.js +1 -0
  95. package/lib/commonjs/design-tokens/theme-css.js.map +1 -1
  96. package/lib/commonjs/design-tokens/theme.css +3 -0
  97. package/lib/commonjs/design-tokens/tokens-json.js +13 -4
  98. package/lib/commonjs/design-tokens/tokens-json.js.map +1 -1
  99. package/lib/commonjs/design-tokens/tokens.json +6227 -265
  100. package/lib/commonjs/index.js +7 -0
  101. package/lib/commonjs/index.js.map +1 -1
  102. package/lib/commonjs/index.web.js +7 -0
  103. package/lib/commonjs/index.web.js.map +1 -1
  104. package/lib/commonjs/theme/ambient-store.js +16 -1
  105. package/lib/commonjs/theme/ambient-store.js.map +1 -1
  106. package/lib/commonjs/theme/color-policy.js +30 -33
  107. package/lib/commonjs/theme/color-policy.js.map +1 -1
  108. package/lib/commonjs/theme/color-presets.js +413 -166
  109. package/lib/commonjs/theme/color-presets.js.map +1 -1
  110. package/lib/commonjs/theme/color-scope/seed-scope.js +1 -0
  111. package/lib/commonjs/theme/color-scope/seed-scope.js.map +1 -1
  112. package/lib/commonjs/theme/glass-colors.js +18 -21
  113. package/lib/commonjs/theme/glass-colors.js.map +1 -1
  114. package/lib/commonjs/theme/index.js +30 -0
  115. package/lib/commonjs/theme/index.js.map +1 -1
  116. package/lib/commonjs/theme/index.web.js +30 -0
  117. package/lib/commonjs/theme/index.web.js.map +1 -1
  118. package/lib/module/design-tokens/color-roles.js +20 -0
  119. package/lib/module/design-tokens/color-roles.js.map +1 -1
  120. package/lib/module/design-tokens/index.js +2 -2
  121. package/lib/module/design-tokens/index.js.map +1 -1
  122. package/lib/module/design-tokens/tailwind-preset.js +4 -2
  123. package/lib/module/design-tokens/tailwind-preset.js.map +1 -1
  124. package/lib/module/design-tokens/theme-css.js +2 -1
  125. package/lib/module/design-tokens/theme-css.js.map +1 -1
  126. package/lib/module/design-tokens/theme.css +3 -0
  127. package/lib/module/design-tokens/tokens-json.js +14 -5
  128. package/lib/module/design-tokens/tokens-json.js.map +1 -1
  129. package/lib/module/design-tokens/tokens.json +6227 -265
  130. package/lib/module/index.js +1 -1
  131. package/lib/module/index.js.map +1 -1
  132. package/lib/module/index.web.js +1 -1
  133. package/lib/module/index.web.js.map +1 -1
  134. package/lib/module/theme/ambient-store.js +16 -1
  135. package/lib/module/theme/ambient-store.js.map +1 -1
  136. package/lib/module/theme/color-policy.js +30 -33
  137. package/lib/module/theme/color-policy.js.map +1 -1
  138. package/lib/module/theme/color-presets.js +412 -165
  139. package/lib/module/theme/color-presets.js.map +1 -1
  140. package/lib/module/theme/color-scope/seed-scope.js +2 -1
  141. package/lib/module/theme/color-scope/seed-scope.js.map +1 -1
  142. package/lib/module/theme/glass-colors.js +18 -21
  143. package/lib/module/theme/glass-colors.js.map +1 -1
  144. package/lib/module/theme/index.js +1 -1
  145. package/lib/module/theme/index.js.map +1 -1
  146. package/lib/module/theme/index.web.js +1 -1
  147. package/lib/module/theme/index.web.js.map +1 -1
  148. package/lib/typescript/commonjs/design-tokens/color-roles.d.ts +20 -0
  149. package/lib/typescript/commonjs/design-tokens/color-roles.d.ts.map +1 -1
  150. package/lib/typescript/commonjs/design-tokens/index.d.ts +3 -3
  151. package/lib/typescript/commonjs/design-tokens/index.d.ts.map +1 -1
  152. package/lib/typescript/commonjs/design-tokens/tailwind-preset.d.ts.map +1 -1
  153. package/lib/typescript/commonjs/design-tokens/theme-css.d.ts.map +1 -1
  154. package/lib/typescript/commonjs/design-tokens/tokens-json.d.ts +12 -3
  155. package/lib/typescript/commonjs/design-tokens/tokens-json.d.ts.map +1 -1
  156. package/lib/typescript/commonjs/index.d.ts +2 -2
  157. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  158. package/lib/typescript/commonjs/index.web.d.ts +2 -2
  159. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  160. package/lib/typescript/commonjs/theme/ambient-store.d.ts.map +1 -1
  161. package/lib/typescript/commonjs/theme/color-policy.d.ts.map +1 -1
  162. package/lib/typescript/commonjs/theme/color-presets.d.ts +386 -62
  163. package/lib/typescript/commonjs/theme/color-presets.d.ts.map +1 -1
  164. package/lib/typescript/commonjs/theme/color-scope/seed-scope.d.ts.map +1 -1
  165. package/lib/typescript/commonjs/theme/glass-colors.d.ts +18 -21
  166. package/lib/typescript/commonjs/theme/glass-colors.d.ts.map +1 -1
  167. package/lib/typescript/commonjs/theme/index.d.ts +2 -2
  168. package/lib/typescript/commonjs/theme/index.d.ts.map +1 -1
  169. package/lib/typescript/commonjs/theme/index.web.d.ts +2 -2
  170. package/lib/typescript/commonjs/theme/index.web.d.ts.map +1 -1
  171. package/lib/typescript/module/design-tokens/color-roles.d.ts +20 -0
  172. package/lib/typescript/module/design-tokens/color-roles.d.ts.map +1 -1
  173. package/lib/typescript/module/design-tokens/index.d.ts +3 -3
  174. package/lib/typescript/module/design-tokens/index.d.ts.map +1 -1
  175. package/lib/typescript/module/design-tokens/tailwind-preset.d.ts.map +1 -1
  176. package/lib/typescript/module/design-tokens/theme-css.d.ts.map +1 -1
  177. package/lib/typescript/module/design-tokens/tokens-json.d.ts +12 -3
  178. package/lib/typescript/module/design-tokens/tokens-json.d.ts.map +1 -1
  179. package/lib/typescript/module/index.d.ts +2 -2
  180. package/lib/typescript/module/index.d.ts.map +1 -1
  181. package/lib/typescript/module/index.web.d.ts +2 -2
  182. package/lib/typescript/module/index.web.d.ts.map +1 -1
  183. package/lib/typescript/module/theme/ambient-store.d.ts.map +1 -1
  184. package/lib/typescript/module/theme/color-policy.d.ts.map +1 -1
  185. package/lib/typescript/module/theme/color-presets.d.ts +386 -62
  186. package/lib/typescript/module/theme/color-presets.d.ts.map +1 -1
  187. package/lib/typescript/module/theme/color-scope/seed-scope.d.ts.map +1 -1
  188. package/lib/typescript/module/theme/glass-colors.d.ts +18 -21
  189. package/lib/typescript/module/theme/glass-colors.d.ts.map +1 -1
  190. package/lib/typescript/module/theme/index.d.ts +2 -2
  191. package/lib/typescript/module/theme/index.d.ts.map +1 -1
  192. package/lib/typescript/module/theme/index.web.d.ts +2 -2
  193. package/lib/typescript/module/theme/index.web.d.ts.map +1 -1
  194. package/package.json +5 -2
  195. package/src/__tests__/support/collision-fixture-barrel.ts +20 -0
  196. package/src/__tests__/support/constructed-style-sheets.ts +68 -0
  197. package/src/__tests__/support/press-host.ts +30 -0
  198. package/src/__tests__/support/rendered-style.ts +99 -0
  199. package/src/__tests__/support/unread-hook-fixture.ts +33 -0
  200. package/src/design-tokens/color-roles.ts +21 -0
  201. package/src/design-tokens/index.ts +3 -2
  202. package/src/design-tokens/tailwind-preset.ts +3 -2
  203. package/src/design-tokens/theme-css.ts +2 -1
  204. package/src/design-tokens/theme.css +3 -0
  205. package/src/design-tokens/tokens-json.ts +29 -9
  206. package/src/design-tokens/tokens.json +6227 -265
  207. package/src/glass/GlassSurface.tsx +2 -2
  208. package/src/index.ts +2 -0
  209. package/src/index.web.ts +2 -0
  210. package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +4082 -0
  211. package/src/theme/__tests__/fixtures/color-engine-golden.json +1 -0
  212. package/src/theme/ambient-store.ts +21 -1
  213. package/src/theme/color-policy.ts +33 -38
  214. package/src/theme/color-presets.ts +473 -146
  215. package/src/theme/color-scope/seed-scope.ts +2 -1
  216. package/src/theme/glass-colors.ts +18 -21
  217. package/src/theme/index.ts +16 -1
  218. package/src/theme/index.web.ts +16 -1
@@ -0,0 +1,67 @@
1
+ ---
2
+ title: Search
3
+ description: A pill-shaped search field whose clear button appears only when there is something to clear.
4
+ order: 48
5
+ ---
6
+
7
+ # Search
8
+
9
+ A search input: magnifier, rounded field, and a clear button that appears once
10
+ there is a query.
11
+
12
+ ```tsx
13
+ import { Search } from '@oxyhq/bloom/search';
14
+
15
+ <Search
16
+ value={query}
17
+ onChangeText={setQuery}
18
+ onClearText={() => setQuery('')}
19
+ label="Search people"
20
+ />
21
+ ```
22
+
23
+ ## Which prop decides what
24
+
25
+ | prop | decides |
26
+ | -------------- | ----------------------------------------------------------- |
27
+ | `value` | the query — the field is controlled |
28
+ | `onChangeText` | typing |
29
+ | `onClearText` | the clear button's press |
30
+ | `label` | the accessible name **and** the placeholder; defaults to `Search` |
31
+
32
+ Everything else passes through to the underlying text input.
33
+
34
+ ## `onClearText` reports; it does not clear
35
+
36
+ The field is controlled, so clearing itself would fight the caller's state and
37
+ leave the list filtered by a query the field no longer shows. Handle it:
38
+
39
+ ```tsx
40
+ onClearText={() => { setQuery(''); refetch(); }}
41
+ ```
42
+
43
+ ## The clear button is conditional on purpose
44
+
45
+ It appears only when `value` is non-empty. A clear button on an empty field is
46
+ a dead target that looks live, and a missing one on a full field strands the
47
+ user with no way back to the unfiltered list except selecting and deleting.
48
+
49
+ ## Searching as you type, or on submit
50
+
51
+ `Search` sets `returnKeyType="search"` but does not decide when the search
52
+ runs. Both are legitimate:
53
+
54
+ - **As you type** — cheap, local filtering. Debounce anything that leaves the
55
+ device.
56
+ - **On submit** — expensive or paginated search. Say so, by leaving the results
57
+ alone until the key is pressed rather than showing stale ones.
58
+
59
+ What is not legitimate is issuing a request per keystroke to a server. That is
60
+ the same failure as a [`Slider`](/docs/bloom/main/slider) that saves in
61
+ `onValueChange`.
62
+
63
+ ## Search or Combobox
64
+
65
+ - **Search** — filters something already on the page.
66
+ - **[Combobox](/docs/bloom/main/combobox)** — picks a value into a form, and
67
+ the picked value stays.
@@ -0,0 +1,76 @@
1
+ ---
2
+ title: SegmentedControl
3
+ description: Two controls that look identical and announce differently — `type` is the decision.
4
+ order: 25
5
+ ---
6
+
7
+ # SegmentedControl
8
+
9
+ A row of segments with a sliding indicator. Two to four options, all visible at
10
+ once, one selected.
11
+
12
+ ```tsx
13
+ import {
14
+ SegmentedControl,
15
+ SegmentedControlItem,
16
+ SegmentedControlItemText,
17
+ } from '@oxyhq/bloom/segmented-control';
18
+
19
+ <SegmentedControl label="Appearance" type="radio" value={mode} onChange={setMode}>
20
+ <SegmentedControlItem value="light">
21
+ <SegmentedControlItemText>Light</SegmentedControlItemText>
22
+ </SegmentedControlItem>
23
+ <SegmentedControlItem value="dark">
24
+ <SegmentedControlItemText>Dark</SegmentedControlItemText>
25
+ </SegmentedControlItem>
26
+ <SegmentedControlItem value="system">
27
+ <SegmentedControlItemText>System</SegmentedControlItemText>
28
+ </SegmentedControlItem>
29
+ </SegmentedControl>
30
+ ```
31
+
32
+ ## `type` is an accessibility decision wearing visual clothes
33
+
34
+ | `type` | means | announces as |
35
+ | ------- | ---------------------------------------------- | ------------------------- |
36
+ | `radio` | the segments **pick a value** — a setting | radio group / radio |
37
+ | `tabs` | the segments **switch the panel below** | tab list / tab |
38
+
39
+ The two render identically. That is exactly why the wrong one is dangerous:
40
+ there is nothing on screen to notice, and it is wrong in every screen reader.
41
+
42
+ Ask what happens when the user picks: if something is now *set*, it is `radio`;
43
+ if the same subject is now shown a different way, it is `tabs`.
44
+
45
+ ## The state spelling follows the role
46
+
47
+ ARIA gives a `tab` a **selected** state and a `radio` a **checked** one, so a
48
+ single spelling would be invalid in one of the two modes. `SegmentedControl`
49
+ emits `aria-selected` for tabs and `aria-checked` for radios, and neither uses
50
+ `accessibilityState` — react-native-web drops that entirely, so a control
51
+ relying on it would announce no state at all on web.
52
+
53
+ ## `label` is required
54
+
55
+ It names the group. "Light / Dark / System" without a group name announces as
56
+ three unrelated radios; with `label="Appearance"` it announces as a choice
57
+ about appearance.
58
+
59
+ ## Which prop decides what
60
+
61
+ | prop | decides |
62
+ | ---------- | ---------------------------------------------- |
63
+ | `label` | the accessible name of the whole group |
64
+ | `type` | radio group vs tab list (see above) |
65
+ | `value` | the selected segment — fully controlled |
66
+ | `onChange` | receives the pressed segment's `value` |
67
+ | `size` | `small` (32px) for a toolbar, `large` (40px) |
68
+
69
+ `SegmentedControlItemText` takes no `value` — it reads whether its segment is
70
+ active from context. Passing one is a type error.
71
+
72
+ ## When to reach for something else
73
+
74
+ - More than about four options, or long labels: a [`Select`](/docs/bloom/main/select).
75
+ - A view of the same subject with real panels underneath: [`Tabs`](/docs/bloom/main/tabs).
76
+ - A single on/off: [`Switch`](/docs/bloom/main/switch).
@@ -0,0 +1,93 @@
1
+ ---
2
+ title: Select
3
+ description: A single-choice picker — trigger, value, grouped options.
4
+ order: 15
5
+ ---
6
+
7
+ # Select
8
+
9
+ A single-choice picker. A bottom sheet on native, an anchored dropdown on web. Controlled or uncontrolled.
10
+
11
+ `Select` owns the value, `SelectTrigger` opens it, `SelectContent` renders the choices from an `items` array, and `SelectItem` is one row.
12
+
13
+ ## Basic
14
+
15
+ ```tsx
16
+ import {
17
+ Select,
18
+ SelectContent,
19
+ SelectIcon,
20
+ SelectItem,
21
+ SelectItemIndicator,
22
+ SelectItemText,
23
+ SelectTrigger,
24
+ SelectValue,
25
+ } from '@oxyhq/bloom/select';
26
+
27
+ const [country, setCountry] = useState('us');
28
+
29
+ <Select value={country} onValueChange={setCountry}>
30
+ <SelectTrigger label="Country">
31
+ <SelectValue placeholder="Pick a country" />
32
+ <SelectIcon />
33
+ </SelectTrigger>
34
+
35
+ <SelectContent
36
+ label="Country"
37
+ items={countries}
38
+ renderItem={(item) => (
39
+ <SelectItem value={item.code} label={item.name}>
40
+ <SelectItemIndicator />
41
+ <SelectItemText>{item.name}</SelectItemText>
42
+ </SelectItem>
43
+ )}
44
+ />
45
+ </Select>
46
+ ```
47
+
48
+ `SelectContent` takes the whole `items` array rather than children, so the native fork can hand it to a `FlatList` and the web fork can measure and scroll it. `renderItem` receives `(item, index, selectedValue)`.
49
+
50
+ `SelectTrigger` renders a pressable around its children, or hands the open handler to your own control with `asChild` — the same escape hatch every anchored Bloom family offers:
51
+
52
+ ```tsx
53
+ <SelectTrigger asChild label="Country">
54
+ <MyField>
55
+ <SelectValue placeholder="Pick a country" />
56
+ <SelectIcon />
57
+ </MyField>
58
+ </SelectTrigger>
59
+ ```
60
+
61
+ It used to take a render prop instead, which handed its child a `state` object whose `pressed` was a hardcoded `false` and whose `hovered` the native fork never computed. Press and hover styling on a select trigger was therefore dead while the API read as though it worked. Bring your own control through `asChild` and its own states are real.
62
+
63
+ ## Groups and labels
64
+
65
+ ```tsx
66
+ <SelectGroup>
67
+ <SelectLabel>Europe</SelectLabel>
68
+ <SelectItem value="es" label="Spain">…</SelectItem>
69
+ <SelectItem value="fr" label="France">…</SelectItem>
70
+ </SelectGroup>
71
+ ```
72
+
73
+ `SelectGroup` renders `role="group"` so a screen reader announces the run as one; `SelectLabel` is its heading. Return them from `renderItem` on the first row of each run — `SelectContent`'s list stays flat.
74
+
75
+ ## Long lists
76
+
77
+ The web dropdown grows to `maxHeight` (default 208, shadcn's `max-h-52`) and then scrolls. When it does, `SelectScrollUpButton` and `SelectScrollDownButton` appear at whichever end still has something to show, and disappear at the ends — a scroll affordance that is always visible tells the user a list scrolls when it does not.
78
+
79
+ `SelectContent` renders both, so you get them by default. They are exported for a caller composing their own content, and on NATIVE they render nothing at all: the sheet's list already scrolls under the finger, and a chevron there is a target with no pointer to hit it.
80
+
81
+ ## Selection state
82
+
83
+ Both forks render each option as `role="radio"` with `aria-checked` — the only spelling react-native-web reads, and one React Native folds back into `accessibilityState`. `SelectItemIndicator` draws the mark: the shared `RadioIndicator` on native, a check on web.
84
+
85
+ `SelectSeparator` draws a rule between runs.
86
+
87
+ ## Why a row is not `Item`
88
+
89
+ Every menu row in Bloom is an `Item`. A select row is not, and the reason is its layout.
90
+
91
+ The web option reserves a 30px indicator gutter and paints the indicator into it ABSOLUTELY, so the label's left edge does not move between a selected row and an unselected one; `Item` has an in-flow leading slot and no absolute one. On top of that, `Item` couples `selected` — which select needs purely to emit `aria-checked` — to a selected BACKGROUND, and the web select deliberately paints none, because the check mark is its selection signal.
92
+
93
+ Composing would mean flattening one of those two, and both are visible: the first as a label that jumps sideways on selection, the second as a highlighted row where there was none. So select keeps its own row, and `radio-indicator` stays the one indicator both it and `Radio` draw.
@@ -0,0 +1,186 @@
1
+ ---
2
+ title: SettingsList
3
+ description: iOS-style grouped settings list — items, groups, dividers.
4
+ order: 18
5
+ ---
6
+
7
+ # SettingsList
8
+
9
+ iOS-style grouped settings list. Items, groups (with optional headers and footers), and dividers. Built for the dense, list-heavy screens common in account / preferences UIs.
10
+
11
+ ## Basic
12
+
13
+ ```tsx
14
+ import {
15
+ SettingsListGroup,
16
+ SettingsListItem,
17
+ SettingsListDivider,
18
+ } from '@oxyhq/bloom/settings-list';
19
+
20
+ <SettingsListGroup title="General">
21
+ <SettingsListItem
22
+ icon={<PersonIcon />}
23
+ title="Account"
24
+ value="ada@oxy.so"
25
+ onPress={() => navigate('Account')}
26
+ />
27
+ <SettingsListDivider />
28
+ <SettingsListItem
29
+ icon={<GlobeIcon />}
30
+ title="Language"
31
+ value="English"
32
+ onPress={() => navigate('Language')}
33
+ />
34
+ </SettingsListGroup>
35
+ ```
36
+
37
+ The group renders a rounded card on iOS-style platforms and on web. The first item rounds top corners, the last item rounds bottom corners.
38
+
39
+ ## With description
40
+
41
+ ```tsx
42
+ <SettingsListItem
43
+ icon={<ShieldIcon />}
44
+ title="Two-factor authentication"
45
+ description="Add a second sign-in step with an authenticator app."
46
+ value="On"
47
+ onPress={() => navigate('TwoFactor')}
48
+ />
49
+ ```
50
+
51
+ `description` is multi-line and styled in muted text under the title.
52
+
53
+ ## Toggle row
54
+
55
+ ```tsx
56
+ <SettingsListItem
57
+ icon={<MoonIcon />}
58
+ title="Dark mode"
59
+ rightElement={<Switch value={darkMode} onValueChange={setDarkMode} />}
60
+ />
61
+ ```
62
+
63
+ When `rightElement` is set, the chevron is hidden automatically (you can override with `showChevron`).
64
+
65
+ ## Destructive
66
+
67
+ ```tsx
68
+ <SettingsListGroup title="Danger zone">
69
+ <SettingsListItem
70
+ icon={<TrashIcon />}
71
+ title="Delete account"
72
+ destructive
73
+ onPress={confirmDelete}
74
+ />
75
+ </SettingsListGroup>
76
+ ```
77
+
78
+ `destructive` switches the title to the negative color and uses the destructive icon tint.
79
+
80
+ ## Disabled
81
+
82
+ ```tsx
83
+ <SettingsListItem
84
+ icon={<KeyIcon />}
85
+ title="Reset password"
86
+ description="Coming soon"
87
+ disabled
88
+ />
89
+ ```
90
+
91
+ ## Footer / header text
92
+
93
+ ```tsx
94
+ <SettingsListGroup
95
+ title="Notifications"
96
+ footer="Push notifications require an enabled device permission."
97
+ >
98
+ <SettingsListItem title="Marketing emails" rightElement={<Switch value={mktg} onValueChange={setMktg} />} />
99
+ <SettingsListDivider />
100
+ <SettingsListItem title="Product updates" rightElement={<Switch value={prod} onValueChange={setProd} />} />
101
+ </SettingsListGroup>
102
+ ```
103
+
104
+ `title` is the group header (uppercase muted label on iOS-style platforms); `footer` is a soft explanatory line under the card.
105
+
106
+ ## Components
107
+
108
+ ### `SettingsListGroup`
109
+
110
+ | Prop | Type | Description |
111
+ |------|------|-------------|
112
+ | `title?` | `string` | Section header. |
113
+ | `footer?` | `string` | Section footer. |
114
+ | `children` | `React.ReactNode` | `Item`, `Divider`. |
115
+ | `style?` | `StyleProp<ViewStyle>` | Override group container style. |
116
+
117
+ ### `SettingsListItem`
118
+
119
+ | Prop | Type | Description |
120
+ |------|------|-------------|
121
+ | `icon?` | `React.ReactNode` | Leading icon. |
122
+ | `title` | `string` | Primary label. |
123
+ | `description?` | `string` | Multi-line secondary text. |
124
+ | `value?` | `string` | Right-side value string. |
125
+ | `rightElement?` | `React.ReactNode` | Replaces value / chevron with a custom node. |
126
+ | `showChevron?` | `boolean` | Defaults to `true` when `onPress` is set. |
127
+ | `destructive?` | `boolean` | Negative palette for title + icon. |
128
+ | `onPress?` | `() => void` | Tap handler. |
129
+ | `disabled?` | `boolean` | |
130
+ | `accessibilityLabel?` | `string` | Override the derived label. |
131
+ | `accessibilityHint?` | `string` | |
132
+ | `accessibilityRole?` | `'button' \| 'link' \| 'none'` | Defaults to `'button'` when pressable. |
133
+ | `leftInset?` | `number` | Left inset to align text when no icon is set. |
134
+
135
+ ### `SettingsListDivider`
136
+
137
+ | Prop | Type | Description |
138
+ |------|------|-------------|
139
+ | `inset?` | `number` | Left inset to align with text (default `52`). |
140
+
141
+ ## Recipes
142
+
143
+ ### Mixed read / link rows
144
+
145
+ ```tsx
146
+ <SettingsListGroup title="About">
147
+ <SettingsListItem title="Version" value="1.4.2" />
148
+ <SettingsListDivider />
149
+ <SettingsListItem title="Build" value="231" />
150
+ <SettingsListDivider />
151
+ <SettingsListItem title="Terms" onPress={() => navigate('Terms')} />
152
+ <SettingsListDivider />
153
+ <SettingsListItem title="Privacy" onPress={() => navigate('Privacy')} />
154
+ </SettingsListGroup>
155
+ ```
156
+
157
+ ### Sign-out section
158
+
159
+ ```tsx
160
+ <SettingsListGroup>
161
+ <SettingsListItem
162
+ title="Sign out"
163
+ onPress={signOut}
164
+ accessibilityRole="button"
165
+ />
166
+ </SettingsListGroup>
167
+ ```
168
+
169
+ ### Account switcher
170
+
171
+ ```tsx
172
+ <SettingsListGroup title="Accounts">
173
+ {accounts.map((a, i) => (
174
+ <Fragment key={a.id}>
175
+ {i > 0 ? <SettingsListDivider /> : null}
176
+ <SettingsListItem
177
+ icon={<Avatar uri={a.avatarUrl} size={28} />}
178
+ title={a.name}
179
+ description={a.handle}
180
+ rightElement={a.id === activeId ? <CheckIcon /> : null}
181
+ onPress={() => switchTo(a.id)}
182
+ />
183
+ </Fragment>
184
+ ))}
185
+ </SettingsListGroup>
186
+ ```
@@ -0,0 +1,66 @@
1
+ ---
2
+ title: Skeleton
3
+ description: Shimmering placeholders shaped like the content that is coming.
4
+ order: 32
5
+ ---
6
+
7
+ # Skeleton
8
+
9
+ Placeholders for content that has not arrived. `Skeleton` is a namespace of
10
+ primitives you compose into the shape of the real thing.
11
+
12
+ ```tsx
13
+ import * as Skeleton from '@oxyhq/bloom/skeleton';
14
+
15
+ <Skeleton.Row style={{ gap: 12 }}>
16
+ <Skeleton.Circle size={40} />
17
+ <Skeleton.Col style={{ gap: 6 }}>
18
+ <Skeleton.Text style={{ width: 140 }} />
19
+ <Skeleton.Text style={{ width: 90 }} />
20
+ </Skeleton.Col>
21
+ </Skeleton.Row>
22
+ ```
23
+
24
+ ## The parts
25
+
26
+ | part | takes | for |
27
+ | ------------------ | -------------------- | ---------------------------------- |
28
+ | `Skeleton.Text` | `style` (`width`, `lineHeight`) | a line of text |
29
+ | `Skeleton.Circle` | `size` | an avatar |
30
+ | `Skeleton.Pill` | `size` | a chip, a badge, a button |
31
+ | `Skeleton.Box` | `width` / `height` | anything rectangular |
32
+ | `Skeleton.Row` / `Skeleton.Col` | `style` | layout, so a placeholder needs no extra views |
33
+
34
+ `Skeleton.Text` reads its width and line height out of `style` rather than
35
+ taking props for them, so the same style object you would give the real `Text`
36
+ produces a placeholder the right size — which is the point.
37
+
38
+ It is a namespace rather than flat exports because the names are generic
39
+ (`Text`, `Box`, `Row`) and would collide with everything. Each ships as a
40
+ subpath, so `import * as` costs no tree-shaking.
41
+
42
+ ## Shape it like the content, not like a grey box
43
+
44
+ The point of a skeleton is that nothing moves when the data arrives. A
45
+ placeholder wider than the real text, or one line where there will be two,
46
+ produces a jump that is more distracting than a spinner would have been. Match
47
+ the line count and the rough widths; do not match them exactly, because
48
+ identical widths on every row read as a pattern rather than as text.
49
+
50
+ ## Skeleton or Loading
51
+
52
+ - **Skeleton** — you know the shape of what is coming and it fills the region.
53
+ A feed, a profile header, a table.
54
+ - **[Loading](/docs/bloom/main/loading)** — you do not know the shape, or the
55
+ wait is short, or it is a small inline region. Also the right choice for an
56
+ action in progress, where a skeleton would imply content is arriving.
57
+
58
+ Do not show a skeleton for longer than a couple of seconds. Past that it stops
59
+ reading as "almost there" and starts reading as broken; show progress or an
60
+ explanation instead.
61
+
62
+ ## Accessibility
63
+
64
+ A skeleton is decorative — it must not be announced as text. Mark the region as
65
+ busy on the container that will hold the content, so a screen reader user is
66
+ told that something is loading rather than read a grid of empty boxes.
@@ -0,0 +1,73 @@
1
+ ---
2
+ title: Slider
3
+ description: A value on a continuum — and the difference between `onValueChange` and `onSlidingComplete`.
4
+ order: 35
5
+ ---
6
+
7
+ # Slider
8
+
9
+ For a value where the *approximate* position matters more than the exact number:
10
+ volume, brightness, a zoom level, a price ceiling.
11
+
12
+ ```tsx
13
+ import { Slider } from '@oxyhq/bloom/slider';
14
+
15
+ <Slider
16
+ value={volume}
17
+ onValueChange={setVolume}
18
+ onSlidingComplete={persistVolume}
19
+ min={0}
20
+ max={100}
21
+ step={1}
22
+ accessibilityLabel="Volume"
23
+ />
24
+ ```
25
+
26
+ ## Which prop decides what
27
+
28
+ | prop | decides |
29
+ | ------------------- | --------------------------------------------------------- |
30
+ | `value` | the position — the slider is fully controlled |
31
+ | `onValueChange` | fires **continuously** while dragging |
32
+ | `onSlidingComplete` | fires **once**, when the finger lifts |
33
+ | `min` / `max` | the range |
34
+ | `step` | the granularity; omit for continuous |
35
+ | `disabled` | dims and ignores gestures |
36
+
37
+ ## The two callbacks are the whole API
38
+
39
+ `onValueChange` fires on every frame of a drag. Put the *preview* there — the
40
+ number under the thumb, the live volume — and nothing else.
41
+
42
+ `onSlidingComplete` fires once. Put anything expensive or persistent there: a
43
+ network request, a write to storage, a re-render of a large list. A `PATCH` in
44
+ `onValueChange` sends one request per frame, which is the classic way a slider
45
+ takes a settings screen offline.
46
+
47
+ ```tsx
48
+ onValueChange={setLocalValue} // cheap, immediate
49
+ onSlidingComplete={(v) => save(v)} // once, at the end
50
+ ```
51
+
52
+ ## Give it a label and a unit
53
+
54
+ A slider announces a number with no meaning attached. `accessibilityLabel`
55
+ names it; if the number is not self-evident (a price, a duration), show the
56
+ formatted value beside the track so it is readable without dragging.
57
+
58
+ The label is not optional in practice, only in the type. ARIA computes no name
59
+ from a `slider`'s contents — a track and a knob render no text — so a caption
60
+ beside it names nothing, and the `aria-value*` props say how far along it is
61
+ and never what it is. Omitting it warns once in development, dev-only and never
62
+ throwing; an empty or whitespace-only string counts as missing, because
63
+ `aria-label=""` is not a name.
64
+
65
+ ## When a slider is the wrong control
66
+
67
+ - **The exact value matters.** Use a number input; a slider cannot be typed
68
+ into and is hard to land on a specific value.
69
+ - **There are fewer than about five positions.** Use a
70
+ [`SegmentedControl`](/docs/bloom/main/segmented-control) or a
71
+ [`Radio`](/docs/bloom/main/radio) group — they name the options.
72
+ - **The range is huge and non-linear.** A slider over 1–1,000,000 gives most of
73
+ its travel to values nobody wants.
@@ -0,0 +1,59 @@
1
+ ---
2
+ title: StatBar
3
+ description: A labelled bar in two shapes — progress toward a maximum, or a split between two sides.
4
+ order: 63
5
+ ---
6
+
7
+ # StatBar
8
+
9
+ One bar, two meanings.
10
+
11
+ ```tsx
12
+ import { StatBar } from '@oxyhq/bloom/stat-bar';
13
+
14
+ <StatBar label="Storage" value={62} max={100} minLabel="0 GB" maxLabel="100 GB" />
15
+
16
+ <StatBar variant="split" label="Votes" percent={64} leftValue="Yes" rightValue="No" />
17
+ ```
18
+
19
+ ## Which prop decides what
20
+
21
+ | prop | decides |
22
+ | ------------------------- | --------------------------------------------------- |
23
+ | `variant` | `progress` (default) or `split` |
24
+ | `value` / `max` | progress: how far along |
25
+ | `percent` | split: where the divide sits |
26
+ | `leftValue` / `rightValue`| split: what each side is |
27
+ | `minLabel` / `maxLabel` | progress: the ends of the range |
28
+ | `label` | what the bar is about — required |
29
+ | `icon` | a glyph beside the label |
30
+ | `fillColor` / `trackColor`| the colours |
31
+
32
+ The two variants are one component because they are the same object visually,
33
+ and one prop rather than two components because a row often switches between
34
+ them for the same subject.
35
+
36
+ ## `progress` and `split` are different claims
37
+
38
+ `progress` says *how much of a maximum has been used* — there is a ceiling, and
39
+ approaching it means something. `split` says *how a whole divides between two
40
+ named sides* — there is no ceiling and neither side is "full".
41
+
42
+ Using `progress` for a split reads as the left side filling up toward a limit,
43
+ which is a claim you did not intend to make.
44
+
45
+ ## `minLabel` / `maxLabel` on a progress bar
46
+
47
+ A bar with no scale is a proportion with no units. "62 of 100" is a different
48
+ statement from "62%", and only one of them tells the user whether to act.
49
+
50
+ ## `percent` is a number, the values are strings
51
+
52
+ `leftValue` and `rightValue` are strings because they are labels, not the
53
+ maths: "Yes" / "No", "1,204" / "677", "You" / "Everyone else". The bar's
54
+ geometry comes from `percent` alone.
55
+
56
+ ## `label` is required
57
+
58
+ A bar in a stack of stats with no label is a colour. It is also the only thing
59
+ a screen reader gets, since the bar itself conveys nothing.
@@ -0,0 +1,33 @@
1
+ ---
2
+ title: Choosing a stat widget
3
+ description: Four families, four different questions about a number.
4
+ order: 61
5
+ ---
6
+
7
+ # Choosing a stat widget
8
+
9
+ Bloom has four small data-display families. They are separate because each
10
+ answers a different question, and picking the wrong one makes a number harder
11
+ to read rather than easier.
12
+
13
+ | family | answers |
14
+ | ----------------------------------------------------- | ---------------------------------------- |
15
+ | [`CompositionBar`](/docs/bloom/main/composition-bar) | what is this total **made of**? |
16
+ | [`StatBar`](/docs/bloom/main/stat-bar) | how far along, or how is it split in two? |
17
+ | [`DotGridMeter`](/docs/bloom/main/dot-grid-meter) | how many out of a countable total? |
18
+ | [`ActivityHeatmap`](/docs/bloom/main/activity-heatmap) | when did this happen, over time? |
19
+
20
+ Two rules apply to all four.
21
+
22
+ ## Colours are the app's, not the theme's
23
+
24
+ Each takes explicit colours, because the categories belong to the product — a
25
+ token's brand colour is not a Bloom role. Whatever you pass, check it against
26
+ the surface it sits on and in both modes: a colour chosen on a light card is
27
+ routinely invisible in dark mode.
28
+
29
+ ## A chart still needs the number in text
30
+
31
+ None of these announces anything useful on its own. Whatever the widget shows,
32
+ the value should also be readable as text nearby — otherwise the information is
33
+ available only to people who can see and interpret the shape.