@oxy.so/bloom 2.12.3 → 3.0.0
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 +4 -4
- package/docs/button-group.mdx +46 -4
- package/docs/composition.mdx +159 -0
- package/docs/field.mdx +39 -1
- package/docs/glass.mdx +46 -0
- package/docs/layout.mdx +72 -4
- package/docs/message-bubble.mdx +37 -3
- package/docs/message-media.mdx +6 -0
- package/docs/migrating-to-3.0.mdx +169 -0
- package/docs/page-header.mdx +192 -36
- package/lib/commonjs/button/Button.js +2 -0
- package/lib/commonjs/button/Button.js.map +1 -1
- package/lib/commonjs/button-group/ButtonGroup.js +124 -19
- package/lib/commonjs/button-group/ButtonGroup.js.map +1 -1
- package/lib/commonjs/control-surface/ControlSurface.js +46 -0
- package/lib/commonjs/control-surface/ControlSurface.js.map +1 -0
- package/lib/commonjs/control-surface/context.js +84 -0
- package/lib/commonjs/control-surface/context.js.map +1 -0
- package/lib/commonjs/control-surface/index.js +26 -0
- package/lib/commonjs/control-surface/index.js.map +1 -0
- package/lib/commonjs/control-surface/types.js +6 -0
- package/lib/commonjs/control-surface/types.js.map +1 -0
- package/lib/commonjs/field/Field.js +71 -26
- package/lib/commonjs/field/Field.js.map +1 -1
- package/lib/commonjs/field/context.js +80 -0
- package/lib/commonjs/field/context.js.map +1 -0
- package/lib/commonjs/field/index.js +7 -0
- package/lib/commonjs/field/index.js.map +1 -1
- package/lib/commonjs/floating/TriggerSlot.js +32 -0
- package/lib/commonjs/floating/TriggerSlot.js.map +1 -1
- package/lib/commonjs/glass/GlassIsland.js +107 -0
- package/lib/commonjs/glass/GlassIsland.js.map +1 -0
- package/lib/commonjs/glass/GlassSurface.js +4 -1
- package/lib/commonjs/glass/GlassSurface.js.map +1 -1
- package/lib/commonjs/glass/index.js +7 -0
- package/lib/commonjs/glass/index.js.map +1 -1
- package/lib/commonjs/index.js +564 -517
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +564 -517
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/label/Label.js +4 -1
- package/lib/commonjs/label/Label.js.map +1 -1
- package/lib/commonjs/layout/bottom-edge.js +3 -42
- package/lib/commonjs/layout/bottom-edge.js.map +1 -1
- package/lib/commonjs/layout/edge-store.js +65 -0
- package/lib/commonjs/layout/edge-store.js.map +1 -0
- package/lib/commonjs/layout/index.js +32 -0
- package/lib/commonjs/layout/index.js.map +1 -1
- package/lib/commonjs/layout/scroll-offset.js +83 -0
- package/lib/commonjs/layout/scroll-offset.js.map +1 -0
- package/lib/commonjs/layout/top-edge.js +109 -0
- package/lib/commonjs/layout/top-edge.js.map +1 -0
- package/lib/commonjs/message-bubble/MessageBubble.js +16 -5
- package/lib/commonjs/message-bubble/MessageBubble.js.map +1 -1
- package/lib/commonjs/page-header/EdgeScrim.js +136 -0
- package/lib/commonjs/page-header/EdgeScrim.js.map +1 -0
- package/lib/commonjs/page-header/PageHeader.js +236 -61
- package/lib/commonjs/page-header/PageHeader.js.map +1 -1
- package/lib/commonjs/provider/BloomProvider.js +5 -2
- package/lib/commonjs/provider/BloomProvider.js.map +1 -1
- package/lib/commonjs/social-button/OxyMark.js +2 -2
- package/lib/commonjs/social-button/OxyMark.js.map +1 -1
- package/lib/commonjs/social-button/SocialButton.js +2 -2
- package/lib/commonjs/social-button/SocialButton.js.map +1 -1
- package/lib/commonjs/styles/svg-id.js +29 -0
- package/lib/commonjs/styles/svg-id.js.map +1 -0
- package/lib/commonjs/text-field/TextField.js +39 -6
- package/lib/commonjs/text-field/TextField.js.map +1 -1
- package/lib/commonjs/theme/glass-colors.js +81 -1
- package/lib/commonjs/theme/glass-colors.js.map +1 -1
- package/lib/module/button/Button.js +2 -0
- package/lib/module/button/Button.js.map +1 -1
- package/lib/module/button-group/ButtonGroup.js +124 -19
- package/lib/module/button-group/ButtonGroup.js.map +1 -1
- package/lib/module/control-surface/ControlSurface.js +41 -0
- package/lib/module/control-surface/ControlSurface.js.map +1 -0
- package/lib/module/control-surface/context.js +77 -0
- package/lib/module/control-surface/context.js.map +1 -0
- package/lib/module/control-surface/index.js +5 -0
- package/lib/module/control-surface/index.js.map +1 -0
- package/lib/module/control-surface/types.js +4 -0
- package/lib/module/control-surface/types.js.map +1 -0
- package/lib/module/field/Field.js +72 -27
- package/lib/module/field/Field.js.map +1 -1
- package/lib/module/field/context.js +74 -0
- package/lib/module/field/context.js.map +1 -0
- package/lib/module/field/index.js +1 -0
- package/lib/module/field/index.js.map +1 -1
- package/lib/module/floating/TriggerSlot.js +33 -1
- package/lib/module/floating/TriggerSlot.js.map +1 -1
- package/lib/module/glass/GlassIsland.js +102 -0
- package/lib/module/glass/GlassIsland.js.map +1 -0
- package/lib/module/glass/GlassSurface.js +5 -2
- package/lib/module/glass/GlassSurface.js.map +1 -1
- package/lib/module/glass/index.js +1 -0
- package/lib/module/glass/index.js.map +1 -1
- package/lib/module/index.js +2 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +2 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/label/Label.js +4 -1
- package/lib/module/label/Label.js.map +1 -1
- package/lib/module/layout/bottom-edge.js +2 -41
- package/lib/module/layout/bottom-edge.js.map +1 -1
- package/lib/module/layout/edge-store.js +58 -0
- package/lib/module/layout/edge-store.js.map +1 -0
- package/lib/module/layout/index.js +8 -3
- package/lib/module/layout/index.js.map +1 -1
- package/lib/module/layout/scroll-offset.js +77 -0
- package/lib/module/layout/scroll-offset.js.map +1 -0
- package/lib/module/layout/top-edge.js +102 -0
- package/lib/module/layout/top-edge.js.map +1 -0
- package/lib/module/message-bubble/MessageBubble.js +16 -5
- package/lib/module/message-bubble/MessageBubble.js.map +1 -1
- package/lib/module/page-header/EdgeScrim.js +132 -0
- package/lib/module/page-header/EdgeScrim.js.map +1 -0
- package/lib/module/page-header/PageHeader.js +233 -58
- package/lib/module/page-header/PageHeader.js.map +1 -1
- package/lib/module/provider/BloomProvider.js +5 -2
- package/lib/module/provider/BloomProvider.js.map +1 -1
- package/lib/module/social-button/OxyMark.js +1 -1
- package/lib/module/social-button/OxyMark.js.map +1 -1
- package/lib/module/social-button/SocialButton.js +1 -1
- package/lib/module/social-button/SocialButton.js.map +1 -1
- package/lib/module/styles/svg-id.js +26 -0
- package/lib/module/styles/svg-id.js.map +1 -0
- package/lib/module/text-field/TextField.js +39 -6
- package/lib/module/text-field/TextField.js.map +1 -1
- package/lib/module/theme/glass-colors.js +79 -0
- package/lib/module/theme/glass-colors.js.map +1 -1
- package/lib/typescript/commonjs/button/Button.d.ts.map +1 -1
- package/lib/typescript/commonjs/button-group/ButtonGroup.d.ts.map +1 -1
- package/lib/typescript/commonjs/button-group/types.d.ts +61 -5
- package/lib/typescript/commonjs/button-group/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/control-surface/ControlSurface.d.ts +4 -0
- package/lib/typescript/commonjs/control-surface/ControlSurface.d.ts.map +1 -0
- package/lib/typescript/commonjs/control-surface/context.d.ts +21 -0
- package/lib/typescript/commonjs/control-surface/context.d.ts.map +1 -0
- package/lib/typescript/commonjs/control-surface/index.d.ts +4 -0
- package/lib/typescript/commonjs/control-surface/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/control-surface/types.d.ts +37 -0
- package/lib/typescript/commonjs/control-surface/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/field/Field.d.ts +1 -1
- package/lib/typescript/commonjs/field/Field.d.ts.map +1 -1
- package/lib/typescript/commonjs/field/context.d.ts +55 -0
- package/lib/typescript/commonjs/field/context.d.ts.map +1 -0
- package/lib/typescript/commonjs/field/index.d.ts +2 -0
- package/lib/typescript/commonjs/field/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/field/types.d.ts +22 -3
- package/lib/typescript/commonjs/field/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/floating/TriggerSlot.d.ts.map +1 -1
- package/lib/typescript/commonjs/glass/GlassIsland.d.ts +4 -0
- package/lib/typescript/commonjs/glass/GlassIsland.d.ts.map +1 -0
- package/lib/typescript/commonjs/glass/GlassSurface.d.ts.map +1 -1
- package/lib/typescript/commonjs/glass/index.d.ts +2 -1
- package/lib/typescript/commonjs/glass/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/glass/types.d.ts +43 -0
- package/lib/typescript/commonjs/glass/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -2
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +3 -2
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/label/Label.d.ts.map +1 -1
- package/lib/typescript/commonjs/label/types.d.ts +7 -1
- package/lib/typescript/commonjs/label/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/layout/bottom-edge.d.ts.map +1 -1
- package/lib/typescript/commonjs/layout/edge-store.d.ts +31 -0
- package/lib/typescript/commonjs/layout/edge-store.d.ts.map +1 -0
- package/lib/typescript/commonjs/layout/index.d.ts +8 -3
- package/lib/typescript/commonjs/layout/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/layout/scroll-offset.d.ts +16 -0
- package/lib/typescript/commonjs/layout/scroll-offset.d.ts.map +1 -0
- package/lib/typescript/commonjs/layout/top-edge.d.ts +77 -0
- package/lib/typescript/commonjs/layout/top-edge.d.ts.map +1 -0
- package/lib/typescript/commonjs/message-bubble/MessageBubble.d.ts +1 -1
- package/lib/typescript/commonjs/message-bubble/MessageBubble.d.ts.map +1 -1
- package/lib/typescript/commonjs/message-bubble/index.d.ts +1 -1
- package/lib/typescript/commonjs/message-bubble/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/message-bubble/types.d.ts +27 -5
- package/lib/typescript/commonjs/message-bubble/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/page-header/EdgeScrim.d.ts +95 -0
- package/lib/typescript/commonjs/page-header/EdgeScrim.d.ts.map +1 -0
- package/lib/typescript/commonjs/page-header/PageHeader.d.ts +1 -1
- package/lib/typescript/commonjs/page-header/PageHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/page-header/index.d.ts +1 -1
- package/lib/typescript/commonjs/page-header/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/page-header/types.d.ts +97 -10
- package/lib/typescript/commonjs/page-header/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/provider/BloomProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/styles/svg-id.d.ts +20 -0
- package/lib/typescript/commonjs/styles/svg-id.d.ts.map +1 -0
- package/lib/typescript/commonjs/text-field/TextField.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/glass-colors.d.ts +67 -0
- package/lib/typescript/commonjs/theme/glass-colors.d.ts.map +1 -1
- package/lib/typescript/module/button/Button.d.ts.map +1 -1
- package/lib/typescript/module/button-group/ButtonGroup.d.ts.map +1 -1
- package/lib/typescript/module/button-group/types.d.ts +61 -5
- package/lib/typescript/module/button-group/types.d.ts.map +1 -1
- package/lib/typescript/module/control-surface/ControlSurface.d.ts +4 -0
- package/lib/typescript/module/control-surface/ControlSurface.d.ts.map +1 -0
- package/lib/typescript/module/control-surface/context.d.ts +21 -0
- package/lib/typescript/module/control-surface/context.d.ts.map +1 -0
- package/lib/typescript/module/control-surface/index.d.ts +4 -0
- package/lib/typescript/module/control-surface/index.d.ts.map +1 -0
- package/lib/typescript/module/control-surface/types.d.ts +37 -0
- package/lib/typescript/module/control-surface/types.d.ts.map +1 -0
- package/lib/typescript/module/field/Field.d.ts +1 -1
- package/lib/typescript/module/field/Field.d.ts.map +1 -1
- package/lib/typescript/module/field/context.d.ts +55 -0
- package/lib/typescript/module/field/context.d.ts.map +1 -0
- package/lib/typescript/module/field/index.d.ts +2 -0
- package/lib/typescript/module/field/index.d.ts.map +1 -1
- package/lib/typescript/module/field/types.d.ts +22 -3
- package/lib/typescript/module/field/types.d.ts.map +1 -1
- package/lib/typescript/module/floating/TriggerSlot.d.ts.map +1 -1
- package/lib/typescript/module/glass/GlassIsland.d.ts +4 -0
- package/lib/typescript/module/glass/GlassIsland.d.ts.map +1 -0
- package/lib/typescript/module/glass/GlassSurface.d.ts.map +1 -1
- package/lib/typescript/module/glass/index.d.ts +2 -1
- package/lib/typescript/module/glass/index.d.ts.map +1 -1
- package/lib/typescript/module/glass/types.d.ts +43 -0
- package/lib/typescript/module/glass/types.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -2
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +3 -2
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/label/Label.d.ts.map +1 -1
- package/lib/typescript/module/label/types.d.ts +7 -1
- package/lib/typescript/module/label/types.d.ts.map +1 -1
- package/lib/typescript/module/layout/bottom-edge.d.ts.map +1 -1
- package/lib/typescript/module/layout/edge-store.d.ts +31 -0
- package/lib/typescript/module/layout/edge-store.d.ts.map +1 -0
- package/lib/typescript/module/layout/index.d.ts +8 -3
- package/lib/typescript/module/layout/index.d.ts.map +1 -1
- package/lib/typescript/module/layout/scroll-offset.d.ts +16 -0
- package/lib/typescript/module/layout/scroll-offset.d.ts.map +1 -0
- package/lib/typescript/module/layout/top-edge.d.ts +77 -0
- package/lib/typescript/module/layout/top-edge.d.ts.map +1 -0
- package/lib/typescript/module/message-bubble/MessageBubble.d.ts +1 -1
- package/lib/typescript/module/message-bubble/MessageBubble.d.ts.map +1 -1
- package/lib/typescript/module/message-bubble/index.d.ts +1 -1
- package/lib/typescript/module/message-bubble/index.d.ts.map +1 -1
- package/lib/typescript/module/message-bubble/types.d.ts +27 -5
- package/lib/typescript/module/message-bubble/types.d.ts.map +1 -1
- package/lib/typescript/module/page-header/EdgeScrim.d.ts +95 -0
- package/lib/typescript/module/page-header/EdgeScrim.d.ts.map +1 -0
- package/lib/typescript/module/page-header/PageHeader.d.ts +1 -1
- package/lib/typescript/module/page-header/PageHeader.d.ts.map +1 -1
- package/lib/typescript/module/page-header/index.d.ts +1 -1
- package/lib/typescript/module/page-header/index.d.ts.map +1 -1
- package/lib/typescript/module/page-header/types.d.ts +97 -10
- package/lib/typescript/module/page-header/types.d.ts.map +1 -1
- package/lib/typescript/module/provider/BloomProvider.d.ts.map +1 -1
- package/lib/typescript/module/styles/svg-id.d.ts +20 -0
- package/lib/typescript/module/styles/svg-id.d.ts.map +1 -0
- package/lib/typescript/module/text-field/TextField.d.ts.map +1 -1
- package/lib/typescript/module/theme/glass-colors.d.ts +67 -0
- package/lib/typescript/module/theme/glass-colors.d.ts.map +1 -1
- package/package.json +15 -1
- package/src/button/Button.tsx +10 -1
- package/src/button-group/ButtonGroup.tsx +146 -19
- package/src/button-group/types.ts +68 -5
- package/src/control-surface/ControlSurface.tsx +41 -0
- package/src/control-surface/context.ts +81 -0
- package/src/control-surface/index.ts +4 -0
- package/src/control-surface/types.ts +40 -0
- package/src/field/Field.tsx +84 -27
- package/src/field/context.ts +110 -0
- package/src/field/index.ts +3 -0
- package/src/field/types.ts +22 -3
- package/src/floating/TriggerSlot.tsx +35 -1
- package/src/glass/GlassIsland.tsx +109 -0
- package/src/glass/GlassSurface.tsx +26 -2
- package/src/glass/index.ts +3 -0
- package/src/glass/types.ts +45 -0
- package/src/index.ts +7 -0
- package/src/index.web.ts +7 -0
- package/src/label/Label.tsx +3 -1
- package/src/label/types.ts +7 -1
- package/src/layout/bottom-edge.tsx +3 -56
- package/src/layout/edge-store.ts +69 -0
- package/src/layout/index.ts +8 -3
- package/src/layout/scroll-offset.ts +76 -0
- package/src/layout/top-edge.tsx +109 -0
- package/src/message-bubble/MessageBubble.tsx +19 -5
- package/src/message-bubble/index.ts +1 -0
- package/src/message-bubble/types.ts +28 -5
- package/src/page-header/EdgeScrim.tsx +118 -0
- package/src/page-header/PageHeader.tsx +253 -61
- package/src/page-header/index.ts +9 -1
- package/src/page-header/types.ts +103 -10
- package/src/provider/BloomProvider.tsx +12 -1
- package/src/social-button/OxyMark.tsx +1 -1
- package/src/social-button/SocialButton.tsx +1 -1
- package/src/styles/svg-id.ts +23 -0
- package/src/text-field/TextField.tsx +44 -5
- package/src/theme/glass-colors.ts +80 -0
- package/lib/commonjs/social-button/use-svg-id.js +0 -16
- package/lib/commonjs/social-button/use-svg-id.js.map +0 -1
- package/lib/module/social-button/use-svg-id.js +0 -13
- package/lib/module/social-button/use-svg-id.js.map +0 -1
- package/lib/typescript/commonjs/social-button/use-svg-id.d.ts +0 -7
- package/lib/typescript/commonjs/social-button/use-svg-id.d.ts.map +0 -1
- package/lib/typescript/module/social-button/use-svg-id.d.ts +0 -7
- package/lib/typescript/module/social-button/use-svg-id.d.ts.map +0 -1
- package/src/social-button/use-svg-id.ts +0 -10
package/src/field/Field.tsx
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import React, { memo, useMemo } from 'react';
|
|
1
|
+
import React, { memo, useId, useMemo } from 'react';
|
|
2
2
|
import { View } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import { Text } from '../typography';
|
|
5
5
|
import { Label } from '../label';
|
|
6
6
|
import { atoms as a } from '../styles';
|
|
7
7
|
import { TEXT_FIELD_STACK_GAP, useTextFieldPalette } from '../text-field/shared';
|
|
8
|
+
import { FieldControlProvider, type FieldControlValue } from './context';
|
|
8
9
|
import type { FieldProps } from './types';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -15,6 +16,20 @@ import type { FieldProps } from './types';
|
|
|
15
16
|
* Bloom's `Label`. Unlike `TextField` — which bakes its label/error into the
|
|
16
17
|
* input chrome — `Field` wraps any arbitrary control (a `Switch`, a
|
|
17
18
|
* `SegmentedControl`, a custom picker, etc.).
|
|
19
|
+
*
|
|
20
|
+
* ── IT ASSOCIATES, IT DOES NOT ONLY STACK ───────────────────────────────────
|
|
21
|
+
*
|
|
22
|
+
* The three pieces are wired to the control through `field/context.ts`: the
|
|
23
|
+
* control's id, `aria-describedby` for whichever of description/error is
|
|
24
|
+
* showing, the invalid state, the required state and the disabled state. A
|
|
25
|
+
* control opts in by reading `useFieldControl()`; one that does not renders
|
|
26
|
+
* exactly as before. Ids are generated from `useId` when the caller gives no
|
|
27
|
+
* `nativeID`, so they survive SSR and hydration.
|
|
28
|
+
*
|
|
29
|
+
* `multiple` is the several-controls case — a radio set, a date's three boxes.
|
|
30
|
+
* The field becomes a labelled `group` and stops handing out a control id,
|
|
31
|
+
* because one id on three inputs is invalid and one error described by three
|
|
32
|
+
* inputs is read three times.
|
|
18
33
|
*/
|
|
19
34
|
/** `HintText`: 4px under the control (`gap-1`) plus its own 1px `pt-px`. */
|
|
20
35
|
const hintStyle = { marginTop: TEXT_FIELD_STACK_GAP, paddingTop: 1 } as const;
|
|
@@ -26,42 +41,84 @@ const FieldComponent = function Field({
|
|
|
26
41
|
error,
|
|
27
42
|
required = false,
|
|
28
43
|
disabled = false,
|
|
44
|
+
multiple = false,
|
|
29
45
|
nativeID,
|
|
30
46
|
style,
|
|
31
47
|
testID,
|
|
32
48
|
}: FieldProps) {
|
|
33
49
|
const palette = useTextFieldPalette();
|
|
34
50
|
const hasError = typeof error === 'string' && error.length > 0;
|
|
35
|
-
|
|
36
|
-
|
|
51
|
+
// `useId` is the seed only when the caller gave no id, so an explicit
|
|
52
|
+
// `nativeID` still owns every derived id and a consumer that wired its own
|
|
53
|
+
// `aria-describedby` keeps working.
|
|
54
|
+
const generated = useId().replace(/[^a-zA-Z0-9]/g, '');
|
|
55
|
+
const base = nativeID ?? `bloom-field-${generated}`;
|
|
56
|
+
const labelID = `${base}-label`;
|
|
57
|
+
const descriptionID = `${base}-description`;
|
|
58
|
+
const errorID = `${base}-error`;
|
|
59
|
+
const hasDescription = !hasError && description != null;
|
|
60
|
+
|
|
61
|
+
const field = useMemo<FieldControlValue>(
|
|
62
|
+
() => ({
|
|
63
|
+
controlId: multiple ? undefined : base,
|
|
64
|
+
labelId: label != null ? labelID : undefined,
|
|
65
|
+
// In a `multiple` field the GROUP is described, not each control: an
|
|
66
|
+
// error pointed at by three inputs is announced three times.
|
|
67
|
+
describedBy: multiple
|
|
68
|
+
? undefined
|
|
69
|
+
: hasError
|
|
70
|
+
? errorID
|
|
71
|
+
: hasDescription
|
|
72
|
+
? descriptionID
|
|
73
|
+
: undefined,
|
|
74
|
+
labelText: typeof label === 'string' ? label : undefined,
|
|
75
|
+
invalid: hasError,
|
|
76
|
+
required,
|
|
77
|
+
disabled,
|
|
78
|
+
}),
|
|
79
|
+
[multiple, base, label, labelID, hasError, errorID, hasDescription, descriptionID, required, disabled],
|
|
80
|
+
);
|
|
37
81
|
|
|
38
82
|
return (
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
83
|
+
<FieldControlProvider value={field}>
|
|
84
|
+
<View
|
|
85
|
+
style={[a.w_full, style]}
|
|
86
|
+
testID={testID}
|
|
87
|
+
// A group only when it holds several controls. A `group` around one
|
|
88
|
+
// control is a wrapper a screen reader has to step into for nothing,
|
|
89
|
+
// and it competes with the control's own name.
|
|
90
|
+
role={multiple ? 'group' : undefined}
|
|
91
|
+
aria-labelledby={multiple && label != null ? labelID : undefined}
|
|
92
|
+
aria-describedby={
|
|
93
|
+
multiple ? (hasError ? errorID : hasDescription ? descriptionID : undefined) : undefined
|
|
94
|
+
}
|
|
95
|
+
>
|
|
96
|
+
{label != null ? (
|
|
97
|
+
<Label nativeID={labelID} htmlFor={multiple ? null : base} required={required} disabled={disabled}>
|
|
98
|
+
{label}
|
|
99
|
+
</Label>
|
|
100
|
+
) : null}
|
|
45
101
|
|
|
46
|
-
|
|
102
|
+
{children}
|
|
47
103
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
104
|
+
{hasError ? (
|
|
105
|
+
<Text
|
|
106
|
+
variant="caption-1-medium"
|
|
107
|
+
nativeID={errorID}
|
|
108
|
+
accessibilityRole="alert"
|
|
109
|
+
style={[hintStyle, { color: palette.error }]}>
|
|
110
|
+
{error}
|
|
111
|
+
</Text>
|
|
112
|
+
) : description != null ? (
|
|
113
|
+
<Text
|
|
114
|
+
variant="caption-1-medium"
|
|
115
|
+
nativeID={descriptionID}
|
|
116
|
+
style={[hintStyle, { color: disabled ? palette.placeholder : palette.hint }]}>
|
|
117
|
+
{description}
|
|
118
|
+
</Text>
|
|
119
|
+
) : null}
|
|
120
|
+
</View>
|
|
121
|
+
</FieldControlProvider>
|
|
65
122
|
);
|
|
66
123
|
};
|
|
67
124
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FIELD MEMBERSHIP — what a `Field` tells the control inside it, so the label,
|
|
3
|
+
* the description and the error are wired to it rather than merely stacked
|
|
4
|
+
* above and below it.
|
|
5
|
+
*
|
|
6
|
+
* ## What was missing
|
|
7
|
+
*
|
|
8
|
+
* `Field` drew the three pieces and rendered `children` untouched. Every
|
|
9
|
+
* association — the id the label points at, the `aria-describedby` that makes a
|
|
10
|
+
* screen reader read the error, the invalid state, the disabled state — was the
|
|
11
|
+
* app's to wire by hand, on every field, and the failure when it was not wired
|
|
12
|
+
* is silent: the field LOOKS associated, because the parts are in the right
|
|
13
|
+
* places and the error is red.
|
|
14
|
+
*
|
|
15
|
+
* ## The contract
|
|
16
|
+
*
|
|
17
|
+
* `Field` publishes ids and state; a control that can accept them calls
|
|
18
|
+
* {@link useFieldControl} and applies them. Opt-IN, not magic: an arbitrary
|
|
19
|
+
* child cannot be given behaviour by being wrapped, and a container that
|
|
20
|
+
* inspected its children's types to decide what to do with them would be the
|
|
21
|
+
* thing `docs/composition.mdx` argues against. A control that does not read the
|
|
22
|
+
* context renders exactly as it did before.
|
|
23
|
+
*
|
|
24
|
+
* ## The ids are stable across SSR
|
|
25
|
+
*
|
|
26
|
+
* From `useId`, seeded by the caller's `nativeID` when it gave one. A
|
|
27
|
+
* module-scope counter would number the server's render and the client's
|
|
28
|
+
* hydration differently, so the `for` attribute React sent and the `id` it
|
|
29
|
+
* re-rendered would stop matching — which is invisible until someone clicks a
|
|
30
|
+
* label.
|
|
31
|
+
*
|
|
32
|
+
* ## One control or several
|
|
33
|
+
*
|
|
34
|
+
* A field with ONE control gives it the field's id, and the error is described
|
|
35
|
+
* BY that control. A field with several (a radio set, a date's three inputs)
|
|
36
|
+
* must not: three inputs sharing one id is invalid HTML, and an error attached
|
|
37
|
+
* to each of them is read three times. `multiple` switches the field to a
|
|
38
|
+
* labelled `group` and publishes no control id at all — the group carries the
|
|
39
|
+
* name and the description, and each control keeps its own.
|
|
40
|
+
*
|
|
41
|
+
* ## What does NOT travel here
|
|
42
|
+
*
|
|
43
|
+
* The VALUE, the validation and the submission. Bloom owns presentation and
|
|
44
|
+
* association; an app's form library owns the data. A context that also carried
|
|
45
|
+
* the value would make `Field` a form framework, which is the one thing this is
|
|
46
|
+
* not.
|
|
47
|
+
*/
|
|
48
|
+
import { createContext, useContext } from 'react';
|
|
49
|
+
|
|
50
|
+
export interface FieldControlValue {
|
|
51
|
+
/**
|
|
52
|
+
* The id the single control should carry, so the label's `for` points at it.
|
|
53
|
+
* `undefined` in a `multiple` field, where each control keeps its own.
|
|
54
|
+
*/
|
|
55
|
+
controlId?: string;
|
|
56
|
+
/** The label's own id — for a control that names itself by reference. */
|
|
57
|
+
labelId?: string;
|
|
58
|
+
/**
|
|
59
|
+
* The id list for `aria-describedby`: the description, or the error when
|
|
60
|
+
* there is one. `undefined` when the field has neither.
|
|
61
|
+
*/
|
|
62
|
+
describedBy?: string;
|
|
63
|
+
/**
|
|
64
|
+
* The label as TEXT, when the field's label is a string.
|
|
65
|
+
*
|
|
66
|
+
* This is the cross-platform half of naming. On web a `<label for>` is a real
|
|
67
|
+
* association and the control needs no name of its own; React Native has no
|
|
68
|
+
* equivalent, so the control has to carry the name, and the name it should
|
|
69
|
+
* carry is the one the user can see. A control that has both its own label
|
|
70
|
+
* prop and this should prefer this — otherwise the visible label and the
|
|
71
|
+
* announced name can disagree, which is worse than either alone.
|
|
72
|
+
*/
|
|
73
|
+
labelText?: string;
|
|
74
|
+
/** Whether the field is currently in its error state. */
|
|
75
|
+
invalid: boolean;
|
|
76
|
+
/** Whether the field is marked required. */
|
|
77
|
+
required: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* The field's disabled state.
|
|
80
|
+
*
|
|
81
|
+
* A CONSTRAINT, not a default: a control inside a disabled field must not be
|
|
82
|
+
* able to re-enable itself, so a consumer applies this with `||` against its
|
|
83
|
+
* own prop rather than with `??`.
|
|
84
|
+
*/
|
|
85
|
+
disabled: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const FieldControlContext = createContext<FieldControlValue | null>(null);
|
|
89
|
+
FieldControlContext.displayName = 'BloomFieldControlContext';
|
|
90
|
+
|
|
91
|
+
/** @internal The provider `Field` renders. */
|
|
92
|
+
export const FieldControlProvider = FieldControlContext.Provider;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The enclosing `Field`'s association, or `null` outside one.
|
|
96
|
+
*
|
|
97
|
+
* ```tsx
|
|
98
|
+
* const field = useFieldControl();
|
|
99
|
+
* <TextInput
|
|
100
|
+
* nativeID={field?.controlId ?? nativeID}
|
|
101
|
+
* aria-describedby={field?.describedBy}
|
|
102
|
+
* aria-invalid={field?.invalid || isInvalid || undefined}
|
|
103
|
+
* accessibilityLabel={field?.labelText ?? label}
|
|
104
|
+
* editable={!(field?.disabled || disabled)}
|
|
105
|
+
* />
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export function useFieldControl(): FieldControlValue | null {
|
|
109
|
+
return useContext(FieldControlContext);
|
|
110
|
+
}
|
package/src/field/index.ts
CHANGED
package/src/field/types.ts
CHANGED
|
@@ -14,11 +14,30 @@ export interface FieldProps {
|
|
|
14
14
|
error?: string | null;
|
|
15
15
|
/** Mark the field's label with a required asterisk. */
|
|
16
16
|
required?: boolean;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Disables the label/description styling AND, for a control that reads the
|
|
19
|
+
* field context, the control itself.
|
|
20
|
+
*
|
|
21
|
+
* It is a CONSTRAINT rather than a default: a control inside a disabled field
|
|
22
|
+
* combines it with `||`, so nothing inside can re-enable itself.
|
|
23
|
+
*/
|
|
18
24
|
disabled?: boolean;
|
|
19
25
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
26
|
+
* The field holds SEVERAL controls — a radio set, a date's three boxes.
|
|
27
|
+
*
|
|
28
|
+
* It becomes a labelled `group` and publishes no control id, because one id
|
|
29
|
+
* shared by three inputs is invalid HTML and one error described by three
|
|
30
|
+
* inputs is announced three times. Each control keeps its own id and name.
|
|
31
|
+
*/
|
|
32
|
+
multiple?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The control's id, wired to the label's `htmlFor` on web so a label click
|
|
35
|
+
* focuses it.
|
|
36
|
+
*
|
|
37
|
+
* Optional now: omitted, the field generates one from `useId` and hands it to
|
|
38
|
+
* a control that reads `useFieldControl()`. Pass it when the id has to be a
|
|
39
|
+
* particular string — an app's form library keyed by field name, or a control
|
|
40
|
+
* that does not read the context and needs the id written on it by hand.
|
|
22
41
|
*/
|
|
23
42
|
nativeID?: string;
|
|
24
43
|
style?: StyleProp<ViewStyle>;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* `alignSelf: 'flex-start'` keeps that wrapper from stretching across its
|
|
23
23
|
* parent, which would anchor the surface to the row rather than to the control.
|
|
24
24
|
*/
|
|
25
|
-
import React, { Children, cloneElement, isValidElement, useRef } from 'react';
|
|
25
|
+
import React, { Children, cloneElement, Fragment, isValidElement, useRef } from 'react';
|
|
26
26
|
import { StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
27
27
|
|
|
28
28
|
import { useRestoreAccessibilityFocus } from '../hooks/use-accessibility-focus';
|
|
@@ -50,6 +50,16 @@ export interface TriggerSlotProps {
|
|
|
50
50
|
* several children, which is the right failure: `asChild` has no meaning for a
|
|
51
51
|
* fragment, and silently picking the first child is how a trigger ends up
|
|
52
52
|
* un-pressable with nothing logged.
|
|
53
|
+
*
|
|
54
|
+
* ── A FRAGMENT PASSES `isValidElement` AND DROPS EVERY PROP ─────────────────
|
|
55
|
+
*
|
|
56
|
+
* `Children.only` accepts `<>{child}</>` — a fragment IS one child, and it IS a
|
|
57
|
+
* valid element — and `cloneElement` on it succeeds, returning a fragment
|
|
58
|
+
* carrying an `onPress` and an `aria-expanded` that React then discards,
|
|
59
|
+
* because a fragment renders no host node to put them on. Nothing throws. The
|
|
60
|
+
* trigger is simply dead, and the caller's most likely next move is to wrap it
|
|
61
|
+
* in another fragment. So it is rejected here, by name, with the fix in the
|
|
62
|
+
* message.
|
|
53
63
|
*/
|
|
54
64
|
function cloneTrigger(
|
|
55
65
|
children: React.ReactNode,
|
|
@@ -61,6 +71,11 @@ function cloneTrigger(
|
|
|
61
71
|
'A trigger with `asChild` needs a single React element child. Drop `asChild` to let Bloom render the pressable.',
|
|
62
72
|
);
|
|
63
73
|
}
|
|
74
|
+
if (child.type === Fragment) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
'A trigger with `asChild` cannot clone a fragment — it renders no element, so the press handler and the a11y state would be dropped silently. Pass the control itself as the single child.',
|
|
77
|
+
);
|
|
78
|
+
}
|
|
64
79
|
const childProps = child.props;
|
|
65
80
|
// A disabled trigger must not OPEN, and the child's own `disabled` is where a
|
|
66
81
|
// caller most naturally writes that — a disabled `Pressable` child rather than
|
|
@@ -73,10 +88,28 @@ function cloneTrigger(
|
|
|
73
88
|
...handle,
|
|
74
89
|
// Compose rather than overwrite: the child's own handler runs first, then
|
|
75
90
|
// ours opens the surface.
|
|
91
|
+
//
|
|
92
|
+
// ── AND THE CHILD'S HANDLER CAN CANCEL THE OPEN ─────────────────────────
|
|
93
|
+
//
|
|
94
|
+
// `event.preventDefault()` in the child's own handler stops the surface
|
|
95
|
+
// from opening. Composition needs an answer to "what if the first handler
|
|
96
|
+
// decides this press should not do the second thing" — a trigger that has
|
|
97
|
+
// just discovered the form is dirty, or that the row it belongs to was
|
|
98
|
+
// removed — and without one the only way to express it is to stop using
|
|
99
|
+
// `asChild` and rebuild the control.
|
|
100
|
+
//
|
|
101
|
+
// It is `preventDefault` rather than a Bloom-specific return value because
|
|
102
|
+
// the event already carries it on BOTH platforms and both mean the same
|
|
103
|
+
// thing by it: on web `onPress` is handed React's synthetic `click` event,
|
|
104
|
+
// and on native the responder event is a React synthetic event too. One
|
|
105
|
+
// consequence worth knowing on web: the same call also suppresses the
|
|
106
|
+
// browser's own default for that click, so cancelling the open on an
|
|
107
|
+
// anchor child cancels its navigation as well.
|
|
76
108
|
onPress: isDisabled
|
|
77
109
|
? childProps.onPress
|
|
78
110
|
: (event) => {
|
|
79
111
|
childProps.onPress?.(event);
|
|
112
|
+
if (event?.defaultPrevented) return;
|
|
80
113
|
handle.onPress(event);
|
|
81
114
|
},
|
|
82
115
|
onLongPress:
|
|
@@ -84,6 +117,7 @@ function cloneTrigger(
|
|
|
84
117
|
? childProps.onLongPress
|
|
85
118
|
: (event) => {
|
|
86
119
|
childProps.onLongPress?.(event);
|
|
120
|
+
if (event?.defaultPrevented) return;
|
|
87
121
|
handle.onLongPress?.(event);
|
|
88
122
|
},
|
|
89
123
|
// A label the caller already wrote is more specific than the family's.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import React, { memo, useMemo } from 'react';
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { ControlSurface } from '../control-surface';
|
|
5
|
+
import { bloomShadowStyle } from '../design-tokens/shadows';
|
|
6
|
+
import { resolveSurfaceLevel } from '../styles/surface-levels';
|
|
7
|
+
import { borderRadius } from '../styles/tokens';
|
|
8
|
+
import { resolveChromeGlassColors } from '../theme/glass-colors';
|
|
9
|
+
import { useTheme } from '../theme/use-theme';
|
|
10
|
+
import { GlassSurface } from './GlassSurface';
|
|
11
|
+
import type { GlassIslandProps } from './types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* An ISLAND: one translucent, rounded container holding one or more controls
|
|
15
|
+
* that float over content the container does not own.
|
|
16
|
+
*
|
|
17
|
+
* ── WHY THIS EXISTS RATHER THAN A SECOND MATERIAL ───────────────────────────
|
|
18
|
+
*
|
|
19
|
+
* `GlassSurface` is the material — blur, tint, sheen, lit rim — and by its own
|
|
20
|
+
* contract it does NOT own the geometry, the hairline or the shadow: it clips
|
|
21
|
+
* itself, and a shadow drawn on a clipping node is clipped away on iOS. So
|
|
22
|
+
* every caller that wants a floating pane has to assemble the same four things
|
|
23
|
+
* in the same order. Two callers now want exactly that assembly — a
|
|
24
|
+
* `ButtonGroup` in its glass variant, and a page header's back capsule — which
|
|
25
|
+
* is the second real need that justifies writing it once.
|
|
26
|
+
*
|
|
27
|
+
* ── THE FILL IS THE LADDER'S, NOT A LOCAL PALETTE ───────────────────────────
|
|
28
|
+
*
|
|
29
|
+
* The island paints rung 1 of `styles/surface-levels` — byte-for-byte the fill
|
|
30
|
+
* a card and a menu panel already paint — at the CHROME alpha. That is the
|
|
31
|
+
* whole reason it reads as Bloom rather than as a fourth material invented for
|
|
32
|
+
* headers, and it is why a preset change moves islands with everything else.
|
|
33
|
+
*
|
|
34
|
+
* ── WHAT IT TELLS ITS CHILDREN ──────────────────────────────────────────────
|
|
35
|
+
*
|
|
36
|
+
* It mounts a `ControlSurface` with `material: 'glass'`, so a control inside it
|
|
37
|
+
* paints flush — no second fill, no second blur — without the caller writing
|
|
38
|
+
* `variant="glass"` on every one. The nesting rule is the material's, not a
|
|
39
|
+
* preference: a translucent pane inside a translucent pane composites two
|
|
40
|
+
* alphas and two blurs for one pane's worth of depth, and on Android the second
|
|
41
|
+
* blur is not available at all.
|
|
42
|
+
*
|
|
43
|
+
* It does NOT publish a `SurfaceLevelProvider`. A published surface fill is a
|
|
44
|
+
* promise that a descendant can read the pixel it lands on, and the pixel here
|
|
45
|
+
* depends on content Bloom cannot see.
|
|
46
|
+
*
|
|
47
|
+
* ── NO `overflow: hidden` ───────────────────────────────────────────────────
|
|
48
|
+
*
|
|
49
|
+
* Deliberately absent, twice over: `GlassSurface` clips its own layers, and
|
|
50
|
+
* `clipsToBounds` on iOS would clip this box's drop shadow away. Controls
|
|
51
|
+
* inside round their own press highlight instead of relying on the island to
|
|
52
|
+
* cut it.
|
|
53
|
+
*/
|
|
54
|
+
const GlassIslandComponent: React.FC<GlassIslandProps> = ({
|
|
55
|
+
children,
|
|
56
|
+
radius = borderRadius.full,
|
|
57
|
+
role,
|
|
58
|
+
accessibilityLabel,
|
|
59
|
+
sheen = true,
|
|
60
|
+
style,
|
|
61
|
+
testID,
|
|
62
|
+
}) => {
|
|
63
|
+
const theme = useTheme();
|
|
64
|
+
const paint = useMemo(() => {
|
|
65
|
+
const level = resolveSurfaceLevel(theme, 1);
|
|
66
|
+
return { fill: level.background, glass: resolveChromeGlassColors(level.background, level.border, theme.isDark) };
|
|
67
|
+
}, [theme]);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<ControlSurface material="glass">
|
|
71
|
+
<View
|
|
72
|
+
role={role}
|
|
73
|
+
accessibilityLabel={accessibilityLabel}
|
|
74
|
+
testID={testID}
|
|
75
|
+
style={[
|
|
76
|
+
styles.island,
|
|
77
|
+
{
|
|
78
|
+
borderRadius: radius,
|
|
79
|
+
borderWidth: paint.glass.hairlineWidth,
|
|
80
|
+
borderColor: paint.glass.hairline,
|
|
81
|
+
},
|
|
82
|
+
bloomShadowStyle('glass'),
|
|
83
|
+
style,
|
|
84
|
+
]}
|
|
85
|
+
>
|
|
86
|
+
<GlassSurface
|
|
87
|
+
fill={paint.fill}
|
|
88
|
+
material="chrome"
|
|
89
|
+
radius={radius}
|
|
90
|
+
sheen={sheen}
|
|
91
|
+
testID={testID ? `${testID}-material` : undefined}
|
|
92
|
+
/>
|
|
93
|
+
{children}
|
|
94
|
+
</View>
|
|
95
|
+
</ControlSurface>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const styles = StyleSheet.create({
|
|
100
|
+
island: {
|
|
101
|
+
flexDirection: 'row',
|
|
102
|
+
alignItems: 'stretch',
|
|
103
|
+
alignSelf: 'flex-start',
|
|
104
|
+
position: 'relative',
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export const GlassIsland = memo(GlassIslandComponent);
|
|
109
|
+
GlassIsland.displayName = 'GlassIsland';
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
GLASS_BLUR_INTENSITY,
|
|
9
9
|
GLASS_RIM_HIGHLIGHT,
|
|
10
10
|
GLASS_SHEEN,
|
|
11
|
+
resolveChromeGlassColors,
|
|
11
12
|
resolveGlassColors,
|
|
12
13
|
} from '../theme/glass-colors';
|
|
13
14
|
import type { GlassSurfaceProps } from './types';
|
|
@@ -54,12 +55,19 @@ let glassSheenIdCounter = 0;
|
|
|
54
55
|
const GlassSurfaceComponent: React.FC<GlassSurfaceProps> = ({
|
|
55
56
|
fill,
|
|
56
57
|
radius,
|
|
58
|
+
material = 'accent',
|
|
57
59
|
sheen = true,
|
|
58
60
|
style,
|
|
59
61
|
testID,
|
|
60
62
|
}) => {
|
|
61
63
|
const theme = useTheme();
|
|
62
|
-
const glass = useMemo(
|
|
64
|
+
const glass = useMemo(
|
|
65
|
+
() =>
|
|
66
|
+
material === 'chrome'
|
|
67
|
+
? resolveChromeGlassColors(fill, fill, theme.isDark)
|
|
68
|
+
: resolveGlassColors(fill),
|
|
69
|
+
[fill, material, theme.isDark],
|
|
70
|
+
);
|
|
63
71
|
// Per instance, because two panes in one document would otherwise share an id
|
|
64
72
|
// and the survivor of an unmount would reference a gradient that is gone. Same
|
|
65
73
|
// counter shape as `AvatarRing`.
|
|
@@ -104,8 +112,24 @@ const GlassSurfaceComponent: React.FC<GlassSurfaceProps> = ({
|
|
|
104
112
|
content Bloom does not own.
|
|
105
113
|
*/}
|
|
106
114
|
<View style={[StyleSheet.absoluteFill, { backgroundColor: glass.fill }]} />
|
|
115
|
+
{/*
|
|
116
|
+
The sheen's `width`/`height` are load-bearing beside `absoluteFill`: an
|
|
117
|
+
`<svg>` is a REPLACED element, so `width: auto` resolves to its intrinsic
|
|
118
|
+
size and an SVG without one falls back to CSS's 300 x 150. `left: 0;
|
|
119
|
+
right: 0` does not stretch it — the `right` is over-constrained and
|
|
120
|
+
dropped. On a surface narrower than 300 the sheen still covers, but its
|
|
121
|
+
gradient is scaled over 150px instead of the surface's own height, so
|
|
122
|
+
only the top of the ramp is ever visible; on a wider one it stops dead
|
|
123
|
+
at 300px. Native lays the host view out with flexbox and is unaffected.
|
|
124
|
+
Gate: `src/__tests__/svg-absolute-fill-size.test.ts`.
|
|
125
|
+
*/}
|
|
107
126
|
{sheen ? (
|
|
108
|
-
<Svg
|
|
127
|
+
<Svg
|
|
128
|
+
width="100%"
|
|
129
|
+
height="100%"
|
|
130
|
+
style={StyleSheet.absoluteFill}
|
|
131
|
+
testID={testID ? `${testID}-sheen` : undefined}
|
|
132
|
+
>
|
|
109
133
|
<Defs>
|
|
110
134
|
{/* Top to bottom: simulated light from above. */}
|
|
111
135
|
{/*
|
package/src/glass/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { GlassIsland } from './GlassIsland';
|
|
1
2
|
export { GlassSurface } from './GlassSurface';
|
|
2
3
|
export {
|
|
3
4
|
GlassBlurTargetProvider,
|
|
@@ -5,6 +6,8 @@ export {
|
|
|
5
6
|
useWindowedBlurTarget,
|
|
6
7
|
} from './blur-target';
|
|
7
8
|
export type {
|
|
9
|
+
GlassIslandProps,
|
|
10
|
+
GlassMaterial,
|
|
8
11
|
GlassSurfaceProps,
|
|
9
12
|
GlassBlurTargetProviderProps,
|
|
10
13
|
GlassBlurWindowProps,
|
package/src/glass/types.ts
CHANGED
|
@@ -14,6 +14,20 @@ export interface GlassBlurWindowProps {
|
|
|
14
14
|
children?: ReactNode;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Which of the two glass ROLES a pane paints.
|
|
19
|
+
*
|
|
20
|
+
* `accent` is a brand fill over one of Bloom's own neutral surfaces — a
|
|
21
|
+
* `primary` control, labelled with that fill's own on-colour. `chrome` is a
|
|
22
|
+
* neutral surface off the ladder (`styles/surface-levels`) over content Bloom
|
|
23
|
+
* does NOT own — a page header's island — labelled with the theme's own `text`.
|
|
24
|
+
*
|
|
25
|
+
* They carry different alphas because they are priced against different
|
|
26
|
+
* backdrops, and in opposite directions; the measurement is on
|
|
27
|
+
* {@link ../theme/glass-colors#GLASS_CHROME_ALPHA}.
|
|
28
|
+
*/
|
|
29
|
+
export type GlassMaterial = 'accent' | 'chrome';
|
|
30
|
+
|
|
17
31
|
export interface GlassSurfaceProps {
|
|
18
32
|
/**
|
|
19
33
|
* The OPAQUE brand fill the pane is tinted with — `theme.colors.primary`,
|
|
@@ -35,6 +49,14 @@ export interface GlassSurfaceProps {
|
|
|
35
49
|
* hides until someone uses a different shape.
|
|
36
50
|
*/
|
|
37
51
|
radius: number;
|
|
52
|
+
/**
|
|
53
|
+
* Which role's alpha to apply to `fill`. Default `accent`.
|
|
54
|
+
*
|
|
55
|
+
* A pane cannot infer this from the colour it is given — `chrome`'s fill is a
|
|
56
|
+
* neutral that in light mode IS `colors.card`, which an accent pane could
|
|
57
|
+
* legitimately be asked to paint too.
|
|
58
|
+
*/
|
|
59
|
+
material?: GlassMaterial;
|
|
38
60
|
/**
|
|
39
61
|
* Whether to paint the sheen — the top-lit gradient that separates glass from
|
|
40
62
|
* flat tinted plastic.
|
|
@@ -49,3 +71,26 @@ export interface GlassSurfaceProps {
|
|
|
49
71
|
style?: StyleProp<ViewStyle>;
|
|
50
72
|
testID?: string;
|
|
51
73
|
}
|
|
74
|
+
|
|
75
|
+
export interface GlassIslandProps {
|
|
76
|
+
children?: ReactNode;
|
|
77
|
+
/**
|
|
78
|
+
* Corner radius. A full pill by default — an island is a capsule, and a
|
|
79
|
+
* capsule of icon-only controls is what makes the row read as separated
|
|
80
|
+
* groups rather than as one bar.
|
|
81
|
+
*/
|
|
82
|
+
radius?: number;
|
|
83
|
+
/**
|
|
84
|
+
* `'group'` when the island holds several RELATED actions the consumer has
|
|
85
|
+
* declared as one group. Omitted for a single control in its own capsule: a
|
|
86
|
+
* group of one is noise for a screen reader, and the control already names
|
|
87
|
+
* itself.
|
|
88
|
+
*/
|
|
89
|
+
role?: 'group';
|
|
90
|
+
/** Names the group. Required by `role="group"` to be worth announcing. */
|
|
91
|
+
accessibilityLabel?: string;
|
|
92
|
+
/** Forwarded to the material. See {@link GlassSurfaceProps.sheen}. */
|
|
93
|
+
sheen?: boolean;
|
|
94
|
+
style?: StyleProp<ViewStyle>;
|
|
95
|
+
testID?: string;
|
|
96
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -155,12 +155,18 @@ export type {
|
|
|
155
155
|
} from './surfaces';
|
|
156
156
|
export * from './button';
|
|
157
157
|
export * from './button-group';
|
|
158
|
+
export * from './control-surface';
|
|
158
159
|
export {
|
|
159
160
|
EDGE_GAP,
|
|
160
161
|
windowEdgeGap,
|
|
161
162
|
BottomEdgeProvider,
|
|
162
163
|
useBottomEdgeInset,
|
|
163
164
|
useClaimBottomEdge,
|
|
165
|
+
TopEdgeProvider,
|
|
166
|
+
useClaimTopEdge,
|
|
167
|
+
useTopEdgeInset,
|
|
168
|
+
ScrollOffsetProvider,
|
|
169
|
+
useScrollOffset,
|
|
164
170
|
} from './layout';
|
|
165
171
|
export { Fab } from './fab';
|
|
166
172
|
export type { FabProps, FabVariant, FabSize, FabPlacement } from './fab';
|
|
@@ -594,6 +600,7 @@ export type {
|
|
|
594
600
|
MessageListEntry,
|
|
595
601
|
MessageListItem,
|
|
596
602
|
MessageListProps,
|
|
603
|
+
MessageMediaFit,
|
|
597
604
|
MessagePosition,
|
|
598
605
|
MessageReaction,
|
|
599
606
|
MessageReplyPreview,
|
package/src/index.web.ts
CHANGED
|
@@ -160,12 +160,18 @@ export type {
|
|
|
160
160
|
} from './surfaces/index.web';
|
|
161
161
|
export * from './button/index.web';
|
|
162
162
|
export * from './button-group';
|
|
163
|
+
export * from './control-surface';
|
|
163
164
|
export {
|
|
164
165
|
EDGE_GAP,
|
|
165
166
|
windowEdgeGap,
|
|
166
167
|
BottomEdgeProvider,
|
|
167
168
|
useBottomEdgeInset,
|
|
168
169
|
useClaimBottomEdge,
|
|
170
|
+
TopEdgeProvider,
|
|
171
|
+
useClaimTopEdge,
|
|
172
|
+
useTopEdgeInset,
|
|
173
|
+
ScrollOffsetProvider,
|
|
174
|
+
useScrollOffset,
|
|
169
175
|
} from './layout';
|
|
170
176
|
export { Fab } from './fab/index.web';
|
|
171
177
|
export type { FabProps, FabVariant, FabSize, FabPlacement } from './fab/index.web';
|
|
@@ -599,6 +605,7 @@ export type {
|
|
|
599
605
|
MessageListEntry,
|
|
600
606
|
MessageListItem,
|
|
601
607
|
MessageListProps,
|
|
608
|
+
MessageMediaFit,
|
|
602
609
|
MessagePosition,
|
|
603
610
|
MessageReaction,
|
|
604
611
|
MessageReplyPreview,
|