@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,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: FrostedIconButton
|
|
3
|
+
description: A translucent icon button for use over media, where a normal button has nothing to sit on.
|
|
4
|
+
order: 65
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# FrostedIconButton
|
|
8
|
+
|
|
9
|
+
A circular icon button with a blurred, translucent background. It is for
|
|
10
|
+
controls that float **over content** — a close button on a photo viewer, a
|
|
11
|
+
mute toggle on a video, a back button over a hero image.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { FrostedIconButton } from '@oxyhq/bloom/frosted-icon-button';
|
|
15
|
+
|
|
16
|
+
<FrostedIconButton
|
|
17
|
+
icon={<CloseIcon />}
|
|
18
|
+
accessibilityLabel="Close"
|
|
19
|
+
onPress={close}
|
|
20
|
+
/>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Which prop decides what
|
|
24
|
+
|
|
25
|
+
| prop | decides |
|
|
26
|
+
| ---------- | ---------------------------------------------------- |
|
|
27
|
+
| `icon` | the glyph |
|
|
28
|
+
| `size` | `sm` · `md`, or a number |
|
|
29
|
+
| `active` | a persistent on state, for a toggle |
|
|
30
|
+
| `disabled` | dims and disables |
|
|
31
|
+
| `hitSlop` | the touch target beyond the drawn circle |
|
|
32
|
+
|
|
33
|
+
## Why it exists rather than a `Button` variant
|
|
34
|
+
|
|
35
|
+
Over arbitrary media there is no background to be legible against: a photo can
|
|
36
|
+
be white here and black there. The frosted fill blurs whatever is behind it so
|
|
37
|
+
the glyph keeps its contrast wherever the button lands, which is not something
|
|
38
|
+
a solid or ghost button can do.
|
|
39
|
+
|
|
40
|
+
That also means it is the wrong choice **on** a surface. Over a card or a page
|
|
41
|
+
background, frosting blurs a flat colour — it costs a blur pass to look like a
|
|
42
|
+
slightly muddy circle. Use `Button`'s icon form there.
|
|
43
|
+
|
|
44
|
+
## Always give it a label
|
|
45
|
+
|
|
46
|
+
An icon-only button announces as "button" and nothing else. `accessibilityLabel`
|
|
47
|
+
is not decoration here; it is the entire accessible name.
|
|
48
|
+
|
|
49
|
+
## `hitSlop` for small buttons
|
|
50
|
+
|
|
51
|
+
A 32px circle over media is usually near a screen edge and pressed with a
|
|
52
|
+
thumb. `hitSlop` grows the target without growing the drawn button, which is
|
|
53
|
+
preferable to drawing something bigger than the design calls for.
|
|
54
|
+
|
|
55
|
+
## Web-only props
|
|
56
|
+
|
|
57
|
+
`onClick`, `title`, `id`, `type` and `aria-label` are on the shared type so one
|
|
58
|
+
call site type-checks on both platforms. They are consumed by the web fork,
|
|
59
|
+
which renders a real DOM `<button>`, and are no-ops on native. Prefer
|
|
60
|
+
`onPress` and `accessibilityLabel`, which work on both.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting started
|
|
3
|
+
description: Install Bloom and wrap your app with the theme and dialog providers.
|
|
4
|
+
order: 1
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Getting started
|
|
8
|
+
|
|
9
|
+
## 1. Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
bun add @oxyhq/bloom
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Install the peer dependencies. The same set covers native and web — Bloom's
|
|
16
|
+
overlays and its toast engine run on react-native-web:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
bun add react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-svg
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 2. Wrap your app root
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
26
|
+
import { BloomThemeProvider, SurfaceProvider } from '@oxyhq/bloom';
|
|
27
|
+
|
|
28
|
+
export default function Root() {
|
|
29
|
+
return (
|
|
30
|
+
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
31
|
+
<BloomThemeProvider mode="system" colorPreset="teal">
|
|
32
|
+
<SurfaceProvider>
|
|
33
|
+
<App />
|
|
34
|
+
</SurfaceProvider>
|
|
35
|
+
</BloomThemeProvider>
|
|
36
|
+
</GestureHandlerRootView>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `BloomThemeProvider` is required — every Bloom component reads from it.
|
|
42
|
+
- `SurfaceProvider` (or a bare `<SurfaceHost />`) is required only if you use the imperative surfaces — `alert()`, `confirm()`, `prompt()`, `surfaces.present()`. Without it, `<Dialog>` still works on its own.
|
|
43
|
+
- `GestureHandlerRootView` is required on native for any sheet-based component.
|
|
44
|
+
|
|
45
|
+
<Callout variant="warning" title="Web setup">
|
|
46
|
+
Inject the CSS animations into your global styles once:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { BLOOM_DIALOG_CSS } from '@oxyhq/bloom/dialog';
|
|
50
|
+
|
|
51
|
+
// In your HTML head or global CSS file:
|
|
52
|
+
<style>{BLOOM_DIALOG_CSS}</style>
|
|
53
|
+
```
|
|
54
|
+
</Callout>
|
|
55
|
+
|
|
56
|
+
## 3. Use a component
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { Button } from '@oxyhq/bloom/button';
|
|
60
|
+
import { toast } from '@oxyhq/bloom';
|
|
61
|
+
|
|
62
|
+
export function Hello() {
|
|
63
|
+
return <Button onPress={() => toast.success('Hello, Bloom!')}>Greet</Button>;
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Recommended layout
|
|
68
|
+
|
|
69
|
+
If your app supports both light and dark themes:
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
<BloomThemeProvider mode="system" colorPreset="teal">
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`mode="system"` follows the OS preference. `colorPreset` picks one of the 10 named palettes — see [Theme](/docs/bloom/main/theme) for the full list.
|
package/docs/grid.mdx
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Grid
|
|
3
|
+
description: Row and Col — a gutter that keeps the grid's outer edges flush with everything else.
|
|
4
|
+
order: 45
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Grid
|
|
8
|
+
|
|
9
|
+
`Row` and `Col` are a twelfth-free fractional grid. A row holds columns; each
|
|
10
|
+
column takes a fraction of the width.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Row, Col } from '@oxyhq/bloom/grid';
|
|
14
|
+
|
|
15
|
+
<Row gap={16}>
|
|
16
|
+
<Col width={2 / 3}>
|
|
17
|
+
<Article />
|
|
18
|
+
</Col>
|
|
19
|
+
<Col width={1 / 3}>
|
|
20
|
+
<Sidebar />
|
|
21
|
+
</Col>
|
|
22
|
+
</Row>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Which prop decides what
|
|
26
|
+
|
|
27
|
+
| prop | decides |
|
|
28
|
+
| ------- | ----------------------------------------------------------- |
|
|
29
|
+
| `gap` | on `Row`, the space between columns |
|
|
30
|
+
| `width` | on `Col`, its fraction of the row — `0.5`, `1 / 3`, `0.25` |
|
|
31
|
+
|
|
32
|
+
## How the gutter works, and why it matters
|
|
33
|
+
|
|
34
|
+
The row pulls itself out by **half** the gap on each side, and each column pads
|
|
35
|
+
in by half. The result is that the outer edges of the grid line up with the
|
|
36
|
+
content above and below it, while the columns are still separated.
|
|
37
|
+
|
|
38
|
+
A grid that only padded its columns would be inset by half a gutter on both
|
|
39
|
+
sides — a few pixels of misalignment against every other block on the page,
|
|
40
|
+
which is the kind of thing that looks like nothing and reads as sloppy.
|
|
41
|
+
|
|
42
|
+
That is also why a `Col` outside any `Row` gets no gutter at all rather than a
|
|
43
|
+
default one: without a row there is no negative margin to compensate for it.
|
|
44
|
+
|
|
45
|
+
## `width` is a fraction, not a column count
|
|
46
|
+
|
|
47
|
+
`width={1 / 3}` rather than `width={4}` out of twelve. Fractions compose with
|
|
48
|
+
any number of columns and do not require the reader to remember the divisor.
|
|
49
|
+
`1 / 3` is exactly what it says; `4` is only meaningful once you know the grid
|
|
50
|
+
is twelve wide.
|
|
51
|
+
|
|
52
|
+
## Grid or flexbox
|
|
53
|
+
|
|
54
|
+
`Row`/`Col` is for a **proportional split** — two thirds and one third,
|
|
55
|
+
regardless of content. Ordinary flexbox is better when the split follows the
|
|
56
|
+
content (a label that hugs its text beside a field that takes the rest), and a
|
|
57
|
+
`gap` on a plain `View` is better when everything is the same size.
|
|
58
|
+
|
|
59
|
+
## Wrapping
|
|
60
|
+
|
|
61
|
+
`Row` does not wrap. A responsive layout switches between one column and two by
|
|
62
|
+
rendering different structures at the breakpoint, rather than by letting a row
|
|
63
|
+
reflow — a wrapped grid loses the alignment the gutter maths exists to
|
|
64
|
+
preserve.
|
package/docs/hooks.mdx
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Hooks
|
|
3
|
+
description: The shared behaviours Bloom's own components are built on.
|
|
4
|
+
order: 73
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Hooks
|
|
8
|
+
|
|
9
|
+
`@oxyhq/bloom/hooks` is the set of behaviours Bloom's components share. They
|
|
10
|
+
are exported because apps hit the same problems, and because a second copy of
|
|
11
|
+
any of them drifts.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { useInteractionState, useDelayedLoading, useGutters } from '@oxyhq/bloom/hooks';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## `useInteractionState`
|
|
18
|
+
|
|
19
|
+
Press and hover state as plain booleans, with the handlers to wire up.
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
const { state: pressed, onIn, onOut } = useInteractionState();
|
|
23
|
+
|
|
24
|
+
<Pressable onPressIn={onIn} onPressOut={onOut} style={[base, pressed && dim]}>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Bloom uses this rather than `Pressable`'s function-form `style`, which
|
|
28
|
+
NativeWind's css-interop swallows — taking the base container style (background,
|
|
29
|
+
radius, border, shadow) with it. That failure is silent and total: the component
|
|
30
|
+
renders unstyled and nothing errors.
|
|
31
|
+
|
|
32
|
+
## `useDelayedLoading`
|
|
33
|
+
|
|
34
|
+
Holds a flag true for a fixed delay, then flips it false.
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
const settling = useDelayedLoading(300); // true for 300ms, then false
|
|
38
|
+
const showContent = !settling && !isFetching;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
It takes the DELAY, not the loading flag: `useDelayedLoading(delay, initialState?)`.
|
|
42
|
+
Use it to guarantee a minimum on-screen time. A spinner that appears for 80ms
|
|
43
|
+
and vanishes is worse than no spinner — the flash reads as a glitch, and the
|
|
44
|
+
user cannot tell what happened.
|
|
45
|
+
|
|
46
|
+
## `useThrottledValue`
|
|
47
|
+
|
|
48
|
+
A value that updates at most every N ms. For anything that re-renders a
|
|
49
|
+
expensive tree on a continuous input — a drag, a scroll position, a typed
|
|
50
|
+
query.
|
|
51
|
+
|
|
52
|
+
## `usePressAnimation`
|
|
53
|
+
|
|
54
|
+
The shared press transform, so a press dip is the same everywhere.
|
|
55
|
+
|
|
56
|
+
## `useGutters`
|
|
57
|
+
|
|
58
|
+
The screen's horizontal insets, from one place, so a header and the content
|
|
59
|
+
below it agree about where the margin is.
|
|
60
|
+
|
|
61
|
+
## `useHaptics`
|
|
62
|
+
|
|
63
|
+
Haptic feedback through Bloom's optional-peer boundary — a no-op when
|
|
64
|
+
`expo-haptics` is not installed, rather than a build failure.
|
|
65
|
+
|
|
66
|
+
Mount `BloomHapticsProvider` if the app needs to turn haptics off globally
|
|
67
|
+
(a user setting, or a platform where they are inappropriate).
|
|
68
|
+
|
|
69
|
+
## `useImagePreload` / `preloadImage`
|
|
70
|
+
|
|
71
|
+
Fetch an image before it is rendered — the transition into a full-screen viewer,
|
|
72
|
+
the next page of a carousel.
|
|
73
|
+
|
|
74
|
+
## `mergeRefs`
|
|
75
|
+
|
|
76
|
+
Combines several refs into one callback.
|
|
77
|
+
|
|
78
|
+
Note the return type: `(instance: T | null) => void`, **not**
|
|
79
|
+
`React.RefCallback<T>`. That is deliberate — the plain function type stays
|
|
80
|
+
assignable across duplicate copies of `@types/react` in a consumer's tree,
|
|
81
|
+
which the branded type does not.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: IconCircle
|
|
3
|
+
description: An icon on a tinted disc — the reference for how Bloom tints anything.
|
|
4
|
+
order: 47
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# IconCircle
|
|
8
|
+
|
|
9
|
+
An icon centred on a coloured disc. Empty states, consent steps, the header of
|
|
10
|
+
a settings section — anywhere an icon needs presence rather than sitting beside
|
|
11
|
+
text.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { IconCircle } from '@oxyhq/bloom/icon-circle';
|
|
15
|
+
import { Shield_Stroke2_Corner0_Rounded as ShieldIcon } from '@oxyhq/bloom/icons';
|
|
16
|
+
|
|
17
|
+
<IconCircle icon={ShieldIcon} />
|
|
18
|
+
<IconCircle icon={ShieldIcon} size="lg" />
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Which prop decides what
|
|
22
|
+
|
|
23
|
+
| prop | decides |
|
|
24
|
+
| ----------- | ---------------------------------------------- |
|
|
25
|
+
| `icon` | the glyph component — passed, not rendered |
|
|
26
|
+
| `size` | `lg` (52px) or `xl` (64px, the default) |
|
|
27
|
+
| `style` | the disc |
|
|
28
|
+
| `iconStyle` | the glyph |
|
|
29
|
+
|
|
30
|
+
`icon` takes the component itself (`icon={ShieldIcon}`), not an element
|
|
31
|
+
(`icon={<ShieldIcon />}`), so the circle can size and colour it.
|
|
32
|
+
|
|
33
|
+
## The tinting rule this component is the reference for
|
|
34
|
+
|
|
35
|
+
The disc is `primarySubtle` and the glyph is `primary` — a **pair** of resolved
|
|
36
|
+
tokens, not one token with alpha appended.
|
|
37
|
+
|
|
38
|
+
That distinction is the whole point. A Bloom accent token resolves to a full
|
|
39
|
+
`rgb(...)`, so `` `${colors.primary}1A` `` is a malformed string that
|
|
40
|
+
react-native-web parses back as fully **opaque**. A disc tinted that way ends
|
|
41
|
+
up the same colour as the glyph on it — contrast 1.00, an invisible icon, and
|
|
42
|
+
markup that reads as correct.
|
|
43
|
+
|
|
44
|
+
## Override both or neither
|
|
45
|
+
|
|
46
|
+
`style` moves the disc, `iconStyle` moves the glyph. Changing one is exactly
|
|
47
|
+
how a tinted disc ends up with an invisible icon:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<IconCircle
|
|
51
|
+
icon={BellIcon}
|
|
52
|
+
style={{ backgroundColor: 'rgb(255 237 213)' }}
|
|
53
|
+
iconStyle={{ color: 'rgb(154 52 18)' }}
|
|
54
|
+
/>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If you find yourself doing this often for the same tone, that is a signal the
|
|
58
|
+
tone belongs in the theme — `resolveAccentColors(colors, tone, fill)` reads the
|
|
59
|
+
`*Subtle` / `*SubtleForeground` pairs the design tokens already gate together.
|
|
60
|
+
|
|
61
|
+
## Not a button
|
|
62
|
+
|
|
63
|
+
`IconCircle` draws; it does not press. Wrap it in a `Pressable` with an
|
|
64
|
+
`accessibilityLabel` if it should do something — and consider
|
|
65
|
+
[`FrostedIconButton`](/docs/bloom/main/frosted-icon-button) or `Button`'s icon
|
|
66
|
+
form first, which are already controls.
|
package/docs/icons.mdx
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Icons
|
|
3
|
+
description: 250-odd glyphs with full names — and why a shortened one is invisible to the compiler.
|
|
4
|
+
order: 66
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Icons
|
|
8
|
+
|
|
9
|
+
Bloom's icon set. Every glyph is a named export carrying its style and corner
|
|
10
|
+
suffix.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Bell_Stroke2_Corner0_Rounded as BellIcon } from '@oxyhq/bloom/icons';
|
|
14
|
+
|
|
15
|
+
<BellIcon size="lg" fill={colors.text} />
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## The naming is `<Subject>_<Style>_Corner0_Rounded`
|
|
19
|
+
|
|
20
|
+
The suffix is not decoration — it is *which drawing you get*. `Stroke2` and
|
|
21
|
+
`Filled` are separate exports rather than a prop on one component, because they
|
|
22
|
+
are different artwork: filled reads as "on"/selected, stroke as the resting
|
|
23
|
+
state. A tab bar uses both for the same subject.
|
|
24
|
+
|
|
25
|
+
Import the exact identifier and alias it locally, as above.
|
|
26
|
+
|
|
27
|
+
## `fill` and `size`, not inheritance
|
|
28
|
+
|
|
29
|
+
Colour travels on `fill` and size on `size`. Neither is inherited from a
|
|
30
|
+
surrounding `Text`, so an icon beside a label has to be told both — an icon
|
|
31
|
+
that "looks black in dark mode" is almost always one that was never given a
|
|
32
|
+
`fill`.
|
|
33
|
+
|
|
34
|
+
## A shortened name is invisible to `tsc`
|
|
35
|
+
|
|
36
|
+
Dropping the suffix — writing the bare subject where the full identifier
|
|
37
|
+
belongs — is not a compile error anywhere such a reference usually appears: in
|
|
38
|
+
a **doc comment** or an `.mdx` example, TypeScript never sees it. A reader
|
|
39
|
+
copies it, gets `undefined`, and React reports "Element type is invalid … but
|
|
40
|
+
got: undefined" against *their* file — which is how a phantom release blocker
|
|
41
|
+
once got filed against the tab bar.
|
|
42
|
+
|
|
43
|
+
`src/__tests__/icon-references.test.ts` scans `src/`, `docs/`, `README.md` and
|
|
44
|
+
`AGENTS.md` for references that do not resolve against the real export union.
|
|
45
|
+
It is the only gate on this class of bug, and it deliberately does **not** strip
|
|
46
|
+
comments, because the comments are the surface under test.
|
|
47
|
+
|
|
48
|
+
## Two import shapes are correct, one is not
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import * as Icons from '@oxyhq/bloom/icons'; // ✅ the subpath, as a namespace
|
|
52
|
+
import { Icons } from '@oxyhq/bloom'; // ✅ the root barrel's namespace
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The third shape — importing `Icons` as a *named* export from the **subpath** —
|
|
56
|
+
is `undefined` at runtime. The subpath exports the components flat; the `Icons`
|
|
57
|
+
name is assembled by the root barrel (`export * as Icons from './icons'`), so
|
|
58
|
+
there is no such named export to destructure.
|
|
59
|
+
|
|
60
|
+
The gate below flags that shape too, which is why this page describes it rather
|
|
61
|
+
than showing it: a counter-example spelled as a literal is indistinguishable
|
|
62
|
+
from the mistake it warns about, and the scan is right to flag both.
|
|
63
|
+
|
|
64
|
+
## Adding an icon
|
|
65
|
+
|
|
66
|
+
One file per subject under `src/icons/`, exporting the full identifiers, and
|
|
67
|
+
re-exported from the barrel. Keep the stroke and filled variants together in
|
|
68
|
+
the same file — they are the same subject, and separating them is how one gets
|
|
69
|
+
updated without the other.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Image aspect-ratio cache
|
|
3
|
+
description: Remembering an image's shape so a list stops jumping when it loads.
|
|
4
|
+
order: 67
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Image aspect-ratio cache
|
|
8
|
+
|
|
9
|
+
A process-wide cache of image intrinsic sizes, keyed by URL.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import {
|
|
13
|
+
getAspectRatio,
|
|
14
|
+
hasAspectRatio,
|
|
15
|
+
fetchAspectRatio,
|
|
16
|
+
setAspectRatio,
|
|
17
|
+
DEFAULT_ASPECT_RATIO,
|
|
18
|
+
} from '@oxyhq/bloom/image-aspect-ratio-cache';
|
|
19
|
+
|
|
20
|
+
const ratio = hasAspectRatio(url) ? getAspectRatio(url) : DEFAULT_ASPECT_RATIO;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## The problem it solves
|
|
24
|
+
|
|
25
|
+
An image's shape is not known until it loads. A list that reserves no space
|
|
26
|
+
reflows every time one arrives, which throws the reading position down the page
|
|
27
|
+
— the classic feed that jumps while you are reading it.
|
|
28
|
+
|
|
29
|
+
Reserving a *fixed* box instead is not a fix: it letterboxes portrait images and
|
|
30
|
+
crops landscape ones. What you want is the real ratio, remembered, so the second
|
|
31
|
+
time the same image appears there is no jump at all.
|
|
32
|
+
|
|
33
|
+
## The functions
|
|
34
|
+
|
|
35
|
+
| function | for |
|
|
36
|
+
| ----------------------- | ----------------------------------------------------- |
|
|
37
|
+
| `hasAspectRatio(url)` | whether the shape is known **without** guessing |
|
|
38
|
+
| `getAspectRatio(url)` | the ratio, if known |
|
|
39
|
+
| `getIntrinsicSize(url)` | the width and height, if known |
|
|
40
|
+
| `fetchAspectRatio(url)` | measure it and cache the result |
|
|
41
|
+
| `setAspectRatio(url, …)`| record one you already know — from a DTO, say |
|
|
42
|
+
| `DEFAULT_ASPECT_RATIO` | what to reserve while it is unknown |
|
|
43
|
+
|
|
44
|
+
## `hasAspectRatio` is not the same as `getAspectRatio() !== undefined`
|
|
45
|
+
|
|
46
|
+
Keep the distinction. "Not measured yet" and "measured, and it happens to be
|
|
47
|
+
the default" want different behaviour: the first should reserve the default and
|
|
48
|
+
then settle, the second is already correct and must not animate.
|
|
49
|
+
|
|
50
|
+
## Prefer `setAspectRatio` over measuring
|
|
51
|
+
|
|
52
|
+
If the API already returns the dimensions — most media DTOs do — record them
|
|
53
|
+
and skip the measurement entirely. A ratio known before first paint means the
|
|
54
|
+
layout is right on frame one, which no amount of caching after the fact
|
|
55
|
+
achieves.
|
|
56
|
+
|
|
57
|
+
## It is a cache, not state
|
|
58
|
+
|
|
59
|
+
Module-level and process-wide, so it survives navigation and re-renders. Do not
|
|
60
|
+
read it in a memoized position: it is external mutable state, and under the
|
|
61
|
+
React Compiler that is exactly the shape that produces a stale read. Read it
|
|
62
|
+
during render for the initial reservation, and drive updates from the load
|
|
63
|
+
event.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ImageResolver
|
|
3
|
+
description: The one chokepoint that turns an Oxy file id into a URL.
|
|
4
|
+
order: 54
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ImageResolver
|
|
8
|
+
|
|
9
|
+
Bloom components take a **file id** and ask the app how to turn it into a URL.
|
|
10
|
+
The app registers that function once, at the root.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { ImageResolverProvider } from '@oxyhq/bloom/image-resolver';
|
|
14
|
+
import { oxyServices } from '@oxyhq/services';
|
|
15
|
+
|
|
16
|
+
<ImageResolverProvider value={(id, variant) => oxyServices.getFileDownloadUrl(id, variant)}>
|
|
17
|
+
<App />
|
|
18
|
+
</ImageResolverProvider>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
type ImageResolver = (id: string, variant?: string) => string | undefined;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Why Bloom does not build the URL itself
|
|
26
|
+
|
|
27
|
+
A URL builder in Bloom would hardcode a host, a path shape and a variant
|
|
28
|
+
vocabulary that belong to the platform, not to a component library — and every
|
|
29
|
+
consumer would then need Bloom released to change any of them. One function,
|
|
30
|
+
registered by the app, keeps the media contract in `@oxyhq/services` where it
|
|
31
|
+
already lives.
|
|
32
|
+
|
|
33
|
+
That is also why there is no `avatarUrl` field on any DTO and no per-app URL
|
|
34
|
+
helper: **one chokepoint**, `getFileDownloadUrl(id, variant)`, plus this
|
|
35
|
+
resolver.
|
|
36
|
+
|
|
37
|
+
## `source` versus `uri` — the failure that is total and silent
|
|
38
|
+
|
|
39
|
+
`Avatar` invokes the resolver only for a **non-URL string** passed as `source`.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
<Avatar source={user.avatar} /> // ✅ file id → resolver → URL
|
|
43
|
+
<Avatar uri={user.avatar} /> // ✗ file id used as a raw URL → 404
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
A full URL or an `{ uri }` object passes through untouched, so both props
|
|
47
|
+
"work" in a test with a real URL and only one of them works with a file id.
|
|
48
|
+
Nothing throws — the image 404s and the avatar falls back to its initial. The
|
|
49
|
+
symptom is *every avatar shows a placeholder character*, which reads as missing
|
|
50
|
+
data rather than as a wiring mistake.
|
|
51
|
+
|
|
52
|
+
The same applies to every Bloom component that wraps `Avatar`:
|
|
53
|
+
[`AvatarGroup`](/docs/bloom/main/avatar-group) items and
|
|
54
|
+
[`UserHoverCard`](/docs/bloom/main/user-hover-card).
|
|
55
|
+
|
|
56
|
+
## `variant`
|
|
57
|
+
|
|
58
|
+
The second argument asks for a size. `Avatar` defaults it to `thumb` rather
|
|
59
|
+
than requesting the full-size original for a 32px circle — a detail worth
|
|
60
|
+
keeping if you write a resolver that ignores the argument.
|
|
61
|
+
|
|
62
|
+
## Register it once
|
|
63
|
+
|
|
64
|
+
One provider at the app root. A second one deeper in the tree silently shadows
|
|
65
|
+
the first for that subtree, which produces images that work on some screens and
|
|
66
|
+
not others.
|
package/docs/index.mdx
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Bloom
|
|
3
|
+
description: Shared UI component library for the Oxy ecosystem. React Native + Expo + Web.
|
|
4
|
+
order: 0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Bloom
|
|
8
|
+
|
|
9
|
+
Shared UI component library for the Oxy ecosystem. Built for React Native + Expo + Web.
|
|
10
|
+
|
|
11
|
+
<Badge>Apache-2.0</Badge>
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
bun add @oxyhq/bloom
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Peer dependencies
|
|
20
|
+
|
|
21
|
+
Required:
|
|
22
|
+
|
|
23
|
+
- `react >= 18`
|
|
24
|
+
- `react-native >= 0.73`
|
|
25
|
+
- `react-native-safe-area-context >= 5`
|
|
26
|
+
|
|
27
|
+
Also required:
|
|
28
|
+
|
|
29
|
+
- `react-native-reanimated >= 3.13` (`Dialog`, `BottomSheet`, `toast`, `Loading`) — on web too
|
|
30
|
+
- `react-native-gesture-handler >= 2.16.1` (`Dialog`, `BottomSheet`, toast swipe-to-dismiss) — native also needs the app root wrapped in `GestureHandlerRootView`
|
|
31
|
+
- `react-native-svg >= 13` (Bloom icons, Avatar `squircle` shape)
|
|
32
|
+
|
|
33
|
+
## What's inside
|
|
34
|
+
|
|
35
|
+
Bloom ships modal primitives, form controls, layout helpers, typography, theming, and a portal system that works on both native and web. Everything is exported under a sub-path (`@oxyhq/bloom/dialog`, `@oxyhq/bloom/button`, etc.) so tree-shaking stays effective.
|
|
36
|
+
|
|
37
|
+
## Next steps
|
|
38
|
+
|
|
39
|
+
- [Getting started](/docs/bloom/main/getting-started) — wire up `BloomThemeProvider` and `SurfaceProvider`.
|
|
40
|
+
- [Theme](/docs/bloom/main/theme) — color presets, modes, design tokens.
|
|
41
|
+
- [Dialog](/docs/bloom/main/dialog) — the unified Dialog API.
|
|
42
|
+
- [BottomSheet](/docs/bloom/main/bottom-sheet) — gesture-driven sheet primitive.
|
|
43
|
+
- [Toast](/docs/bloom/main/toast) — passive notifications.
|
|
44
|
+
- [alert()](/docs/bloom/main/alert) — imperative one-shot confirmations.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: InputGroup
|
|
3
|
+
description: A control with things attached to it — one bordered box, not several.
|
|
4
|
+
order: 34
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# InputGroup
|
|
8
|
+
|
|
9
|
+
An input with prefixes, suffixes or buttons attached, drawn as **one** bordered
|
|
10
|
+
box so the whole thing reads as a single control.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { InputGroup, InputGroupAddon } from '@oxyhq/bloom/input-group';
|
|
14
|
+
|
|
15
|
+
<InputGroup>
|
|
16
|
+
<InputGroupAddon>oxy.so/</InputGroupAddon>
|
|
17
|
+
<TextFieldInput label="Handle" value={handle} onChangeText={setHandle} />
|
|
18
|
+
</InputGroup>
|
|
19
|
+
|
|
20
|
+
<InputGroup>
|
|
21
|
+
<TextFieldInput label="Search" value={query} onChangeText={setQuery} />
|
|
22
|
+
<InputGroupAddon divider>
|
|
23
|
+
<Button size="small" onPress={run}>Search</Button>
|
|
24
|
+
</InputGroupAddon>
|
|
25
|
+
</InputGroup>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Which prop decides what
|
|
29
|
+
|
|
30
|
+
| prop | decides |
|
|
31
|
+
| ----------- | -------------------------------------------------------------- |
|
|
32
|
+
| `size` | `sm` · `md` · `lg` — applied to the group, so parts agree |
|
|
33
|
+
| `isInvalid` | the error border on the whole box |
|
|
34
|
+
| `disabled` | dims and disables the group |
|
|
35
|
+
| `divider` | per addon, a rule between it and the input |
|
|
36
|
+
| `noPadding` | per addon, for an addon that supplies its own (a button) |
|
|
37
|
+
|
|
38
|
+
## Why the state lives on the group
|
|
39
|
+
|
|
40
|
+
`isInvalid` and `disabled` are properties of the **control**, not of the input
|
|
41
|
+
element inside it. Setting them per part is how a box ends up with a red border
|
|
42
|
+
and a normal-coloured button, or an enabled button beside a disabled input.
|
|
43
|
+
|
|
44
|
+
## A prefix is not a placeholder and not a label
|
|
45
|
+
|
|
46
|
+
`oxy.so/` as an addon is part of the value's shape: it is always visible, it
|
|
47
|
+
does not disappear on typing, and it is not editable. That is different from a
|
|
48
|
+
placeholder (an example, which vanishes) and from a label (the name of the
|
|
49
|
+
field, which belongs to [`Field`](/docs/bloom/main/field) or
|
|
50
|
+
[`Label`](/docs/bloom/main/label)).
|
|
51
|
+
|
|
52
|
+
`InputGroup` goes *inside* a `Field` — the group is the control, the field is
|
|
53
|
+
its label, hint and error.
|
|
54
|
+
|
|
55
|
+
## `divider`
|
|
56
|
+
|
|
57
|
+
Use it when the addon is a distinct target — a button, a dropdown. Skip it for
|
|
58
|
+
a static prefix or an icon, where a rule makes one control look like two.
|