@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
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ContextMenu
|
|
3
|
+
description: The menu a right-click opens on web and a long press opens on native.
|
|
4
|
+
order: 16
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ContextMenu
|
|
8
|
+
|
|
9
|
+
The secondary menu for an object on screen. Right-click on web, long press on native.
|
|
10
|
+
|
|
11
|
+
Its parts are shadcn's, and its rows are the SAME rows `DropdownMenu` and `Menubar` publish — one implementation under three prefixes, so a row behaves identically wherever it is.
|
|
12
|
+
|
|
13
|
+
## Basic
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import {
|
|
17
|
+
ContextMenu,
|
|
18
|
+
ContextMenuContent,
|
|
19
|
+
ContextMenuItem,
|
|
20
|
+
ContextMenuSeparator,
|
|
21
|
+
ContextMenuShortcut,
|
|
22
|
+
ContextMenuTrigger,
|
|
23
|
+
} from '@oxyhq/bloom/context-menu';
|
|
24
|
+
|
|
25
|
+
<ContextMenu>
|
|
26
|
+
<ContextMenuTrigger label="Photo actions">
|
|
27
|
+
<Image source={photo.source} style={styles.thumb} />
|
|
28
|
+
</ContextMenuTrigger>
|
|
29
|
+
|
|
30
|
+
<ContextMenuContent>
|
|
31
|
+
<ContextMenuItem
|
|
32
|
+
onPress={() => share(photo)}
|
|
33
|
+
trailing={<ContextMenuShortcut>⌘S</ContextMenuShortcut>}>
|
|
34
|
+
Share
|
|
35
|
+
</ContextMenuItem>
|
|
36
|
+
<ContextMenuItem onPress={() => save(photo)}>Save to library</ContextMenuItem>
|
|
37
|
+
<ContextMenuSeparator />
|
|
38
|
+
<ContextMenuItem variant="destructive" onPress={() => remove(photo)}>
|
|
39
|
+
Delete
|
|
40
|
+
</ContextMenuItem>
|
|
41
|
+
</ContextMenuContent>
|
|
42
|
+
</ContextMenu>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`asChild` composes the trigger onto your own element instead of wrapping it, exactly as it does on `DropdownMenu`.
|
|
46
|
+
|
|
47
|
+
## It opens where the pointer is
|
|
48
|
+
|
|
49
|
+
On web the menu anchors to the CLICK POINT, not to the trigger's box: right-clicking the bottom-right corner of a large card opens the menu there, not under the card's top-left. A click point is a zero-area anchor, which the same fit / flip / clamp arithmetic every other Bloom surface uses already handles.
|
|
50
|
+
|
|
51
|
+
On native there is no cursor to anchor to, so a long press presents the same bottom sheet `DropdownMenu` uses.
|
|
52
|
+
|
|
53
|
+
## No `open` prop, on purpose
|
|
54
|
+
|
|
55
|
+
`ContextMenu` is uncontrolled by construction. Its open POSITION comes from the pointer event that opened it, so there is no `open` a caller could set that would mean anything — Radix's `ContextMenu.Root` takes the same view. What you get instead is `onOpenChange`:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
<ContextMenu onOpenChange={(open) => setDimmed(open)}>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Rows
|
|
62
|
+
|
|
63
|
+
Everything `DropdownMenu` documents applies, under the `ContextMenu` prefix: `ContextMenuItem`, `ContextMenuCheckboxItem`, `ContextMenuRadioGroup` / `ContextMenuRadioItem`, `ContextMenuLabel`, `ContextMenuSeparator`, `ContextMenuShortcut`, `ContextMenuGroup`, and `ContextMenuSub` / `ContextMenuSubTrigger` / `ContextMenuSubContent`.
|
|
64
|
+
|
|
65
|
+
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) for the hover, keyboard and placement behaviour, which is shared.
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Design Tokens
|
|
3
|
+
description: The centralized semantic Tailwind / NativeWind vocabulary shared by every Oxy app.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Design Tokens
|
|
7
|
+
|
|
8
|
+
`@oxyhq/bloom/design-tokens` ships the **Oxy Unified Design Language**: a
|
|
9
|
+
namespaced, semantic set of utility classes (`bg-fill`, `text-text-tertiary`,
|
|
10
|
+
`p-space-8`, `rounded-radius-20`, `text-body`, `font-body`, `shadow-s`) that
|
|
11
|
+
resolve to the SAME class names on **web (Tailwind)** and **native (NativeWind)**.
|
|
12
|
+
|
|
13
|
+
Color roles are aliases onto Bloom's existing resolved theme tokens
|
|
14
|
+
(`--background`, `--surface`, `--primary`, …), so they follow the active
|
|
15
|
+
light/dark mode and color preset automatically. The vocabulary is **additive** —
|
|
16
|
+
existing utilities (`bg-background`, `text-muted-foreground`, `rounded-lg`) keep
|
|
17
|
+
working.
|
|
18
|
+
|
|
19
|
+
## Preset recipes and runtime colour
|
|
20
|
+
|
|
21
|
+
Bloom ships **34 named recipes**, authored once in the type-checked
|
|
22
|
+
`COLOR_PRESET_REGISTRY`. A recipe is not a frozen light/dark token table: it is
|
|
23
|
+
an identity seed, a scheme variant and optionally explicit secondary/tertiary
|
|
24
|
+
seeds. `BloomThemeProvider`, `getResolvedTokens()` and `BloomColorScope` run
|
|
25
|
+
those seeds through the same colour policy for the active mode and scope.
|
|
26
|
+
Arbitrary consumer seeds remain supported through `buildThemeFromSeed()` and
|
|
27
|
+
`BloomSeedScope`; the named catalog is a set of useful starting points, not the
|
|
28
|
+
limit of the dynamic engine.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import {
|
|
32
|
+
COLOR_PRESET_REGISTRY,
|
|
33
|
+
COLOR_PRESET_FAMILY_REGISTRY,
|
|
34
|
+
COLOR_PRESET_GROUPS,
|
|
35
|
+
} from '@oxyhq/bloom';
|
|
36
|
+
|
|
37
|
+
const cobalt = COLOR_PRESET_REGISTRY.find((recipe) => recipe.name === 'cobalt');
|
|
38
|
+
cobalt?.displayName; // 'Cobalt + Signal Yellow'
|
|
39
|
+
cobalt?.family; // 'ocean'
|
|
40
|
+
cobalt?.tertiaryHex; // '#ffd000'
|
|
41
|
+
|
|
42
|
+
COLOR_PRESET_FAMILY_REGISTRY[0].displayName; // stable family copy and order
|
|
43
|
+
COLOR_PRESET_GROUPS.ocean.presets; // ordered recipes for a picker
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The 16 `pairing: 'curated'` recipes pin a complementary `tertiaryHex`. The
|
|
47
|
+
identity remains `--primary`; the standout action is `--tertiary`, which is the
|
|
48
|
+
role used by FABs and compose actions. The other 18 recipes derive their accent
|
|
49
|
+
families dynamically. Every recipe exposes `displayName`, `description`,
|
|
50
|
+
`family`, `pairing` and `featured` metadata, so consumer pickers do not need a
|
|
51
|
+
second name/category map. Historical `APP_COLOR_PRESETS`, name arrays, hex
|
|
52
|
+
lookup and access-gate arrays are derived from this registry and retain their
|
|
53
|
+
existing public shapes.
|
|
54
|
+
|
|
55
|
+
For visual review, run Storybook and open **Theme / Color System Playground**
|
|
56
|
+
(`src/theme/ColorSystemLab.stories.tsx`). It keeps signed-in and public Mention
|
|
57
|
+
views, renders both modes from `getResolvedTokens()`, and filters the complete
|
|
58
|
+
registry by family or curated/derived pairing.
|
|
59
|
+
|
|
60
|
+
## Opt in
|
|
61
|
+
|
|
62
|
+
### Native (NativeWind) and Tailwind v3 web
|
|
63
|
+
|
|
64
|
+
Add the Bloom preset to your `tailwind.config.js`:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
const { bloomTailwindPreset } = require('@oxyhq/bloom/tailwind-preset');
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
presets: [bloomTailwindPreset],
|
|
71
|
+
content: [/* your existing globs */],
|
|
72
|
+
theme: { extend: {/* your existing app overrides */} },
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
That single line adds every semantic utility. Your `BloomThemeProvider` already
|
|
77
|
+
populates the underlying `--x` color tokens at runtime.
|
|
78
|
+
|
|
79
|
+
### Tailwind v4 web (auth, console, website) and CSS-first NativeWind (Homiio)
|
|
80
|
+
|
|
81
|
+
Tailwind v4 apps configure tokens in CSS. **Import Bloom's shipped theme CSS —
|
|
82
|
+
never paste the tokens into your own `global.css`.** Bloom ships a static
|
|
83
|
+
`theme.css` (generated from the same source as `bloomThemeCss()`, so JS and CSS
|
|
84
|
+
cannot drift) at a dedicated export:
|
|
85
|
+
|
|
86
|
+
```css
|
|
87
|
+
/* your global.css — after the Tailwind import */
|
|
88
|
+
@import "tailwindcss";
|
|
89
|
+
@import "@oxyhq/bloom/design-tokens/theme.css";
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
That single `@import` registers the full Bloom `@theme` vocabulary (color-role
|
|
93
|
+
aliases, spacing, radius, border-width, typography, shadow), so utilities like
|
|
94
|
+
`rounded-radius-28`, `p-space-8`, and `text-body` resolve without hand-copying
|
|
95
|
+
any `--radius-radius-*` / `--spacing-*` block. The aliases are
|
|
96
|
+
`--color-<role>: var(--canonical)` — direct `var(--x)` references (never
|
|
97
|
+
`hsl(var(--x))`, per Bloom's web CSS-var contract).
|
|
98
|
+
|
|
99
|
+
Keep only your **app-local** color seeds / `:root` overrides in `global.css`.
|
|
100
|
+
Do NOT re-declare Bloom's radius or spacing scales locally — the imported CSS is
|
|
101
|
+
the single authority.
|
|
102
|
+
|
|
103
|
+
If you need the block inline (e.g. an app that assembles its stylesheet at build
|
|
104
|
+
time) rather than via `@import`, `bloomThemeBlock()` / `bloomThemeCss()` return
|
|
105
|
+
the exact same content programmatically:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { bloomThemeBlock } from '@oxyhq/bloom/design-tokens';
|
|
109
|
+
// write `bloomThemeBlock()` into your generated global.css
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### The pre-JS palette (prerendered apps)
|
|
113
|
+
|
|
114
|
+
`theme.css` is the **alias** layer: `--color-background: var(--background)`. It
|
|
115
|
+
says nothing about what `--background` *is* — `BloomThemeProvider` writes that at
|
|
116
|
+
startup. So between first paint and hydration, a prerendered page shows whatever
|
|
117
|
+
palette its own stylesheet declares, and a hand-written one is a second source of
|
|
118
|
+
truth that drifts from the preset it claims to mirror.
|
|
119
|
+
|
|
120
|
+
Generate it instead, at build time:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { getPresetVars, buildSeedScopeVars } from '@oxyhq/bloom/design-tokens';
|
|
124
|
+
|
|
125
|
+
// Document root: canonical tokens are all it needs.
|
|
126
|
+
getPresetVars('oxy', 'dark'); // { '--background': 'rgb(16 13 16)', … }
|
|
127
|
+
|
|
128
|
+
// A scoped block — a brand section, a forced-dark region — needs the
|
|
129
|
+
// `--color-x` aliases too, because an alias substitutes where it is DECLARED:
|
|
130
|
+
// overriding `--background` on a subtree does not move a `--color-background`
|
|
131
|
+
// declared at `:root`. That is why hand-written brand themes write every colour
|
|
132
|
+
// twice. `buildSeedScopeVars` returns both namespaces from one seed.
|
|
133
|
+
buildSeedScopeVars({ seed: '#7c5aed', mode: 'dark' });
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`buildSeedScopeVars` also writes the ROLE vocabulary (`withScopeAliases`)
|
|
137
|
+
alongside the colour aliases — omitting it is what makes a scoped block paint
|
|
138
|
+
near-black text over a dark photo in forced-dark mode, since the role that
|
|
139
|
+
resolves foreground-over-fill never got scoped with everything else.
|
|
140
|
+
|
|
141
|
+
A preset's own `hex` + `variant` reproduces `getPresetVars` token-for-token, so
|
|
142
|
+
the document root and a brand scope go through the same code path. Both
|
|
143
|
+
functions are pure — no react, no react-native — so a plain node/bun script can
|
|
144
|
+
import them.
|
|
145
|
+
|
|
146
|
+
### Consumers that are not browsers
|
|
147
|
+
|
|
148
|
+
A consumer that cannot run a stylesheet — native UI code, a design tool, a
|
|
149
|
+
codegen step — reads the resolved tokens as data instead. Bloom ships them in
|
|
150
|
+
W3C Design Tokens (DTCG) format, generated from the same colour engine, so the
|
|
151
|
+
JSON and the CSS cannot disagree:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import tokens from '@oxyhq/bloom/design-tokens/tokens.json';
|
|
155
|
+
|
|
156
|
+
tokens.color.oxy.dark.card.$value; // '#3b323c' — the value of --card
|
|
157
|
+
tokens.color.teal.light.primary.$value; // '#1d6a75'
|
|
158
|
+
tokens.spacing['space-8'].$value; // '8px'
|
|
159
|
+
tokens.typography.body.fontWeight.$value; // 400
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The JSON is an interoperability artifact generated by `generate:tokens-json`,
|
|
163
|
+
not an editable source. Each preset group also carries a `so.oxy.bloom`
|
|
164
|
+
extension with its seed recipe and picker metadata (family, description,
|
|
165
|
+
pairing, featured and optional secondary/tertiary seeds). Keeping the authored
|
|
166
|
+
registry in TypeScript gives duplicate/shape checks and inferred identifier
|
|
167
|
+
types; generating JSON from it gives non-TypeScript tools the same data without
|
|
168
|
+
introducing a second catalog.
|
|
169
|
+
|
|
170
|
+
`color.<preset>.<scheme>.<token>` covers every preset Bloom ships and both
|
|
171
|
+
schemes. Token names are the CSS custom properties with the leading `--`
|
|
172
|
+
removed, so the mapping to `getPresetVars` output is mechanical. Values are
|
|
173
|
+
sRGB hex — `#rrggbb`, or `#rrggbbaa` for the translucent `-subtle` family.
|
|
174
|
+
Font families and shadows are deliberately absent: families resolve to
|
|
175
|
+
`var(--bloom-font-*)`, which is meaningless off the web, and shadows ship as a
|
|
176
|
+
platform-forked style object (`bloomShadowStyle`). If you need the same data in
|
|
177
|
+
memory rather than from the file, `bloomDesignTokens()` from
|
|
178
|
+
`@oxyhq/bloom/design-tokens` returns it.
|
|
179
|
+
|
|
180
|
+
## Color roles
|
|
181
|
+
|
|
182
|
+
| Utility | Role | Canonical token |
|
|
183
|
+
| --- | --- | --- |
|
|
184
|
+
| `bg-bg` | page background | `--background` |
|
|
185
|
+
| `bg-fill` | default surface | `--card` |
|
|
186
|
+
| `bg-fill-secondary` | muted fill | `--muted` |
|
|
187
|
+
| `bg-fill-hover` | secondary hover | `--accent` |
|
|
188
|
+
| `bg-fill-brand` | brand fill | `--primary` |
|
|
189
|
+
| `bg-fill-brand-hover` | brand hover | `--ring` |
|
|
190
|
+
| `bg-fill-inverse` | inverse fill | `--foreground` |
|
|
191
|
+
| `bg-fill-inverse-hover` | inverse hover | `--muted-foreground` |
|
|
192
|
+
| `bg-fill-placeholder` | disabled surface | `--muted` |
|
|
193
|
+
| `text-text` | primary text | `--foreground` |
|
|
194
|
+
| `text-text-secondary` | secondary text | `--muted-foreground` |
|
|
195
|
+
| `text-text-tertiary` | tertiary text | `--muted-foreground` |
|
|
196
|
+
| `text-text-inverse` | text on brand | `--primary-foreground` |
|
|
197
|
+
| `text-text-fixed-light` | always-light text | `--primary-foreground` |
|
|
198
|
+
| `text-text-placeholder` | placeholder text | `--muted-foreground` |
|
|
199
|
+
| `border-border` | default border | `--border` |
|
|
200
|
+
| `border-border-image` | hairline color | `--border` |
|
|
201
|
+
| `border-border-secondary` | softer border | `--input` |
|
|
202
|
+
| `border-border-input` | input border | `--input` |
|
|
203
|
+
| `border-border-input-active` | focused input | `--ring` |
|
|
204
|
+
|
|
205
|
+
## The neutral surface ramp
|
|
206
|
+
|
|
207
|
+
`--background`, `--surface`, `--popover`, `--muted`/`--accent` and `--card` are a
|
|
208
|
+
stack: several components carry meaning **only** in the step between two of them —
|
|
209
|
+
an `ActivityHeatmap` empty cell, `Card variant="filled"`, a `Code` block, the
|
|
210
|
+
`StatBar` and `CompositionBar` tracks, `DotGridMeter`'s off dots, a filled `Tabs`
|
|
211
|
+
track, a selected `Item`, the solid `TabBar`, a plain `Card` on a panel, the
|
|
212
|
+
`Toast` action, and the `Avatar`/`BenefitList`/`LinkPreviewCard` placeholders.
|
|
213
|
+
|
|
214
|
+
Those tones are owned by `SURFACE_RAMP` in `theme/color-policy.ts` rather than
|
|
215
|
+
taken from Material 3's container roles. M3's ramp steps by 2 tones, which at
|
|
216
|
+
these lightnesses is about **1.2 ΔE00** — below the just-noticeable difference for
|
|
217
|
+
two large flat patches. Measured across all 34 presets, **4 tones (~2.4 ΔE00)** is
|
|
218
|
+
the first spacing at which every pair separates.
|
|
219
|
+
|
|
220
|
+
`--background` is deliberately not part of the ramp: the page tone is every
|
|
221
|
+
consuming app's page colour, so the rungs below it carry the spacing instead.
|
|
222
|
+
`--card` is not either — it sits on the far side of the page in light (tone 100,
|
|
223
|
+
white) and at the top of the ramp in dark, so it is expressed as the engine role
|
|
224
|
+
that means exactly that in each mode.
|
|
225
|
+
|
|
226
|
+
If you move any of these, measure **every** pair rather than the one you are
|
|
227
|
+
fixing: the light tones 92/94/96/98/100 are exactly `--muted`, `--popover`,
|
|
228
|
+
`--surface`, `--content-area` and `--card`, so any new value for one lands on
|
|
229
|
+
another unless you check.
|
|
230
|
+
|
|
231
|
+
## Scales
|
|
232
|
+
|
|
233
|
+
- **Spacing** (px): `space-2/4/8/12/16/20/24/32` + `screen-margin` (20).
|
|
234
|
+
`p-space-8`, `gap-space-20`, `px-screen-margin`.
|
|
235
|
+
- **Radius** (px): `radius-8/12/20/28/max`. `rounded-radius-20`.
|
|
236
|
+
- **Hairline border**: `border-hairline` (0.5px), pair with `border-border-image`.
|
|
237
|
+
- **Typography**: `text-<role>` (size/line-height) + `font-<role>`
|
|
238
|
+
(family/weight) for `caption, bodySmall, bodyTitleSmall, body, subtitle,
|
|
239
|
+
sectionTitle, headerBold, buttonLarge`.
|
|
240
|
+
- **Shadow**: `shadow-s`, `shadow-m` (web box-shadow). On native surfaces apply
|
|
241
|
+
`bloomShadowStyle('s' | 'm')` (returns RN elevation/shadow props).
|
|
242
|
+
|
|
243
|
+
### Type scale
|
|
244
|
+
|
|
245
|
+
| Role | Size / line | Weight | Family |
|
|
246
|
+
| --- | --- | --- | --- |
|
|
247
|
+
| `caption` | 11 / 14 | 400 | sans |
|
|
248
|
+
| `bodySmall` | 13 / 18 | 400 | sans |
|
|
249
|
+
| `bodyTitleSmall` | 13 / 18 | 600 | sans |
|
|
250
|
+
| `body` | 15 / 22 | 400 | sans |
|
|
251
|
+
| `subtitle` | 17 / 24 | 500 | sans |
|
|
252
|
+
| `sectionTitle` | 20 / 26 | 600 | sans |
|
|
253
|
+
| `headerBold` | 28 / 34 | 700 | display |
|
|
254
|
+
| `buttonLarge` | 17 / 22 | 600 | sans |
|
package/docs/dialog.mdx
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Dialog
|
|
3
|
+
description: A single <Dialog> for every overlay surface — centered modal, side-sheet, or bottom-sheet.
|
|
4
|
+
order: 3
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Dialog
|
|
8
|
+
|
|
9
|
+
A single `<Dialog>` component for every overlay surface — centered modal, side-sheet (left/right drawer), or bottom-sheet — controlled by the `placement` prop. Same component, same props on every platform.
|
|
10
|
+
|
|
11
|
+
<Callout variant="warning" title="Required providers (native)">
|
|
12
|
+
Your app root **must** be wrapped with `GestureHandlerRootView` from `react-native-gesture-handler` for the bottom-sheet pan gestures to work. See [Getting started](/docs/bloom/main/getting-started).
|
|
13
|
+
</Callout>
|
|
14
|
+
|
|
15
|
+
## Declarative (the 90% case)
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { Dialog, useDialogControl, Button } from '@oxyhq/bloom';
|
|
19
|
+
|
|
20
|
+
function SignOutButton() {
|
|
21
|
+
const control = useDialogControl();
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<Button onPress={() => control.open()}>Sign out</Button>
|
|
25
|
+
|
|
26
|
+
<Dialog
|
|
27
|
+
control={control}
|
|
28
|
+
title="Sign out?"
|
|
29
|
+
description="You'll need to enter your password to sign in again."
|
|
30
|
+
actions={[
|
|
31
|
+
{ label: 'Sign out', color: 'destructive', onPress: doSignOut },
|
|
32
|
+
{ label: 'Cancel', color: 'cancel' },
|
|
33
|
+
]}
|
|
34
|
+
/>
|
|
35
|
+
</>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Controlled open state
|
|
41
|
+
|
|
42
|
+
<Callout variant="warning" title="Prefer useDialogControl">
|
|
43
|
+
The controlled path fires `onClose` synchronously on `open` flipping to `false` — ahead of the exit animation. If your `onClose` handler unmounts the consumer (navigates away, removes the dialog from the tree), it races the animation still in flight. `useDialogControl()` doesn't have this footgun: it closes imperatively and only resolves once the exit finishes.
|
|
44
|
+
</Callout>
|
|
45
|
+
|
|
46
|
+
Pass `open` + `onClose` to drive the dialog from external state instead of `useDialogControl`:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<Dialog
|
|
50
|
+
placement="center"
|
|
51
|
+
open={isOpen}
|
|
52
|
+
onClose={() => setIsOpen(false)}
|
|
53
|
+
title="Confirm?"
|
|
54
|
+
actions={[
|
|
55
|
+
{ label: 'OK', onPress: handleOk },
|
|
56
|
+
{ label: 'Cancel', color: 'cancel' },
|
|
57
|
+
]}
|
|
58
|
+
/>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Side-sheet (drawer)
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// Fixed left side-sheet
|
|
65
|
+
<Dialog placement="left" control={control} title="Filters">
|
|
66
|
+
<FilterPanel />
|
|
67
|
+
</Dialog>
|
|
68
|
+
|
|
69
|
+
// Responsive: bottom-sheet on mobile, left drawer on desktop
|
|
70
|
+
<Dialog placement={{ base: 'bottom', md: 'left' }} control={control} title="Filters">
|
|
71
|
+
<FilterPanel />
|
|
72
|
+
</Dialog>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Custom content
|
|
76
|
+
|
|
77
|
+
Provide any JSX as `children`. Combine with `title` to keep a consistent header. Set `contentPadding={0}` when the children own their own insets.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<Dialog control={control} title="Pick a tag" contentPadding={0}>
|
|
81
|
+
<YourCustomBody />
|
|
82
|
+
</Dialog>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Pure custom
|
|
86
|
+
|
|
87
|
+
Drop the declarative props entirely — `children` owns every pixel.
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<Dialog control={control}>
|
|
91
|
+
<YourEntirelyCustomLayout />
|
|
92
|
+
</Dialog>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Props
|
|
96
|
+
|
|
97
|
+
| Prop | Type | Default | Description |
|
|
98
|
+
|------|------|---------|-------------|
|
|
99
|
+
| `control?` | `DialogControl` | — | From `useDialogControl()`. Omit when using controlled `open`. |
|
|
100
|
+
| `open?` | `boolean` | — | Controlled open state. When provided, wins over `control`. |
|
|
101
|
+
| `placement?` | `DialogPlacement \| ResponsiveDialogPlacement` | `'center'` | Surface anchor. See [Placement](#placement). |
|
|
102
|
+
| `title?` | `string` | — | Header text. |
|
|
103
|
+
| `description?` | `string` | — | Supporting copy below the title. |
|
|
104
|
+
| `actions?` | `DialogAction[]` | — | Confirmation buttons. See [DialogAction](#dialogaction). |
|
|
105
|
+
| `children?` | `React.ReactNode` | — | Custom content rendered after the description. |
|
|
106
|
+
| `onClose?` | `() => void` | — | Fires after the dialog finishes closing. In controlled mode, the host flips `open` to `false`. |
|
|
107
|
+
| `contentPadding?` | `number` | `20` | Inner padding of the dialog body (px). Set `0` for custom children that own their insets. |
|
|
108
|
+
| `width?` | `number` | `460` | Side-sheet width (px) on `left`/`right` placements. |
|
|
109
|
+
| `maxWidth?` | `number` | `480` | Centered-card max width (px). |
|
|
110
|
+
| `maxHeightRatio?` | `number` | `0.9` | Bottom-sheet max height as a fraction of the viewport height. |
|
|
111
|
+
| `inset?` | `{ top?; bottom?; left?; right? }` | — | Side-sheet inset (px) from the overlay container edges. |
|
|
112
|
+
| `showHandle?` | `boolean` | `true` | Show drag handle in bottom-sheet mode. |
|
|
113
|
+
| `dismissOnBackdrop?` | `boolean` | `true` | Tap backdrop to dismiss. |
|
|
114
|
+
| `panelStyle?` | `StyleProp<ViewStyle>` | — | Style for the panel surface. |
|
|
115
|
+
| `panelClassName?` | `string` | — | NativeWind class for the panel surface. |
|
|
116
|
+
| `containerStyle?` | `StyleProp<ViewStyle>` | — | Style for the root overlay (e.g. rail offset, theme-var scope). |
|
|
117
|
+
| `containerClassName?` | `string` | — | NativeWind class for the root overlay. |
|
|
118
|
+
| `label?` | `string` | — | Accessibility label. |
|
|
119
|
+
| `testID?` | `string` | — | RN testing ID. |
|
|
120
|
+
|
|
121
|
+
## Placement
|
|
122
|
+
|
|
123
|
+
`placement` accepts a single value or a **responsive map** resolved by `useWindowDimensions()`:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
type DialogPlacement = 'center' | 'left' | 'right' | 'bottom';
|
|
127
|
+
|
|
128
|
+
type ResponsiveDialogPlacement =
|
|
129
|
+
| DialogPlacement
|
|
130
|
+
| { base: DialogPlacement; sm?: DialogPlacement; md?: DialogPlacement; lg?: DialogPlacement; xl?: DialogPlacement };
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Breakpoints (px): `sm` 640 / `md` 768 / `lg` 1024 / `xl` 1280. The largest breakpoint whose min-width is `<=` the viewport wins; falls back to `base` below all breakpoints.
|
|
134
|
+
|
|
135
|
+
Per-placement behavior:
|
|
136
|
+
- `center` — DOM-portal modal (web) / detached floating card (native).
|
|
137
|
+
- `left` / `right` — animated side-sheet anchored to the viewport edge.
|
|
138
|
+
- `bottom` — reuses the cross-platform `BottomSheet` on BOTH web and native (drag-to-dismiss + internal scroll work everywhere). Custom `children` on bottom placement are rendered in a NON-scrollable body — children own scrolling.
|
|
139
|
+
|
|
140
|
+
### `DialogAction`
|
|
141
|
+
|
|
142
|
+
| Prop | Type | Description |
|
|
143
|
+
|------|------|-------------|
|
|
144
|
+
| `label` | `string` | Button text. |
|
|
145
|
+
| `color?` | `'default' \| 'cancel' \| 'destructive'` | Defaults to `'default'`. |
|
|
146
|
+
| `onPress?` | `(e) => void` | Invoked **after** the dialog finishes closing. |
|
|
147
|
+
| `disabled?` | `boolean` | |
|
|
148
|
+
| `shouldCloseOnPress?` | `boolean` | Defaults to `true`. Set `false` while an async action is in flight. |
|
|
149
|
+
| `testID?` | `string` | |
|
|
150
|
+
|
|
151
|
+
## Blurring the backdrop on Android
|
|
152
|
+
|
|
153
|
+
A backdrop blurs its content on iOS and web with no setup. **On Android it needs
|
|
154
|
+
the app content wrapped in a blur target**, which `BloomProvider` composes — so
|
|
155
|
+
if you mount it, there is nothing to do:
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
import { BloomProvider } from '@oxyhq/bloom/provider';
|
|
159
|
+
|
|
160
|
+
<BloomProvider>{children}</BloomProvider>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
An app that does not mount `BloomProvider` keeps the tint-only backdrop. That is
|
|
164
|
+
a supported state, not a missing step — see below.
|
|
165
|
+
|
|
166
|
+
Two things are worth knowing rather than discovering:
|
|
167
|
+
|
|
168
|
+
- **It applies to the bottom-sheet path only.** `expo-blur` on Android blurs a
|
|
169
|
+
target view, and a blur view that is a DESCENDANT of its own target crashes
|
|
170
|
+
the app — `SIGSEGV`, an unbounded render-tree recursion. Only a surface in a
|
|
171
|
+
separate native window is safe, which means a real RN `<Modal>`: that is
|
|
172
|
+
`BottomSheet`, and `Dialog`'s bottom placement through it. `Dialog`'s centre
|
|
173
|
+
and side placements portal into the same window and keep the tint-only
|
|
174
|
+
backdrop. Bloom enforces this structurally — the target is simply not
|
|
175
|
+
reachable from a surface that has not crossed a window boundary — so there is
|
|
176
|
+
no wiring you can get wrong.
|
|
177
|
+
- **Not mounting it is a supported state**, not a broken one. The backdrop still
|
|
178
|
+
paints its tint at full intensity; you lose the blur and nothing else, with no
|
|
179
|
+
warning and no crash.
|
|
180
|
+
|
|
181
|
+
## Web setup
|
|
182
|
+
|
|
183
|
+
Inject the CSS animations into your global styles once:
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import { BLOOM_DIALOG_CSS } from '@oxyhq/bloom/dialog';
|
|
187
|
+
|
|
188
|
+
// In your HTML head or global CSS file:
|
|
189
|
+
<style>{BLOOM_DIALOG_CSS}</style>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Examples
|
|
193
|
+
|
|
194
|
+
### Three-action confirmation
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
<Dialog
|
|
198
|
+
control={control}
|
|
199
|
+
title="Delete project?"
|
|
200
|
+
description="This will permanently delete the project and all of its files."
|
|
201
|
+
actions={[
|
|
202
|
+
{ label: 'Delete', color: 'destructive', onPress: handleDelete },
|
|
203
|
+
{ label: 'Archive', onPress: handleArchive },
|
|
204
|
+
{ label: 'Cancel', color: 'cancel' },
|
|
205
|
+
]}
|
|
206
|
+
/>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Async confirm with loading state
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
const [busy, setBusy] = useState(false);
|
|
213
|
+
|
|
214
|
+
<Dialog
|
|
215
|
+
control={control}
|
|
216
|
+
title="Submit?"
|
|
217
|
+
actions={[
|
|
218
|
+
{
|
|
219
|
+
label: busy ? 'Submitting…' : 'Submit',
|
|
220
|
+
disabled: busy,
|
|
221
|
+
shouldCloseOnPress: false, // keep dialog open while we work
|
|
222
|
+
onPress: async () => {
|
|
223
|
+
setBusy(true);
|
|
224
|
+
try {
|
|
225
|
+
await submit();
|
|
226
|
+
control.close();
|
|
227
|
+
} finally {
|
|
228
|
+
setBusy(false);
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
{ label: 'Cancel', color: 'cancel' },
|
|
233
|
+
]}
|
|
234
|
+
/>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Form inside a dialog
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
<Dialog control={control} title="Add tag">
|
|
241
|
+
<TextFieldInput label="Tag name" value={name} onChangeText={setName} />
|
|
242
|
+
<Button onPress={() => { save(name); control.close(); }}>Save</Button>
|
|
243
|
+
</Dialog>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Responsive drawer→sheet
|
|
247
|
+
|
|
248
|
+
Bottom-sheet on small screens, left drawer on `md` and wider:
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
<Dialog
|
|
252
|
+
placement={{ base: 'bottom', md: 'left' }}
|
|
253
|
+
control={control}
|
|
254
|
+
title="Store settings"
|
|
255
|
+
containerClassName="md:left-[4.75rem]"
|
|
256
|
+
>
|
|
257
|
+
<SettingsPanel />
|
|
258
|
+
</Dialog>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Migration from CenteredDialog / ResponsiveSheet
|
|
262
|
+
|
|
263
|
+
`CenteredDialog` and `ResponsiveSheet` were removed in 0.16.x.
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
// Before
|
|
267
|
+
<CenteredDialog visible={v} onClose={c}>…</CenteredDialog>
|
|
268
|
+
// After
|
|
269
|
+
<Dialog placement="center" open={v} onClose={c}>…</Dialog>
|
|
270
|
+
|
|
271
|
+
// Before
|
|
272
|
+
<ResponsiveSheet side="left" open={o} onClose={c}>…</ResponsiveSheet>
|
|
273
|
+
// After
|
|
274
|
+
<Dialog placement={{ base: 'bottom', md: 'left' }} open={o} onClose={c}>…</Dialog>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Removed exports: `CenteredDialog`, `CenteredDialogProps`, `BLOOM_CENTERED_DIALOG_CSS`, `CENTERED_DIALOG_BACKDROP_TESTID`, `ResponsiveSheet`, `@oxyhq/bloom/responsive-sheet` subpath.
|
package/docs/divider.mdx
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Divider
|
|
3
|
+
description: The one separator. Nothing else in the library draws a rule by hand.
|
|
4
|
+
order: 27
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Divider
|
|
8
|
+
|
|
9
|
+
A hairline in the `border` role. Three families used to draw their own 1px
|
|
10
|
+
separator and each had a slightly different colour — that is the whole reason
|
|
11
|
+
this component exists.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Divider } from '@oxyhq/bloom/divider';
|
|
15
|
+
|
|
16
|
+
<Divider />
|
|
17
|
+
<Divider spacing={12} />
|
|
18
|
+
<Divider vertical spacing={12} />
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Which prop decides what
|
|
22
|
+
|
|
23
|
+
| prop | decides |
|
|
24
|
+
| ----------- | ------------------------------------------------------------ |
|
|
25
|
+
| `vertical` | the axis |
|
|
26
|
+
| `spacing` | margin on the axis it separates — the prop you usually want |
|
|
27
|
+
| `thickness` | the weight; defaults to the platform hairline |
|
|
28
|
+
| `color` | an override; defaults to the `border` role |
|
|
29
|
+
|
|
30
|
+
Reach for `spacing` first. Most "the divider looks wrong" cases are margin, not
|
|
31
|
+
weight — a rule pressed against the text above it reads as heavier than it is.
|
|
32
|
+
|
|
33
|
+
## A vertical divider needs a parent with a height
|
|
34
|
+
|
|
35
|
+
It fills the cross axis (`alignSelf: 'stretch'`); it does not invent a height.
|
|
36
|
+
In a row with no height it is invisible rather than broken — which is the usual
|
|
37
|
+
reason a toolbar rule "doesn't render".
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', height: 40 }}>
|
|
41
|
+
<Text>Drafts</Text>
|
|
42
|
+
<Divider vertical spacing={12} />
|
|
43
|
+
<Text>Sent</Text>
|
|
44
|
+
</View>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## When not to use one
|
|
48
|
+
|
|
49
|
+
A divider between every row of a list is usually noise: a settings group already
|
|
50
|
+
separates itself with its card, and rows separated by space read as a group
|
|
51
|
+
without a rule. Use one where a boundary is *meaningful* — between sections,
|
|
52
|
+
not between items.
|
|
53
|
+
|
|
54
|
+
`SettingsListDivider` is the inset variant for grouped rows; prefer it inside a
|
|
55
|
+
settings group so the rule starts after the icon column instead of cutting the
|
|
56
|
+
card in half.
|