@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/docs/item.mdx
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Item
|
|
3
|
+
description: The one row primitive — and the `role` prop that decides how it is announced.
|
|
4
|
+
order: 31
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Item
|
|
8
|
+
|
|
9
|
+
A row: optional leading element, a title, an optional subtitle, an optional
|
|
10
|
+
trailing element. It is what menus, option lists, comboboxes and settings rows
|
|
11
|
+
are built from, so a row anywhere in the ecosystem has the same height, the same
|
|
12
|
+
press feedback and the same disabled treatment.
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Item } from '@oxyhq/bloom/item';
|
|
16
|
+
|
|
17
|
+
<Item
|
|
18
|
+
leading={<Avatar source={user.avatar} size={32} />}
|
|
19
|
+
title={user.displayName}
|
|
20
|
+
subtitle={`@${user.username}`}
|
|
21
|
+
trailing={selected ? <CheckIcon /> : null}
|
|
22
|
+
onPress={() => choose(user)}
|
|
23
|
+
role="option"
|
|
24
|
+
/>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Which prop decides what
|
|
28
|
+
|
|
29
|
+
| prop | decides |
|
|
30
|
+
| ------------- | --------------------------------------------------------------- |
|
|
31
|
+
| `title` / `subtitle` | the two text lines |
|
|
32
|
+
| `leading` / `trailing` | the slots on either side |
|
|
33
|
+
| `children` | replaces the title/subtitle block entirely |
|
|
34
|
+
| `onPress` | makes the row pressable |
|
|
35
|
+
| `role` | **how it is announced** — see below |
|
|
36
|
+
| `selected` | the selected state, for `option` / `radio` rows |
|
|
37
|
+
| `active` | the "you are here" state, for navigation rows |
|
|
38
|
+
| `density` | `comfortable` (default) or `compact` for a dense list |
|
|
39
|
+
| `destructive` | paints the title in the error role |
|
|
40
|
+
|
|
41
|
+
## `role` is the prop that matters
|
|
42
|
+
|
|
43
|
+
A row looks the same in every context and means something different in each.
|
|
44
|
+
`role` is how you say which:
|
|
45
|
+
|
|
46
|
+
| `role` | for |
|
|
47
|
+
| ---------- | ----------------------------------------------- |
|
|
48
|
+
| `option` | a choice in a listbox — a combobox, a picker |
|
|
49
|
+
| `radio` | a mutually exclusive choice |
|
|
50
|
+
| `menuitem` | an action in a menu |
|
|
51
|
+
| `listitem` | a row in a plain list |
|
|
52
|
+
|
|
53
|
+
Getting this wrong is invisible on screen and wrong in every screen reader: a
|
|
54
|
+
combobox whose rows are `listitem` announces as a list, and the user is never
|
|
55
|
+
told they are choosing.
|
|
56
|
+
|
|
57
|
+
## `selected` and `active` are different states
|
|
58
|
+
|
|
59
|
+
`selected` means *this is the chosen one* — a checkmark in a picker. `active`
|
|
60
|
+
means *this is where you are* — the current route in a sidebar. They look
|
|
61
|
+
similar and they are not the same claim: a sidebar row is active without the
|
|
62
|
+
user having selected anything, and a picker row is selected whether or not it
|
|
63
|
+
is focused.
|
|
64
|
+
|
|
65
|
+
## `destructive` colours, it does not confirm
|
|
66
|
+
|
|
67
|
+
It paints the title in the error role. It does not add a confirmation step —
|
|
68
|
+
if the action is irreversible, `confirm()` from
|
|
69
|
+
[`surfaces`](/docs/bloom/main/surfaces) is what asks.
|
package/docs/kbd.mdx
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Kbd
|
|
3
|
+
description: A keyboard key rendered as a key — in menus, palettes and help text.
|
|
4
|
+
order: 44
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Kbd
|
|
8
|
+
|
|
9
|
+
Renders a keyboard key with the raised-cap look, so a shortcut in running text
|
|
10
|
+
reads as a key rather than as a stray capital letter.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Kbd } from '@oxyhq/bloom/kbd';
|
|
14
|
+
|
|
15
|
+
<Text>
|
|
16
|
+
Press <Kbd>⌘</Kbd> <Kbd>K</Kbd> to open the command palette.
|
|
17
|
+
</Text>
|
|
18
|
+
|
|
19
|
+
<Kbd size="sm">Esc</Kbd>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Which prop decides what
|
|
23
|
+
|
|
24
|
+
| prop | decides |
|
|
25
|
+
| ------ | ------------------------------------------------- |
|
|
26
|
+
| `size` | `sm` for inside a row or a menu, `md` for prose |
|
|
27
|
+
|
|
28
|
+
## One key per `Kbd`
|
|
29
|
+
|
|
30
|
+
`⌘K` as a single child renders one wide cap, which is not what a keyboard looks
|
|
31
|
+
like and not what the user has to press. Two keys, two `Kbd`s.
|
|
32
|
+
|
|
33
|
+
## Use the platform's own symbols
|
|
34
|
+
|
|
35
|
+
`⌘` on Apple platforms, `Ctrl` elsewhere; `⌥` versus `Alt`; `⇧` versus `Shift`.
|
|
36
|
+
A shortcut written for the wrong platform is worse than none — the user presses
|
|
37
|
+
a key that does nothing and concludes the feature is broken.
|
|
38
|
+
|
|
39
|
+
`Kbd` renders what you give it; deciding which spelling to give it belongs to
|
|
40
|
+
the app, which knows the platform.
|
|
41
|
+
|
|
42
|
+
## It is decoration, not a control
|
|
43
|
+
|
|
44
|
+
`Kbd` draws a key. It does not register a shortcut, and pressing it does
|
|
45
|
+
nothing. Binding keys belongs to the app or the surface that owns the key
|
|
46
|
+
handling — the same reason [`Command`](/docs/bloom/main/command)'s `shortcut`
|
|
47
|
+
field is a hint rather than a binding.
|
|
48
|
+
|
|
49
|
+
## Where it earns its place
|
|
50
|
+
|
|
51
|
+
- A [`Command`](/docs/bloom/main/command) row, showing the shortcut for the
|
|
52
|
+
action.
|
|
53
|
+
- A menu item, for the same reason.
|
|
54
|
+
- Help and onboarding text, where the key is the instruction.
|
|
55
|
+
|
|
56
|
+
Not in a tooltip on a touch device: a user with no keyboard reading about `⌘K`
|
|
57
|
+
is being told about a feature they cannot reach.
|
package/docs/label.mdx
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Label
|
|
3
|
+
description: A control's name, wired to the control rather than merely sitting above it.
|
|
4
|
+
order: 33
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Label
|
|
8
|
+
|
|
9
|
+
The text that names a control. Use it when you are laying a form out yourself;
|
|
10
|
+
[`Field`](/docs/bloom/main/field) renders one for you and handles the wiring.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Label } from '@oxyhq/bloom/label';
|
|
14
|
+
|
|
15
|
+
<Label nativeID="workspace-name" required>Workspace name</Label>
|
|
16
|
+
<TextField>
|
|
17
|
+
<TextFieldInput nativeID="workspace-name" label="Workspace name" value={name} onChangeText={setName} />
|
|
18
|
+
</TextField>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Which prop decides what
|
|
22
|
+
|
|
23
|
+
| prop | decides |
|
|
24
|
+
| ---------- | ------------------------------------------------------------- |
|
|
25
|
+
| `nativeID` | the id the control points at — this is what makes it a label |
|
|
26
|
+
| `htmlFor` | the reverse association on web |
|
|
27
|
+
| `required` | the required marker, and that it is announced as required |
|
|
28
|
+
| `disabled` | dims it in step with the control |
|
|
29
|
+
| `size` | `xs` · `sm` · `md` |
|
|
30
|
+
|
|
31
|
+
## Without the wiring it is just text above a box
|
|
32
|
+
|
|
33
|
+
A `<Text>` above an input is not a label: a screen reader reaches the input and
|
|
34
|
+
announces "edit text, blank". The association is the whole feature — either
|
|
35
|
+
through `nativeID`/`htmlFor` here, or by letting `Field` do it.
|
|
36
|
+
|
|
37
|
+
On web it also buys a click target: clicking the label focuses the control.
|
|
38
|
+
|
|
39
|
+
## `required` is a claim, not a decoration
|
|
40
|
+
|
|
41
|
+
The marker and the announced requirement come from the same prop, so they
|
|
42
|
+
cannot disagree. Do not draw your own asterisk beside the text — it is
|
|
43
|
+
invisible to a screen reader and produces a field that looks required and
|
|
44
|
+
announces as optional.
|
|
45
|
+
|
|
46
|
+
## Do not use a label as a heading
|
|
47
|
+
|
|
48
|
+
A label names a control. A section title above a group of fields is a heading;
|
|
49
|
+
using `Label` for it makes a screen reader look for a control that is not
|
|
50
|
+
there.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: LinkPreviewCard
|
|
3
|
+
description: An unfurled link — structural props, so Bloom never depends on the contracts package.
|
|
4
|
+
order: 59
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# LinkPreviewCard
|
|
8
|
+
|
|
9
|
+
The card that renders an unfurled link: cover image, site name, title,
|
|
10
|
+
description.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { LinkPreviewCard } from '@oxyhq/bloom/link-preview';
|
|
14
|
+
|
|
15
|
+
<LinkPreviewCard
|
|
16
|
+
url={preview.url}
|
|
17
|
+
siteName={preview.siteName}
|
|
18
|
+
title={preview.title}
|
|
19
|
+
description={preview.description}
|
|
20
|
+
image={preview.image}
|
|
21
|
+
/>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Which prop decides what
|
|
25
|
+
|
|
26
|
+
| prop | decides |
|
|
27
|
+
| ------------- | ----------------------------------------------------------- |
|
|
28
|
+
| `url` | where it goes, and the fallback for everything else |
|
|
29
|
+
| `title` / `description` / `siteName` / `image` | the unfurled metadata |
|
|
30
|
+
| `coverFill` | whether the cover crops to fill or fits |
|
|
31
|
+
| `onPress` | overrides the default "open the URL" |
|
|
32
|
+
|
|
33
|
+
## Only `url` is required
|
|
34
|
+
|
|
35
|
+
Everything else is optional because unfurling fails all the time — a site with
|
|
36
|
+
no Open Graph tags, a link behind auth, a timeout. The hostname is the fallback
|
|
37
|
+
for both the site name and the title, so a bare URL still renders a usable card
|
|
38
|
+
rather than an empty box with a shadow.
|
|
39
|
+
|
|
40
|
+
Design the calling code for that: pass whatever the unfurler returned, without
|
|
41
|
+
deciding not to render when the metadata is thin.
|
|
42
|
+
|
|
43
|
+
## The shape is structural, not a contracts type
|
|
44
|
+
|
|
45
|
+
The props mirror the platform's `LinkPreview` DTO but are declared
|
|
46
|
+
independently, so Bloom does not depend on `@oxyhq/contracts`. If the DTO gains
|
|
47
|
+
a field, map it at the call site.
|
|
48
|
+
|
|
49
|
+
## `image` is a plain URL, not a file id
|
|
50
|
+
|
|
51
|
+
It is rendered directly with the RN `Image` primitive, **not** through
|
|
52
|
+
[`ImageResolver`](/docs/bloom/main/image-resolver) — an OG image comes from
|
|
53
|
+
whatever host the linked page uses, so there is no Oxy file id to resolve.
|
|
54
|
+
|
|
55
|
+
That is the opposite of the avatar rule, and the reason both are worth stating:
|
|
56
|
+
an avatar goes through the resolver, an OG image does not.
|
|
57
|
+
|
|
58
|
+
## The chrome is `Card`'s
|
|
59
|
+
|
|
60
|
+
`outlined` at the `radius-20` rung. It used to be `bg-card` / `border-border`
|
|
61
|
+
classes, which are **inert on web until the consumer wires the Tailwind
|
|
62
|
+
pipeline** — the card then drew as an unbordered transparent block with no
|
|
63
|
+
error anywhere. As resolved inline tokens it paints either way.
|
|
64
|
+
|
|
65
|
+
The text inside is still className-first, and those class strings must stay
|
|
66
|
+
literal so a consumer's Tailwind content scan over `lib/` can see them.
|
package/docs/list.mdx
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: VirtualList
|
|
3
|
+
description: The one list primitive — windowed on web, virtualized on native, one prop shape.
|
|
4
|
+
order: 52
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# VirtualList
|
|
8
|
+
|
|
9
|
+
Bloom's list. On web it windows against the scroller; on native it is a
|
|
10
|
+
virtualized list. Same props either way.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { VirtualList } from '@oxyhq/bloom/list';
|
|
14
|
+
|
|
15
|
+
<VirtualList
|
|
16
|
+
data={posts}
|
|
17
|
+
keyExtractor={(post) => post.id}
|
|
18
|
+
estimatedItemSize={96}
|
|
19
|
+
renderItem={({ item }) => <PostRow post={item} />}
|
|
20
|
+
ListEmptyComponent={<Empty query={query} />}
|
|
21
|
+
onEndReached={loadMore}
|
|
22
|
+
onEndReachedThreshold={0.5}
|
|
23
|
+
/>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Which prop decides what
|
|
27
|
+
|
|
28
|
+
| prop | decides |
|
|
29
|
+
| ----------------------- | ----------------------------------------------------- |
|
|
30
|
+
| `data` / `renderItem` | what is drawn |
|
|
31
|
+
| `keyExtractor` | identity across updates |
|
|
32
|
+
| `estimatedItemSize` | how the scrollbar behaves before rows are measured |
|
|
33
|
+
| `ListHeaderComponent` / `ListFooterComponent` / `ListEmptyComponent` | the slots |
|
|
34
|
+
| `onEndReached` / `onEndReachedThreshold` | pagination |
|
|
35
|
+
| `refreshing` / `onRefresh` | pull to refresh |
|
|
36
|
+
|
|
37
|
+
## `estimatedItemSize` is why scrolling feels right or wrong
|
|
38
|
+
|
|
39
|
+
It is a hint, but a badly wrong one makes the scrollbar jump as real heights
|
|
40
|
+
replace the estimate. Estimate the **common** row, not the average of a bimodal
|
|
41
|
+
set: for a list that is mostly one-line rows with occasional images, the
|
|
42
|
+
one-line height is the better answer even though it is nowhere near the mean.
|
|
43
|
+
|
|
44
|
+
## `ListEmptyComponent` is the one to wire first
|
|
45
|
+
|
|
46
|
+
An unstyled empty list is indistinguishable from a list that failed to load.
|
|
47
|
+
The empty state should say which — "No results for *bloom*" is a different
|
|
48
|
+
message from "Couldn't load your feed".
|
|
49
|
+
|
|
50
|
+
## `onEndReached` can fire while a page is in flight
|
|
51
|
+
|
|
52
|
+
It fires once per approach to the end, not once per scroll event — but a second
|
|
53
|
+
approach can happen before the first request resolves. Guard on your own
|
|
54
|
+
loading flag; the list does not know what your fetch is doing.
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
onEndReached={() => { if (!isFetching && hasMore) fetchNext(); }}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## `keyExtractor` is not optional in practice
|
|
61
|
+
|
|
62
|
+
Without stable keys, rows recycle onto the wrong data: a list that reorders
|
|
63
|
+
shows the previous row's content for a frame, and inputs inside rows keep the
|
|
64
|
+
wrong value. Use the entity's id, never the index.
|
|
65
|
+
|
|
66
|
+
## Native tuning props
|
|
67
|
+
|
|
68
|
+
`removeClippedSubviews`, `maxToRenderPerBatch`, `windowSize`,
|
|
69
|
+
`initialNumToRender`, `recycleItems` and `maintainVisibleContentPosition` are
|
|
70
|
+
honoured on native where applicable and accepted-but-ignored on web, where the
|
|
71
|
+
window virtualizer manages its own mounted-row budget. They are on the type so
|
|
72
|
+
one call site type-checks on both platforms.
|
package/docs/loading.mdx
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Loading
|
|
3
|
+
description: Spinner, top bar, and inline loading indicators with one consistent API.
|
|
4
|
+
order: 13
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Loading
|
|
8
|
+
|
|
9
|
+
One component, three variants. Pick the right one for the affordance:
|
|
10
|
+
|
|
11
|
+
- `spinner` — centered, optional text, the default
|
|
12
|
+
- `top` — sliding bar at the top of the screen for global progress
|
|
13
|
+
- `inline` — small spinner with a text label, for inline buttons or rows
|
|
14
|
+
|
|
15
|
+
For content placeholders reach for the `Skeleton` family
|
|
16
|
+
(`@oxyhq/bloom/skeleton`), which actually shimmers. `Loading` never had a real
|
|
17
|
+
skeleton — its `skeleton` variant drew static rectangles under the same name —
|
|
18
|
+
so the variant is gone.
|
|
19
|
+
|
|
20
|
+
## Spinner (default)
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { Loading } from '@oxyhq/bloom';
|
|
24
|
+
|
|
25
|
+
<Loading variant="spinner" />
|
|
26
|
+
<Loading variant="spinner" size="large" />
|
|
27
|
+
<Loading variant="spinner" text="Loading…" />
|
|
28
|
+
<Loading variant="spinner" text="Saving…" iconSize={32} />
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Top bar
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<Loading variant="top" showLoading={isFetching} />
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Renders a fixed-height bar at the top of the parent. Use it as a global progress indicator (route transitions, background sync). Requires `react-native-reanimated`.
|
|
38
|
+
|
|
39
|
+
## Inline
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
<Loading variant="inline" text="Connecting…" />
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Spinner + label rendered in a row — small enough to drop into a list row or a button.
|
|
46
|
+
|
|
47
|
+
## NativeWind className
|
|
48
|
+
|
|
49
|
+
For native-tailwind users, `className` overrides `color`:
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<Loading variant="spinner" className="text-primary" />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Props
|
|
56
|
+
|
|
57
|
+
### Shared
|
|
58
|
+
|
|
59
|
+
| Prop | Type | Description |
|
|
60
|
+
|------|------|-------------|
|
|
61
|
+
| `variant?` | `'spinner' \| 'top' \| 'inline'` | Default: `'spinner'`. |
|
|
62
|
+
| `size?` | `'small' \| 'medium' \| 'large'` | Default: `'medium'`. |
|
|
63
|
+
| `color?` | `string` | Spinner color. Defaults to theme primary. |
|
|
64
|
+
| `className?` | `string` | NativeWind class. Overrides `color`. |
|
|
65
|
+
| `style?` | `ViewStyle` | Container style. |
|
|
66
|
+
| `showLoading?` | `boolean` | For animated variants — pause when false. |
|
|
67
|
+
| `testID?` | `string` | |
|
|
68
|
+
|
|
69
|
+
### `spinner`
|
|
70
|
+
|
|
71
|
+
| Prop | Type | Description |
|
|
72
|
+
|------|------|-------------|
|
|
73
|
+
| `text?` | `string` | Optional label rendered under the spinner. |
|
|
74
|
+
| `textStyle?` | `TextStyle` | Label style. |
|
|
75
|
+
| `showText?` | `boolean` | Hide the label without removing layout. |
|
|
76
|
+
| `iconSize?` | `number` | Override the size prop's default icon px. |
|
|
77
|
+
| `spinnerIcon?` | `React.ReactNode` | Custom spinner (defaults to `ActivityIndicator`). |
|
|
78
|
+
|
|
79
|
+
### `top`
|
|
80
|
+
|
|
81
|
+
| Prop | Type | Description |
|
|
82
|
+
|------|------|-------------|
|
|
83
|
+
| `iconSize?` | `number` | |
|
|
84
|
+
| `heightOffset?` | `number` | Offset from the top edge. |
|
|
85
|
+
| `spinnerIcon?` | `React.ReactNode` | |
|
|
86
|
+
|
|
87
|
+
### `inline`
|
|
88
|
+
|
|
89
|
+
| Prop | Type | Description |
|
|
90
|
+
|------|------|-------------|
|
|
91
|
+
| `text?` | `string` | Label text. |
|
|
92
|
+
| `textStyle?` | `TextStyle` | |
|
|
93
|
+
| `spinnerIcon?` | `React.ReactNode` | |
|
|
94
|
+
|
|
95
|
+
## Recipes
|
|
96
|
+
|
|
97
|
+
### Lazy content placeholders
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import * as Skeleton from '@oxyhq/bloom/skeleton';
|
|
101
|
+
|
|
102
|
+
const { data, isPending } = useQuery({ queryKey: ['articles'], queryFn });
|
|
103
|
+
|
|
104
|
+
if (isPending) {
|
|
105
|
+
return (
|
|
106
|
+
<Skeleton.Col style={{ gap: 8 }}>
|
|
107
|
+
{Array.from({ length: 5 }, (_, i) => <Skeleton.Text key={i} />)}
|
|
108
|
+
</Skeleton.Col>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return <ArticleList articles={data} />;
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Button busy state
|
|
115
|
+
|
|
116
|
+
Most of the time you want `<Button loading>` instead of an inline `Loading` — it preserves width. Reach for `inline` only when you can't use `Button`.
|
|
117
|
+
|
|
118
|
+
### Global top progress
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import { useIsFetching, useIsMutating } from '@tanstack/react-query';
|
|
122
|
+
|
|
123
|
+
function GlobalProgress() {
|
|
124
|
+
const fetches = useIsFetching();
|
|
125
|
+
const mutations = useIsMutating();
|
|
126
|
+
const busy = fetches > 0 || mutations > 0;
|
|
127
|
+
return <Loading variant="top" showLoading={busy} />;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MediaInsetBorder
|
|
3
|
+
description: A hairline drawn INSIDE a media edge, so a white photo still has a boundary.
|
|
4
|
+
order: 66
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# MediaInsetBorder
|
|
8
|
+
|
|
9
|
+
Draws a hairline just inside its own bounds, over whatever it wraps.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { MediaInsetBorder } from '@oxyhq/bloom/media-inset-border';
|
|
13
|
+
|
|
14
|
+
<MediaInsetBorder style={{ borderRadius: 12 }}>
|
|
15
|
+
<Image source={photo} style={{ width: 320, height: 200 }} />
|
|
16
|
+
</MediaInsetBorder>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Which prop decides what
|
|
20
|
+
|
|
21
|
+
| prop | decides |
|
|
22
|
+
| -------- | -------------------------------------------------------------- |
|
|
23
|
+
| `opaque` | whether the hairline is solid rather than a translucent overlay |
|
|
24
|
+
|
|
25
|
+
## The problem it solves
|
|
26
|
+
|
|
27
|
+
A photo whose edges are white — a screenshot, a product shot on a light
|
|
28
|
+
background, a diagram — has no visible boundary on a light page. It bleeds into
|
|
29
|
+
the surface and the layout looks broken rather than seamless.
|
|
30
|
+
|
|
31
|
+
A border on the container does not fix it, because the image paints *over* the
|
|
32
|
+
container's inner edge at the corners. This draws the line on top, inside the
|
|
33
|
+
media's own bounds, so it survives the image's own corners and any clipping.
|
|
34
|
+
|
|
35
|
+
## It goes around media, not around content
|
|
36
|
+
|
|
37
|
+
For a boundary around a text block or a group of controls, use
|
|
38
|
+
[`Card`](/docs/bloom/main/card)'s `outlined` variant or
|
|
39
|
+
[`Divider`](/docs/bloom/main/divider). This component exists specifically for
|
|
40
|
+
the case where the *content itself* can be the same colour as the page.
|
|
41
|
+
|
|
42
|
+
## Match the radius
|
|
43
|
+
|
|
44
|
+
The inset border takes its shape from the style you give it, so a rounded photo
|
|
45
|
+
needs the same `borderRadius` here as on the image. A mismatch shows as a
|
|
46
|
+
hairline that clips the corners — subtle, and worse than no border.
|
|
47
|
+
|
|
48
|
+
## `opaque`
|
|
49
|
+
|
|
50
|
+
Use the translucent default over photos, where a solid line reads as a frame.
|
|
51
|
+
Reach for `opaque` where the media is flat colour and the translucent line
|
|
52
|
+
disappears into it.
|
package/docs/menubar.mdx
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Menubar
|
|
3
|
+
description: A horizontal bar of menus, at most one open at a time.
|
|
4
|
+
order: 20
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Menubar
|
|
8
|
+
|
|
9
|
+
A desktop-style menu bar: a row of triggers, each opening a menu, with at most one open at a time.
|
|
10
|
+
|
|
11
|
+
New to Bloom in this release. Its parts are shadcn's, and its rows are the same rows `DropdownMenu` and `ContextMenu` publish.
|
|
12
|
+
|
|
13
|
+
## Basic
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import {
|
|
17
|
+
Menubar,
|
|
18
|
+
MenubarContent,
|
|
19
|
+
MenubarItem,
|
|
20
|
+
MenubarMenu,
|
|
21
|
+
MenubarSeparator,
|
|
22
|
+
MenubarShortcut,
|
|
23
|
+
MenubarTrigger,
|
|
24
|
+
} from '@oxyhq/bloom/menubar';
|
|
25
|
+
|
|
26
|
+
<Menubar>
|
|
27
|
+
<MenubarMenu value="file">
|
|
28
|
+
<MenubarTrigger>File</MenubarTrigger>
|
|
29
|
+
<MenubarContent>
|
|
30
|
+
<MenubarItem trailing={<MenubarShortcut>⌘N</MenubarShortcut>} onPress={newTab}>
|
|
31
|
+
New tab
|
|
32
|
+
</MenubarItem>
|
|
33
|
+
<MenubarItem onPress={open}>Open…</MenubarItem>
|
|
34
|
+
<MenubarSeparator />
|
|
35
|
+
<MenubarItem variant="destructive" onPress={closeWindow}>
|
|
36
|
+
Close window
|
|
37
|
+
</MenubarItem>
|
|
38
|
+
</MenubarContent>
|
|
39
|
+
</MenubarMenu>
|
|
40
|
+
|
|
41
|
+
<MenubarMenu value="edit">
|
|
42
|
+
<MenubarTrigger>Edit</MenubarTrigger>
|
|
43
|
+
<MenubarContent>
|
|
44
|
+
<MenubarItem onPress={undo}>Undo</MenubarItem>
|
|
45
|
+
<MenubarItem onPress={redo}>Redo</MenubarItem>
|
|
46
|
+
</MenubarContent>
|
|
47
|
+
</MenubarMenu>
|
|
48
|
+
</Menubar>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Each `MenubarMenu` needs a `value`. That value IS the open state.
|
|
52
|
+
|
|
53
|
+
## One open menu, one state
|
|
54
|
+
|
|
55
|
+
`Menubar` owns which menu is open, and each `MenubarMenu` derives its own boolean from it:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
const [open, setOpen] = useState<string | undefined>(undefined);
|
|
59
|
+
|
|
60
|
+
<Menubar value={open} onValueChange={setOpen}>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Uncontrolled by default (`defaultValue` sets the initially-open menu; omit it for none). Switching menus is a single state write, so there is never a frame with both open or neither.
|
|
64
|
+
|
|
65
|
+
## Presentation
|
|
66
|
+
|
|
67
|
+
`MenubarTrigger` draws its own chrome — `rounded-md px-2 py-1.5` with a `text-sm font-medium` label, washed with the accent colour while its menu is open. Pass a string and it is set for you; pass nodes and they go inside that button; pass `asChild` and your own element replaces the whole thing.
|
|
68
|
+
|
|
69
|
+
On web each menu is an anchored panel under its own trigger, aligned to the trigger's left edge. On native each is the bottom sheet the other menu families use — a phone has no room for a hanging panel, and every Bloom overlay resolving to the same sheet is what makes the native surfaces feel like one system.
|
|
70
|
+
|
|
71
|
+
Pressing an open menu's trigger closes it.
|
|
72
|
+
|
|
73
|
+
## Rows
|
|
74
|
+
|
|
75
|
+
`MenubarItem`, `MenubarCheckboxItem`, `MenubarRadioGroup` / `MenubarRadioItem`, `MenubarLabel`, `MenubarSeparator`, `MenubarShortcut`, `MenubarGroup`, and `MenubarSub` / `MenubarSubTrigger` / `MenubarSubContent`.
|
|
76
|
+
|
|
77
|
+
Sub-menus fly out beside their trigger row on web and render as an inline disclosure on native — see [DropdownMenu](/docs/bloom/main/dropdown-menu).
|
|
78
|
+
|
|
79
|
+
## What is not here yet
|
|
80
|
+
|
|
81
|
+
Two things a desktop menu bar has that this one does not, both deliberate rather than forgotten:
|
|
82
|
+
|
|
83
|
+
- **Hover to switch.** On a real desktop bar, once one menu is open, moving the pointer across a sibling trigger switches to it without a click. That needs hover intent, which is the same machinery a sub-menu flyout needs.
|
|
84
|
+
- **Arrow-key navigation between menus.** Roving focus across the bar and into the open panel.
|
|
85
|
+
|
|
86
|
+
A press opens, a press closes, and every row is reachable by tab. Nothing is half-wired.
|