@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.
- package/README.md +16 -4
- package/docs/accordion.mdx +69 -0
- package/docs/activity-heatmap.mdx +71 -0
- package/docs/admonition.mdx +77 -0
- package/docs/alert-dialog.mdx +75 -0
- package/docs/alert.mdx +93 -0
- package/docs/animated-check.mdx +53 -0
- package/docs/aspect-ratio.mdx +45 -0
- package/docs/avatar-group.mdx +91 -0
- package/docs/avatar.mdx +126 -0
- package/docs/badge.mdx +74 -0
- package/docs/benefit-list.mdx +56 -0
- package/docs/bottom-sheet.mdx +103 -0
- package/docs/button.mdx +218 -0
- package/docs/card.mdx +106 -0
- package/docs/checkbox.mdx +59 -0
- package/docs/chip.mdx +53 -0
- package/docs/code.mdx +50 -0
- package/docs/combobox.mdx +64 -0
- package/docs/command.mdx +85 -0
- package/docs/composition-bar.mdx +63 -0
- package/docs/connection-dots.mdx +50 -0
- package/docs/connection-status.mdx +56 -0
- package/docs/content-panel.mdx +62 -0
- package/docs/context-menu.mdx +65 -0
- package/docs/design-tokens.mdx +254 -0
- package/docs/dialog.mdx +277 -0
- package/docs/divider.mdx +56 -0
- package/docs/dot-grid-meter.mdx +66 -0
- package/docs/dropdown-menu.mdx +161 -0
- package/docs/error-boundary.mdx +72 -0
- package/docs/fab.mdx +67 -0
- package/docs/field.mdx +70 -0
- package/docs/fill.mdx +58 -0
- package/docs/fonts.mdx +144 -0
- package/docs/frosted-icon-button.mdx +60 -0
- package/docs/getting-started.mdx +75 -0
- package/docs/grid.mdx +64 -0
- package/docs/hooks.mdx +81 -0
- package/docs/icon-circle.mdx +66 -0
- package/docs/icons.mdx +69 -0
- package/docs/image-aspect-ratio-cache.mdx +63 -0
- package/docs/image-resolver.mdx +66 -0
- package/docs/index.mdx +44 -0
- package/docs/input-group.mdx +58 -0
- package/docs/item.mdx +69 -0
- package/docs/kbd.mdx +57 -0
- package/docs/label.mdx +50 -0
- package/docs/link-preview.mdx +66 -0
- package/docs/list.mdx +72 -0
- package/docs/loading.mdx +129 -0
- package/docs/media-inset-border.mdx +52 -0
- package/docs/menubar.mdx +86 -0
- package/docs/migrating-to-1.0.mdx +434 -0
- package/docs/motion.mdx +83 -0
- package/docs/overlay.mdx +75 -0
- package/docs/popover.mdx +73 -0
- package/docs/portal.mdx +63 -0
- package/docs/pressable-scale.mdx +51 -0
- package/docs/profile-card.mdx +66 -0
- package/docs/progressive-blur.mdx +54 -0
- package/docs/prompt-input.mdx +183 -0
- package/docs/provider.mdx +64 -0
- package/docs/radio-indicator.mdx +60 -0
- package/docs/radio.mdx +65 -0
- package/docs/scroll.mdx +66 -0
- package/docs/search.mdx +67 -0
- package/docs/segmented-control.mdx +76 -0
- package/docs/select.mdx +93 -0
- package/docs/settings-list.mdx +186 -0
- package/docs/skeleton.mdx +66 -0
- package/docs/slider.mdx +73 -0
- package/docs/stat-bar.mdx +59 -0
- package/docs/stat-widgets.mdx +33 -0
- package/docs/styles.mdx +61 -0
- package/docs/subtle-hover.mdx +53 -0
- package/docs/surfaces.mdx +72 -0
- package/docs/switch.mdx +90 -0
- package/docs/tab-bar.mdx +386 -0
- package/docs/tabs.mdx +75 -0
- package/docs/text-field.mdx +187 -0
- package/docs/theme.mdx +67 -0
- package/docs/toast.mdx +285 -0
- package/docs/tooltip.mdx +67 -0
- package/docs/typography.mdx +88 -0
- package/docs/user-hover-card.mdx +140 -0
- package/docs/zoomable-image-gallery.mdx +66 -0
- package/lib/commonjs/design-tokens/color-roles.js +21 -1
- package/lib/commonjs/design-tokens/color-roles.js.map +1 -1
- package/lib/commonjs/design-tokens/index.js +6 -0
- package/lib/commonjs/design-tokens/index.js.map +1 -1
- package/lib/commonjs/design-tokens/tailwind-preset.js +3 -1
- package/lib/commonjs/design-tokens/tailwind-preset.js.map +1 -1
- package/lib/commonjs/design-tokens/theme-css.js +1 -0
- package/lib/commonjs/design-tokens/theme-css.js.map +1 -1
- package/lib/commonjs/design-tokens/theme.css +3 -0
- package/lib/commonjs/design-tokens/tokens-json.js +13 -4
- package/lib/commonjs/design-tokens/tokens-json.js.map +1 -1
- package/lib/commonjs/design-tokens/tokens.json +6227 -265
- package/lib/commonjs/index.js +7 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +7 -0
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/theme/ambient-store.js +16 -1
- package/lib/commonjs/theme/ambient-store.js.map +1 -1
- package/lib/commonjs/theme/color-policy.js +30 -33
- package/lib/commonjs/theme/color-policy.js.map +1 -1
- package/lib/commonjs/theme/color-presets.js +413 -166
- package/lib/commonjs/theme/color-presets.js.map +1 -1
- package/lib/commonjs/theme/color-scope/seed-scope.js +1 -0
- package/lib/commonjs/theme/color-scope/seed-scope.js.map +1 -1
- package/lib/commonjs/theme/glass-colors.js +18 -21
- package/lib/commonjs/theme/glass-colors.js.map +1 -1
- package/lib/commonjs/theme/index.js +30 -0
- package/lib/commonjs/theme/index.js.map +1 -1
- package/lib/commonjs/theme/index.web.js +30 -0
- package/lib/commonjs/theme/index.web.js.map +1 -1
- package/lib/module/design-tokens/color-roles.js +20 -0
- package/lib/module/design-tokens/color-roles.js.map +1 -1
- package/lib/module/design-tokens/index.js +2 -2
- package/lib/module/design-tokens/index.js.map +1 -1
- package/lib/module/design-tokens/tailwind-preset.js +4 -2
- package/lib/module/design-tokens/tailwind-preset.js.map +1 -1
- package/lib/module/design-tokens/theme-css.js +2 -1
- package/lib/module/design-tokens/theme-css.js.map +1 -1
- package/lib/module/design-tokens/theme.css +3 -0
- package/lib/module/design-tokens/tokens-json.js +14 -5
- package/lib/module/design-tokens/tokens-json.js.map +1 -1
- package/lib/module/design-tokens/tokens.json +6227 -265
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/theme/ambient-store.js +16 -1
- package/lib/module/theme/ambient-store.js.map +1 -1
- package/lib/module/theme/color-policy.js +30 -33
- package/lib/module/theme/color-policy.js.map +1 -1
- package/lib/module/theme/color-presets.js +412 -165
- package/lib/module/theme/color-presets.js.map +1 -1
- package/lib/module/theme/color-scope/seed-scope.js +2 -1
- package/lib/module/theme/color-scope/seed-scope.js.map +1 -1
- package/lib/module/theme/glass-colors.js +18 -21
- package/lib/module/theme/glass-colors.js.map +1 -1
- package/lib/module/theme/index.js +1 -1
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/index.web.js +1 -1
- package/lib/module/theme/index.web.js.map +1 -1
- package/lib/typescript/commonjs/design-tokens/color-roles.d.ts +20 -0
- package/lib/typescript/commonjs/design-tokens/color-roles.d.ts.map +1 -1
- package/lib/typescript/commonjs/design-tokens/index.d.ts +3 -3
- package/lib/typescript/commonjs/design-tokens/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/design-tokens/tailwind-preset.d.ts.map +1 -1
- package/lib/typescript/commonjs/design-tokens/theme-css.d.ts.map +1 -1
- package/lib/typescript/commonjs/design-tokens/tokens-json.d.ts +12 -3
- package/lib/typescript/commonjs/design-tokens/tokens-json.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +2 -2
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +2 -2
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/ambient-store.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-policy.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-presets.d.ts +386 -62
- package/lib/typescript/commonjs/theme/color-presets.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-scope/seed-scope.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/glass-colors.d.ts +18 -21
- package/lib/typescript/commonjs/theme/glass-colors.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/index.d.ts +2 -2
- package/lib/typescript/commonjs/theme/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/index.web.d.ts +2 -2
- package/lib/typescript/commonjs/theme/index.web.d.ts.map +1 -1
- package/lib/typescript/module/design-tokens/color-roles.d.ts +20 -0
- package/lib/typescript/module/design-tokens/color-roles.d.ts.map +1 -1
- package/lib/typescript/module/design-tokens/index.d.ts +3 -3
- package/lib/typescript/module/design-tokens/index.d.ts.map +1 -1
- package/lib/typescript/module/design-tokens/tailwind-preset.d.ts.map +1 -1
- package/lib/typescript/module/design-tokens/theme-css.d.ts.map +1 -1
- package/lib/typescript/module/design-tokens/tokens-json.d.ts +12 -3
- package/lib/typescript/module/design-tokens/tokens-json.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +2 -2
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +2 -2
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/theme/ambient-store.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-policy.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-presets.d.ts +386 -62
- package/lib/typescript/module/theme/color-presets.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-scope/seed-scope.d.ts.map +1 -1
- package/lib/typescript/module/theme/glass-colors.d.ts +18 -21
- package/lib/typescript/module/theme/glass-colors.d.ts.map +1 -1
- package/lib/typescript/module/theme/index.d.ts +2 -2
- package/lib/typescript/module/theme/index.d.ts.map +1 -1
- package/lib/typescript/module/theme/index.web.d.ts +2 -2
- package/lib/typescript/module/theme/index.web.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/__tests__/support/collision-fixture-barrel.ts +20 -0
- package/src/__tests__/support/constructed-style-sheets.ts +68 -0
- package/src/__tests__/support/press-host.ts +30 -0
- package/src/__tests__/support/rendered-style.ts +99 -0
- package/src/__tests__/support/unread-hook-fixture.ts +33 -0
- package/src/design-tokens/color-roles.ts +21 -0
- package/src/design-tokens/index.ts +3 -2
- package/src/design-tokens/tailwind-preset.ts +3 -2
- package/src/design-tokens/theme-css.ts +2 -1
- package/src/design-tokens/theme.css +3 -0
- package/src/design-tokens/tokens-json.ts +29 -9
- package/src/design-tokens/tokens.json +6227 -265
- package/src/glass/GlassSurface.tsx +2 -2
- package/src/index.ts +2 -0
- package/src/index.web.ts +2 -0
- package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +4082 -0
- package/src/theme/__tests__/fixtures/color-engine-golden.json +1 -0
- package/src/theme/ambient-store.ts +21 -1
- package/src/theme/color-policy.ts +33 -38
- package/src/theme/color-presets.ts +473 -146
- package/src/theme/color-scope/seed-scope.ts +2 -1
- package/src/theme/glass-colors.ts +18 -21
- package/src/theme/index.ts +16 -1
- package/src/theme/index.web.ts +16 -1
package/README.md
CHANGED
|
@@ -76,6 +76,16 @@ On native, wrap the app root in `GestureHandlerRootView` from `react-native-gest
|
|
|
76
76
|
|
|
77
77
|
</details>
|
|
78
78
|
|
|
79
|
+
## Upgrading from 0.x
|
|
80
|
+
|
|
81
|
+
Bloom follows the clean cut rule, so there are no `@deprecated` aliases and no old name shims anywhere in 1.0.0. If a symbol you import was renamed, nothing re-exports it under the old spelling and the build fails. **[`docs/migrating-to-1.0.mdx`](./docs/migrating-to-1.0.mdx) is the only bridge** — every removed subpath, every rename, every prop change, derived from the published 0.89.0 and 1.0.0 tarballs rather than from commit messages.
|
|
82
|
+
|
|
83
|
+
That file ships inside the package, so the copy you read is the one that describes the version you installed:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
$EDITOR node_modules/@oxyhq/bloom/docs/migrating-to-1.0.mdx
|
|
87
|
+
```
|
|
88
|
+
|
|
79
89
|
## Getting started
|
|
80
90
|
|
|
81
91
|
Mount `BloomProvider` once, at the very top of the app. It composes every piece of app wide Bloom state, theme, haptics, image resolution, scroll restoration and tab bar minimise progress, so none of them can end up at the wrong depth.
|
|
@@ -144,10 +154,10 @@ Bloom publishes 88 subpath exports. Importing from the subpath rather than the r
|
|
|
144
154
|
| Group | Exports |
|
|
145
155
|
|---|---|
|
|
146
156
|
| Providers and theme | `provider`, `theme`, `color-presets`, `preset-vars`, `design-tokens`, `tailwind-preset`, `styles`, `hooks` |
|
|
147
|
-
| Overlays | `dialog`, `alert-dialog`, `bottom-sheet`, `popover`, `context-menu`, `menu`, `tooltip`, `overlay`, `portal`, `surfaces` |
|
|
157
|
+
| Overlays | `dialog`, `alert-dialog`, `bottom-sheet`, `popover`, `context-menu`, `dropdown-menu`, `menubar`, `tooltip`, `overlay`, `portal`, `surfaces` |
|
|
148
158
|
| Actions | `button`, `fab`, `frosted-icon-button`, `pressable-scale`, `subtle-hover` |
|
|
149
|
-
| Forms | `text-field`, `field`, `input-group`, `label`, `select`, `combobox`, `command`, `checkbox`, `switch`, `slider`, `segmented-control`, `search`, `prompt-input` |
|
|
150
|
-
| Layout and lists | `grid`, `list`, `scroll`, `tabs`, `tab-bar`, `settings-list`, `content-panel`, `card`, `accordion`, `divider`, `item` |
|
|
159
|
+
| Forms | `text-field`, `field`, `input-group`, `label`, `select`, `combobox`, `command`, `checkbox`, `radio`, `switch`, `slider`, `segmented-control`, `search`, `prompt-input` |
|
|
160
|
+
| Layout and lists | `grid`, `list`, `scroll`, `tabs`, `tab-bar`, `settings-list`, `content-panel`, `card`, `accordion`, `aspect-ratio`, `divider`, `item` |
|
|
151
161
|
| Identity and media | `avatar`, `avatar-group`, `user-hover-card`, `profile-card`, `image-resolver`, `image-aspect-ratio-cache`, `zoomable-image-gallery`, `media-inset-border`, `progressive-blur`, `fill` |
|
|
152
162
|
| Feedback and data | `toast`, `admonition`, `loading`, `skeleton`, `error-boundary`, `badge`, `chip`, `kbd`, `code`, `link-preview`, `connection-status`, `connection-dots` |
|
|
153
163
|
| Charts and motion | `composition-bar`, `dot-grid-meter`, `stat-bar`, `activity-heatmap`, `motion`, `animated-check`, `icon-circle`, `radio-indicator` |
|
|
@@ -157,7 +167,9 @@ Bloom publishes 88 subpath exports. Importing from the subpath rather than the r
|
|
|
157
167
|
|
|
158
168
|
## Documentation
|
|
159
169
|
|
|
160
|
-
Component guides live in [`docs/`](./docs)
|
|
170
|
+
Component guides live in [`docs/`](./docs), one `.mdx` per family, and **they ship inside the package** at `node_modules/@oxyhq/bloom/docs/`. The copy you read there always describes the version you installed, which matters most for [migrating to 1.0](./docs/migrating-to-1.0.mdx).
|
|
171
|
+
|
|
172
|
+
Start with [getting started](./docs/getting-started.mdx), [theme](./docs/theme.mdx) and [design tokens](./docs/design-tokens.mdx). Then per surface: [dialog](./docs/dialog.mdx), [bottom sheet](./docs/bottom-sheet.mdx), [toast](./docs/toast.mdx), [alert](./docs/alert.mdx), [dropdown menu](./docs/dropdown-menu.mdx), [select](./docs/select.mdx), [context menu](./docs/context-menu.mdx), [button](./docs/button.mdx), [avatar](./docs/avatar.mdx), [text field](./docs/text-field.mdx), [settings list](./docs/settings-list.mdx), [tab bar](./docs/tab-bar.mdx), [prompt input](./docs/prompt-input.mdx) and [loading](./docs/loading.mdx).
|
|
161
173
|
|
|
162
174
|
The theme reference is in [README.theme.md](./README.theme.md).
|
|
163
175
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Accordion
|
|
3
|
+
description: Collapsible sections — fully controlled, and when a disclosure is the wrong shape.
|
|
4
|
+
order: 28
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Accordion
|
|
8
|
+
|
|
9
|
+
Sections that expand and collapse. Use it when a page holds more than a reader
|
|
10
|
+
needs at once and the sections are *independent* — an FAQ, a set of advanced
|
|
11
|
+
options, a long form split into stages.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@oxyhq/bloom/accordion';
|
|
15
|
+
|
|
16
|
+
const [open, setOpen] = useState<string | string[] | undefined>('shipping');
|
|
17
|
+
|
|
18
|
+
<Accordion type="single" value={open} onValueChange={setOpen}>
|
|
19
|
+
<AccordionItem value="shipping">
|
|
20
|
+
<AccordionTrigger>When will it arrive?</AccordionTrigger>
|
|
21
|
+
<AccordionContent>Two to four working days.</AccordionContent>
|
|
22
|
+
</AccordionItem>
|
|
23
|
+
<AccordionItem value="returns">
|
|
24
|
+
<AccordionTrigger>Can I return it?</AccordionTrigger>
|
|
25
|
+
<AccordionContent>Within 30 days, unopened.</AccordionContent>
|
|
26
|
+
</AccordionItem>
|
|
27
|
+
</Accordion>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Which prop decides what
|
|
31
|
+
|
|
32
|
+
| prop | decides |
|
|
33
|
+
| --------------- | -------------------------------------------------------------------- |
|
|
34
|
+
| `type` | `single` — one open section — or `multiple` |
|
|
35
|
+
| `value` | which section(s) are open: a string for `single`, an array for `multiple` |
|
|
36
|
+
| `onValueChange` | receives the next value, or `undefined` when everything closes |
|
|
37
|
+
| `disabled` | per `AccordionItem`, a section that cannot be opened |
|
|
38
|
+
| `icon` | per `AccordionTrigger`, replaces the default chevron |
|
|
39
|
+
|
|
40
|
+
**It is fully controlled — `value` and `onValueChange` are required.** There is
|
|
41
|
+
no uncontrolled mode and no `collapsible` flag: closing the last open section
|
|
42
|
+
means `onValueChange` hands you `undefined`, and whether you accept that or
|
|
43
|
+
re-open something is your decision, not the component's. That is what a wizard
|
|
44
|
+
(always something open) and an FAQ (nothing open is fine) actually differ by.
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
// FAQ: allow everything closed.
|
|
48
|
+
onValueChange={setOpen}
|
|
49
|
+
|
|
50
|
+
// Wizard: never let the current step close.
|
|
51
|
+
onValueChange={(next) => setOpen(next ?? open)}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## When an accordion is the wrong shape
|
|
55
|
+
|
|
56
|
+
- **The user needs to compare sections.** Collapsing hides exactly what they are
|
|
57
|
+
comparing.
|
|
58
|
+
- **The content is two lines.** More interaction than reading it would have been.
|
|
59
|
+
- **Something inside must be findable.** Browser find-in-page and screen-reader
|
|
60
|
+
search do not see collapsed content, so an accordion is a bad home for
|
|
61
|
+
anything a user might arrive looking for.
|
|
62
|
+
|
|
63
|
+
## Animation
|
|
64
|
+
|
|
65
|
+
The height transition is driven imperatively from a measured content height
|
|
66
|
+
rather than by a layout animation. On web that distinction is load-bearing:
|
|
67
|
+
an animation started from a Reanimated mapper never ticks, so the obvious
|
|
68
|
+
"animate the content in" silently does nothing. If you nest something that
|
|
69
|
+
animates its own height, let one of the two own the measurement.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ActivityHeatmap
|
|
3
|
+
description: A calendar grid coloured by count — when something happened, over months.
|
|
4
|
+
order: 65
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ActivityHeatmap
|
|
8
|
+
|
|
9
|
+
Days as cells, coloured by how much happened. Contributions, posts, sessions,
|
|
10
|
+
streaks.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { ActivityHeatmap } from '@oxyhq/bloom/activity-heatmap';
|
|
14
|
+
|
|
15
|
+
<ActivityHeatmap
|
|
16
|
+
data={days}
|
|
17
|
+
numDays={182}
|
|
18
|
+
weekStartsOn={1}
|
|
19
|
+
onPressDay={(day) => showDay(day.date)}
|
|
20
|
+
/>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`data` is `{ date, count }[]`; `date` is an ISO day string.
|
|
24
|
+
|
|
25
|
+
## Which prop decides what
|
|
26
|
+
|
|
27
|
+
| prop | decides |
|
|
28
|
+
| -------------------------- | --------------------------------------------------- |
|
|
29
|
+
| `data` | the counts |
|
|
30
|
+
| `numDays` / `endDate` | the window shown |
|
|
31
|
+
| `levels` | the count thresholds between colour steps |
|
|
32
|
+
| `colorScale` / `emptyColor`| the colours per level, and for zero |
|
|
33
|
+
| `weekStartsOn` | `0` Sunday or `1` Monday |
|
|
34
|
+
| `cellSize` / `gap` | the geometry |
|
|
35
|
+
| `monthLabels` / `weekdayLabels` | the axis text, for localisation |
|
|
36
|
+
| `onPressDay` | makes a cell pressable |
|
|
37
|
+
|
|
38
|
+
## `levels` is the prop to think about
|
|
39
|
+
|
|
40
|
+
The default buckets suit a roughly uniform distribution. Real activity data is
|
|
41
|
+
usually skewed — a handful of enormous days and a long tail of ones — and with
|
|
42
|
+
default thresholds almost everything paints at the lowest level, so the chart
|
|
43
|
+
says "nothing much happened" about a year of work.
|
|
44
|
+
|
|
45
|
+
Set thresholds that match your distribution. Quantiles of the non-zero counts
|
|
46
|
+
are a better starting point than evenly spaced values.
|
|
47
|
+
|
|
48
|
+
Pass one more colour than you pass thresholds. A level is the number of
|
|
49
|
+
thresholds a count meets, so N thresholds address `colorScale[0..N]`; index 0 is
|
|
50
|
+
"positive, but below the first threshold". That index only exists while the
|
|
51
|
+
first threshold is 2 or more — start it at 1 and every positive count meets it,
|
|
52
|
+
so the faintest colour in your scale can never paint.
|
|
53
|
+
|
|
54
|
+
## Zero is not the lowest level
|
|
55
|
+
|
|
56
|
+
`emptyColor` is separate from the scale on purpose. "Nothing happened" and "the
|
|
57
|
+
least that ever happens" are different facts, and collapsing them hides exactly
|
|
58
|
+
the streaks the chart is usually read for.
|
|
59
|
+
|
|
60
|
+
## `weekStartsOn` is a locale decision
|
|
61
|
+
|
|
62
|
+
Not a style one. Getting it wrong shifts every column by a day, so the chart
|
|
63
|
+
disagrees with the user's calendar — which reads as bad data rather than as a
|
|
64
|
+
wrong setting. `monthLabels` and `weekdayLabels` exist for the same reason:
|
|
65
|
+
they must come from the app's locale, not from English defaults.
|
|
66
|
+
|
|
67
|
+
## Give the window a reason
|
|
68
|
+
|
|
69
|
+
`numDays` should match a period the user thinks in — a year, six months, a
|
|
70
|
+
quarter. An arbitrary window invites the reader to work out what they are
|
|
71
|
+
looking at, and the label above the chart should say it outright.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Admonition
|
|
3
|
+
description: A bordered callout inside the content — for information the reader must not scroll past.
|
|
4
|
+
order: 49
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Admonition
|
|
8
|
+
|
|
9
|
+
A bordered block that interrupts the reading flow: a warning about a setting, a
|
|
10
|
+
note about a limitation, an apology for something that went wrong.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Admonition } from '@oxyhq/bloom/admonition';
|
|
14
|
+
|
|
15
|
+
<Admonition type="warning">
|
|
16
|
+
Turning this off signs you out of every other device.
|
|
17
|
+
</Admonition>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
For a callout with an action, compose the parts:
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import {
|
|
24
|
+
AdmonitionRoot,
|
|
25
|
+
AdmonitionRow,
|
|
26
|
+
AdmonitionIcon,
|
|
27
|
+
AdmonitionContent,
|
|
28
|
+
AdmonitionText,
|
|
29
|
+
AdmonitionButton,
|
|
30
|
+
} from '@oxyhq/bloom/admonition';
|
|
31
|
+
|
|
32
|
+
<AdmonitionRoot type="error">
|
|
33
|
+
<AdmonitionRow>
|
|
34
|
+
<AdmonitionIcon />
|
|
35
|
+
<AdmonitionContent>
|
|
36
|
+
<AdmonitionText>We couldn't reach the server.</AdmonitionText>
|
|
37
|
+
<AdmonitionButton onPress={retry}>Try again</AdmonitionButton>
|
|
38
|
+
</AdmonitionContent>
|
|
39
|
+
</AdmonitionRow>
|
|
40
|
+
</AdmonitionRoot>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## `type` picks the icon and the border together
|
|
44
|
+
|
|
45
|
+
| `type` | for |
|
|
46
|
+
| --------- | ------------------------------------------------------- |
|
|
47
|
+
| `info` | a neutral note |
|
|
48
|
+
| `tip` | a suggestion — painted in the brand colour |
|
|
49
|
+
| `warning` | something that could go wrong if they continue |
|
|
50
|
+
| `error` | something that has gone wrong |
|
|
51
|
+
| `apology` | a limitation on our side, not the reader's mistake |
|
|
52
|
+
|
|
53
|
+
`AdmonitionIcon` reads the type from context, so the icon and the border cannot
|
|
54
|
+
disagree — which is how an `error` callout ends up with a warning icon when the
|
|
55
|
+
two are set by hand.
|
|
56
|
+
|
|
57
|
+
## Admonition or toast
|
|
58
|
+
|
|
59
|
+
- **Admonition** — stays. It is *about* the content it sits next to, and the
|
|
60
|
+
reader can return to it.
|
|
61
|
+
- **[Toast](/docs/bloom/main/toast)** — passes. It is about something that just
|
|
62
|
+
happened.
|
|
63
|
+
|
|
64
|
+
Anything the user might need to read twice belongs in an admonition. A toast is
|
|
65
|
+
gone in four seconds and cannot be recovered.
|
|
66
|
+
|
|
67
|
+
## `apology` exists because the wording differs
|
|
68
|
+
|
|
69
|
+
"Something went wrong" and "we couldn't do this, and it is our fault" are
|
|
70
|
+
different messages, and the second reads badly in an `error` box that looks
|
|
71
|
+
like the user broke something. Use it where the limitation is ours.
|
|
72
|
+
|
|
73
|
+
## Do not stack them
|
|
74
|
+
|
|
75
|
+
Three callouts in a row is a page that has given up prioritising. If everything
|
|
76
|
+
is important, nothing reads as important — and a reader who learns to scroll
|
|
77
|
+
past one will scroll past the one that mattered.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: AlertDialog
|
|
3
|
+
description: The declarative confirm dialog — and when to use `confirm()` instead.
|
|
4
|
+
order: 40
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AlertDialog
|
|
8
|
+
|
|
9
|
+
A confirm dialog you render: a title, a description, and a confirm/cancel pair.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { AlertDialog } from '@oxyhq/bloom/alert-dialog';
|
|
13
|
+
|
|
14
|
+
<AlertDialog
|
|
15
|
+
visible={confirmingDelete}
|
|
16
|
+
onClose={() => setConfirmingDelete(false)}
|
|
17
|
+
title="Delete this draft?"
|
|
18
|
+
description="This cannot be undone."
|
|
19
|
+
confirmLabel="Delete"
|
|
20
|
+
destructive
|
|
21
|
+
onConfirm={remove}
|
|
22
|
+
/>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Which prop decides what
|
|
26
|
+
|
|
27
|
+
| prop | decides |
|
|
28
|
+
| --------------- | ------------------------------------------------------------ |
|
|
29
|
+
| `visible` | whether it is open — the dialog is controlled |
|
|
30
|
+
| `onClose` | called whenever it closes, however it closed |
|
|
31
|
+
| `title` | the question |
|
|
32
|
+
| `description` | the consequence |
|
|
33
|
+
| `confirmLabel` / `cancelLabel` | the button text |
|
|
34
|
+
| `onConfirm` / `onCancel` | what each button does |
|
|
35
|
+
| `destructive` | paints the confirm button in the error role |
|
|
36
|
+
| `hideCancel` | one button only — an acknowledgement, not a choice |
|
|
37
|
+
| `dismissible` | whether a backdrop press or Escape closes it |
|
|
38
|
+
|
|
39
|
+
There is no free-form action array: the shape is confirm-and-cancel, and that
|
|
40
|
+
constraint is the point. A dialog with three or four actions is a menu wearing
|
|
41
|
+
a dialog's clothes, and the user has to read all of them before deciding.
|
|
42
|
+
|
|
43
|
+
## AlertDialog or `confirm()`
|
|
44
|
+
|
|
45
|
+
Both ask the same question; they differ in where the answer goes.
|
|
46
|
+
|
|
47
|
+
- **`AlertDialog`** — declarative and controlled. Right when the open state is
|
|
48
|
+
already part of the screen's state.
|
|
49
|
+
- **[`confirm()`](/docs/bloom/main/surfaces)** — imperative, returns a promise.
|
|
50
|
+
Right inside an event handler or a mutation callback, where you want to
|
|
51
|
+
`await` the answer and there is no natural place to hang a `visible` flag.
|
|
52
|
+
|
|
53
|
+
The family owns no queue and no host: it bridges onto `Dialog`'s imperative
|
|
54
|
+
control internally, so it stacks with every other overlay and its exit
|
|
55
|
+
animation is not raced by a consumer unmounting in `onClose`.
|
|
56
|
+
|
|
57
|
+
## `destructive` is a colour
|
|
58
|
+
|
|
59
|
+
It does not add a second confirmation and it does not make the action
|
|
60
|
+
reversible. If the action destroys something the user cannot get back, the
|
|
61
|
+
recovery path — an undo, a trash — is the real safeguard; the red button is
|
|
62
|
+
only a signal.
|
|
63
|
+
|
|
64
|
+
## Write the question, not "Are you sure?"
|
|
65
|
+
|
|
66
|
+
The title says what will happen, and `confirmLabel` says what the button does —
|
|
67
|
+
"Delete" rather than "OK". Anyone dismissing dialogs quickly reads only the
|
|
68
|
+
button, and should still know what they agreed to.
|
|
69
|
+
|
|
70
|
+
## `hideCancel` is for an acknowledgement
|
|
71
|
+
|
|
72
|
+
One button means there is no choice to make — something already happened and
|
|
73
|
+
the user is being told. Consider whether that needs a dialog at all: if nothing
|
|
74
|
+
is required of the reader, a [`toast`](/docs/bloom/main/toast) says it without
|
|
75
|
+
interrupting.
|
package/docs/alert.mdx
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: alert()
|
|
3
|
+
description: Imperative one-shot confirmation dialogs, matching React Native's Alert.alert signature.
|
|
4
|
+
order: 6
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# alert()
|
|
8
|
+
|
|
9
|
+
Imperative one-shot confirmation dialogs, matching React Native's `Alert.alert(title, message?, buttons?)` signature. You can call `alert()` from anywhere — an event handler, an async callback, a top-level helper — without threading a provider through the call site.
|
|
10
|
+
|
|
11
|
+
It presents onto Bloom's shared **surface stack**, the same one `confirm()`, `prompt()` and `surfaces.present()` use, so an alert layers correctly above whatever is already open. Two `alert()` calls in a row STACK; they do not queue.
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
Mount the surface host once near your app root:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { SurfaceProvider } from '@oxyhq/bloom';
|
|
19
|
+
|
|
20
|
+
<SurfaceProvider>
|
|
21
|
+
<App />
|
|
22
|
+
</SurfaceProvider>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`<SurfaceHost />` on its own does the same job without wrapping `children`, if you'd rather place it yourself.
|
|
26
|
+
|
|
27
|
+
See [Getting started](/docs/bloom/main/getting-started) for the full provider stack.
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { alert } from '@oxyhq/bloom';
|
|
33
|
+
|
|
34
|
+
alert('Sign out?', 'Are you sure you want to sign out of this device?', [
|
|
35
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
36
|
+
{ text: 'Sign out', style: 'destructive', onPress: doSignOut },
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
// Single OK button (default when no buttons passed):
|
|
40
|
+
alert('Saved');
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Signature
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
function alert(
|
|
47
|
+
title: string,
|
|
48
|
+
message?: string,
|
|
49
|
+
buttons?: AlertButton[],
|
|
50
|
+
): void
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `AlertButton`
|
|
54
|
+
|
|
55
|
+
| Prop | Type | Description |
|
|
56
|
+
|------|------|-------------|
|
|
57
|
+
| `text` | `string` | Required label. |
|
|
58
|
+
| `style?` | `'default' \| 'cancel' \| 'destructive'` | Defaults to `'default'`. |
|
|
59
|
+
| `onPress?` | `() => void` | Fires on the press, as the surface animates out. |
|
|
60
|
+
|
|
61
|
+
## Patterns
|
|
62
|
+
|
|
63
|
+
### Drop-in replacement for `Alert.alert`
|
|
64
|
+
|
|
65
|
+
If you're migrating from React Native's built-in `Alert`, you can swap the import:
|
|
66
|
+
|
|
67
|
+
```diff
|
|
68
|
+
-import { Alert } from 'react-native';
|
|
69
|
+
+import { alert } from '@oxyhq/bloom';
|
|
70
|
+
|
|
71
|
+
-Alert.alert('Title', 'Message');
|
|
72
|
+
+alert('Title', 'Message');
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`alert()` matches the same call signature, so existing call sites work unchanged.
|
|
76
|
+
|
|
77
|
+
<Callout variant="warning" title="alert vs Dialog">
|
|
78
|
+
`alert()` is for **imperative one-shot prompts** (e.g. confirming destructive actions from a deeply nested callback). When you need state-driven control over a dialog (open/close timing, busy states), use the declarative [`<Dialog>`](/docs/bloom/main/dialog) component instead.
|
|
79
|
+
</Callout>
|
|
80
|
+
|
|
81
|
+
## Related
|
|
82
|
+
|
|
83
|
+
`alert()` answers nothing. Its two siblings on the same stack do:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { confirm, prompt } from '@oxyhq/bloom/surfaces';
|
|
87
|
+
|
|
88
|
+
const ok = await confirm({ title: 'Delete app?', destructive: true });
|
|
89
|
+
const name = await prompt({ title: 'Name this list' }); // string | null
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`confirm` resolves `false` and `prompt` resolves `null` on a backdrop / Escape /
|
|
93
|
+
Android-back dismissal, so neither can hang.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: AnimatedCheck
|
|
3
|
+
description: A checkmark that draws itself — confirmation for something that just finished.
|
|
4
|
+
order: 67
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AnimatedCheck
|
|
8
|
+
|
|
9
|
+
A checkmark that strokes itself in when told to.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { AnimatedCheck, type AnimatedCheckRef } from '@oxyhq/bloom/animated-check';
|
|
13
|
+
|
|
14
|
+
const check = useRef<AnimatedCheckRef>(null);
|
|
15
|
+
|
|
16
|
+
<AnimatedCheck ref={check} size={48} />
|
|
17
|
+
|
|
18
|
+
// when the thing succeeds
|
|
19
|
+
check.current?.play();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Which prop decides what
|
|
23
|
+
|
|
24
|
+
| prop | decides |
|
|
25
|
+
| ------- | ---------------------------- |
|
|
26
|
+
| `size` | the glyph size |
|
|
27
|
+
| `color` | the stroke colour |
|
|
28
|
+
|
|
29
|
+
The animation itself is imperative: `ref.play()`. There is no `visible` or
|
|
30
|
+
`animate` prop, because the interesting moment is a **transition** — the check
|
|
31
|
+
should draw when the operation finishes, not whenever the component happens to
|
|
32
|
+
mount or a prop happens to flip.
|
|
33
|
+
|
|
34
|
+
## Where it belongs
|
|
35
|
+
|
|
36
|
+
The end of something the user waited for: a payment confirmed, a file uploaded,
|
|
37
|
+
a form submitted. It replaces the spinner that was there.
|
|
38
|
+
|
|
39
|
+
Not as a static tick in a list — that is an icon
|
|
40
|
+
(`Check_Stroke2_Corner0_Rounded`) and drawing it with an animation component
|
|
41
|
+
costs an animation and a ref for a glyph. And not for every save: a check
|
|
42
|
+
animation on an autosave is motion for something the user did not ask about.
|
|
43
|
+
|
|
44
|
+
## Say it in words too
|
|
45
|
+
|
|
46
|
+
An animation is not announced. Whatever the check confirms should also be
|
|
47
|
+
readable — a line of text beside it, or a [toast](/docs/bloom/main/toast) — or a
|
|
48
|
+
screen reader user gets no confirmation at all.
|
|
49
|
+
|
|
50
|
+
## Reduced motion
|
|
51
|
+
|
|
52
|
+
If the user has asked for reduced motion, show the check rather than drawing
|
|
53
|
+
it. The information is the checkmark; the stroke is decoration.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: AspectRatio
|
|
3
|
+
description: A box that keeps a fixed width-to-height ratio.
|
|
4
|
+
order: 25
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AspectRatio
|
|
8
|
+
|
|
9
|
+
A box that keeps a fixed width-to-height ratio while the layout around it decides its width. The usual job: a media well that must not jump as an image loads, because its height was known before the image was.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { AspectRatio } from '@oxyhq/bloom/aspect-ratio';
|
|
13
|
+
|
|
14
|
+
<AspectRatio ratio={16 / 9}>
|
|
15
|
+
<Image source={{ uri: cover }} style={{ width: '100%', height: '100%' }} />
|
|
16
|
+
</AspectRatio>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`ratio` is width divided by height and defaults to `1`. A number rather than a preset name, for the same reason shadcn's web `AspectRatio` takes one: a ratio is arithmetic the call site already has, and naming presets needs a new name for every ratio anyone wants.
|
|
20
|
+
|
|
21
|
+
## A ratio it cannot use takes the default
|
|
22
|
+
|
|
23
|
+
`0`, `NaN`, `Infinity` and negatives are not ratios — a box has positive finite lengths on both axes — and every one of them comes out of the arithmetic above when the image has not loaded yet: `0 / 0` is `NaN`, `w / 0` is `Infinity`, `0 / h` is `0`. Passed through, they leave the well with no usable height, which is the layout jump this component exists to prevent. So they fall back to `1` rather than reaching the style.
|
|
24
|
+
|
|
25
|
+
You still want a real number here. The fallback keeps the well a well; it does not know your media's shape, so a component that can be told the ratio ahead of the load should be told it.
|
|
26
|
+
|
|
27
|
+
## It fills its parent's width
|
|
28
|
+
|
|
29
|
+
An aspect box with no width is zero-sized, and "fill the parent, derive the height" is what every call site wants. So the width is `100%` and the height follows from `ratio`. A caller who needs a fixed width passes one through `style`, which lands last:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
<AspectRatio ratio={1} style={{ width: 96, borderRadius: 12, overflow: 'hidden' }}>
|
|
33
|
+
<Avatar source={user.avatar} />
|
|
34
|
+
</AspectRatio>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## It takes no `className`
|
|
38
|
+
|
|
39
|
+
`aspectRatio` is the ONE property this component exists to set, and react-native-css merges utilities into `style` before inline style — so a caller writing `className="aspect-video"` would have it silently overwritten by the `ratio` prop, with no error anywhere.
|
|
40
|
+
|
|
41
|
+
One owner per property: use this component with `ratio`, or a plain `View` with the class, never both. Wrap it for the surrounding layout classes.
|
|
42
|
+
|
|
43
|
+
## Not a port
|
|
44
|
+
|
|
45
|
+
react-native-reusables' `aspect-ratio.tsx` is four lines re-exporting `@rn-primitives/aspect-ratio`'s Root — a primitive that exists on the web because CSS needed `padding-bottom` tricks before `aspect-ratio` landed. React Native has had `aspectRatio` as an ordinary style property the whole time, so there was nothing to copy. What earns the name here is the shadcn spelling and the two decisions above.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: AvatarGroup
|
|
3
|
+
description: The one stacked-avatar component. Apps must not keep a local avatar stack.
|
|
4
|
+
order: 22
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AvatarGroup
|
|
8
|
+
|
|
9
|
+
Overlapping avatars with an overflow count — "3 people are typing", "12 members",
|
|
10
|
+
a facepile on a post. **Every grouped or stacked avatar UI in the ecosystem uses
|
|
11
|
+
this.** App-local avatar-stack components are not a variation to be tolerated;
|
|
12
|
+
they drift on overlap, ring colour and overflow rules, and they miss the avatar
|
|
13
|
+
resolution rules below.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { AvatarGroup } from '@oxyhq/bloom/avatar-group';
|
|
17
|
+
|
|
18
|
+
<AvatarGroup
|
|
19
|
+
items={members}
|
|
20
|
+
max={5}
|
|
21
|
+
total={members.length}
|
|
22
|
+
size={32}
|
|
23
|
+
onPressItem={(item) => router.push(`/@${item.username}`)}
|
|
24
|
+
/>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Which prop decides what
|
|
28
|
+
|
|
29
|
+
| prop | decides |
|
|
30
|
+
| ------------------ | -------------------------------------------------------------- |
|
|
31
|
+
| `items` | who is shown, in order |
|
|
32
|
+
| `max` | how many faces before the overflow chip |
|
|
33
|
+
| `total` | the number the overflow chip counts to |
|
|
34
|
+
| `size` / `overlap` | the geometry |
|
|
35
|
+
| `ringColor` | the colour of the gap between overlapping faces |
|
|
36
|
+
| `onPressItem` | makes each face a link to that profile |
|
|
37
|
+
| `hoverCard` | a hover card on web; a no-op on native, where there is no hover |
|
|
38
|
+
| `renderItemAction` | a slot on each face |
|
|
39
|
+
|
|
40
|
+
`total` is separate from `items.length` on purpose: a list endpoint usually
|
|
41
|
+
returns a page of members plus a full count, and the overflow chip should say
|
|
42
|
+
`+248` rather than `+3`.
|
|
43
|
+
|
|
44
|
+
## The avatar prop that goes wrong silently
|
|
45
|
+
|
|
46
|
+
Pass an Oxy avatar to `source`, **not** `uri`.
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
items={users.map((u) => ({ id: u.id, source: u.avatar, name: u.displayName }))} // ✅
|
|
50
|
+
items={users.map((u) => ({ id: u.id, uri: u.avatar }))} // ✗
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`source` sends a non-URL string — an Oxy file id — through the consumer's
|
|
54
|
+
registered `ImageResolver`. `uri` is taken as a raw URL, so a file id resolves
|
|
55
|
+
to nothing, the image 404s, and every face falls back to its initial. Nothing
|
|
56
|
+
throws. The symptom is "all avatars show a placeholder character", which reads
|
|
57
|
+
as missing data rather than as a wiring mistake.
|
|
58
|
+
|
|
59
|
+
## `ringColor` is the surface behind, not a decoration
|
|
60
|
+
|
|
61
|
+
The gap between overlapping faces is drawn in `ringColor`, so it must match
|
|
62
|
+
whatever the group sits **on** — the page background on a screen, the `card`
|
|
63
|
+
role inside a card. A ring left at the default over a coloured surface is the
|
|
64
|
+
usual reason a facepile looks like it has white halos.
|
|
65
|
+
|
|
66
|
+
## `hoverCard`
|
|
67
|
+
|
|
68
|
+
Web-only by construction: hover does not exist on touch, so the prop is a no-op
|
|
69
|
+
on native rather than a different interaction. The card itself is
|
|
70
|
+
[`UserHoverCard`](/docs/bloom/main/user-hover-card), which holds no follow
|
|
71
|
+
state — the consumer passes the SDK's `FollowButton` into its `action` slot.
|
|
72
|
+
|
|
73
|
+
`renderItemAction` is the only slot the hover card exposes per item.
|
|
74
|
+
`UserHoverCard`'s `footer` is not forwarded: a facepile hover card is a glance at
|
|
75
|
+
who these people are, and an app that wants a fuller preview renders
|
|
76
|
+
`UserHoverCard` behind its own trigger.
|
|
77
|
+
|
|
78
|
+
The card floats via `Portal`, imported from `'../portal/index.web'` rather than
|
|
79
|
+
the bare `'../portal'` — export conditions don't apply to relative specifiers,
|
|
80
|
+
so a bare import here resolves to the NATIVE barrel off Metro (Vite, webpack,
|
|
81
|
+
SSR), and the card renders nothing, silently, because the native `Portal`
|
|
82
|
+
needs a `PortalOutlet` no web app mounts. If you touch this import, verify
|
|
83
|
+
against the BUILT artefact, not just the source.
|
|
84
|
+
|
|
85
|
+
The card floats under a `View` with `onPointerEnter`/`onPointerLeave`, and that
|
|
86
|
+
must not become a `Pressable`. react-native-web's `Pressable` dispatches a
|
|
87
|
+
bubbling `react-gui:hover:lock` event on hover, and an ancestor `Pressable` that
|
|
88
|
+
is currently hovered ends its own hover for any lock it did not send — so a
|
|
89
|
+
`Pressable` bridge dismissed the card the moment the cursor reached the injected
|
|
90
|
+
FollowButton or the identity area. Nothing was logged; the card just went away
|
|
91
|
+
about 120ms later.
|