@delacour/react-native-ui 0.1.0-alpha.3
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 +113 -0
- package/package.json +125 -0
- package/src/components/accordion/AGENTS.md +192 -0
- package/src/components/accordion/accordion-content.tsx +125 -0
- package/src/components/accordion/accordion-description.tsx +17 -0
- package/src/components/accordion/accordion-indicator.tsx +104 -0
- package/src/components/accordion/accordion-item.tsx +78 -0
- package/src/components/accordion/accordion-title.tsx +18 -0
- package/src/components/accordion/accordion-trigger.tsx +148 -0
- package/src/components/accordion/accordion.context.tsx +153 -0
- package/src/components/accordion/accordion.tsx +253 -0
- package/src/components/accordion/accordion.types.ts +11 -0
- package/src/components/accordion/accordion.variants.test.ts +435 -0
- package/src/components/accordion/accordion.variants.ts +358 -0
- package/src/components/accordion/index.ts +45 -0
- package/src/components/badge/AGENTS.md +83 -0
- package/src/components/badge/badge-close-button.tsx +48 -0
- package/src/components/badge/badge-end-content.tsx +15 -0
- package/src/components/badge/badge-label.tsx +24 -0
- package/src/components/badge/badge-start-content.tsx +16 -0
- package/src/components/badge/badge.context.tsx +64 -0
- package/src/components/badge/badge.tsx +192 -0
- package/src/components/badge/badge.types.ts +10 -0
- package/src/components/badge/badge.variants.test.ts +269 -0
- package/src/components/badge/badge.variants.ts +238 -0
- package/src/components/badge/index.ts +17 -0
- package/src/components/bottom-sheet/AGENTS.md +232 -0
- package/src/components/bottom-sheet/bottom-sheet-background.tsx +35 -0
- package/src/components/bottom-sheet/bottom-sheet-close.tsx +60 -0
- package/src/components/bottom-sheet/bottom-sheet-container.tsx +245 -0
- package/src/components/bottom-sheet/bottom-sheet-content.tsx +66 -0
- package/src/components/bottom-sheet/bottom-sheet-description.tsx +29 -0
- package/src/components/bottom-sheet/bottom-sheet-footer.tsx +148 -0
- package/src/components/bottom-sheet/bottom-sheet-handle.tsx +47 -0
- package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +180 -0
- package/src/components/bottom-sheet/bottom-sheet-portal.tsx +72 -0
- package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +95 -0
- package/src/components/bottom-sheet/bottom-sheet-title.tsx +24 -0
- package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +73 -0
- package/src/components/bottom-sheet/bottom-sheet.context.tsx +135 -0
- package/src/components/bottom-sheet/bottom-sheet.tsx +125 -0
- package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +276 -0
- package/src/components/bottom-sheet/bottom-sheet.variants.ts +199 -0
- package/src/components/bottom-sheet/index.ts +40 -0
- package/src/components/bottom-sheet/use-bottom-sheet-input.ts +125 -0
- package/src/components/button/AGENTS.md +217 -0
- package/src/components/button/button-end-content.tsx +15 -0
- package/src/components/button/button-group-separator.tsx +37 -0
- package/src/components/button/button-group-text.tsx +101 -0
- package/src/components/button/button-group.tsx +158 -0
- package/src/components/button/button-label.tsx +24 -0
- package/src/components/button/button-start-content.tsx +15 -0
- package/src/components/button/button.context.tsx +187 -0
- package/src/components/button/button.tsx +273 -0
- package/src/components/button/button.types.ts +10 -0
- package/src/components/button/button.variants.test.ts +811 -0
- package/src/components/button/button.variants.ts +480 -0
- package/src/components/button/index.ts +50 -0
- package/src/components/chart/AGENTS.md +210 -0
- package/src/components/chart/chart-area.tsx +93 -0
- package/src/components/chart/chart-bar.tsx +103 -0
- package/src/components/chart/chart-bars.tsx +75 -0
- package/src/components/chart/chart-candlestick.tsx +60 -0
- package/src/components/chart/chart-grid.tsx +33 -0
- package/src/components/chart/chart-legend.tsx +41 -0
- package/src/components/chart/chart-line.tsx +50 -0
- package/src/components/chart/chart-scatter.tsx +35 -0
- package/src/components/chart/chart-tooltip-dot.tsx +70 -0
- package/src/components/chart/chart-tooltip-x.tsx +121 -0
- package/src/components/chart/chart-tooltip-y.tsx +55 -0
- package/src/components/chart/chart-tooltip.tsx +133 -0
- package/src/components/chart/chart-x-axis.tsx +27 -0
- package/src/components/chart/chart-y-axis.tsx +21 -0
- package/src/components/chart/chart.context.tsx +127 -0
- package/src/components/chart/chart.tsx +448 -0
- package/src/components/chart/chart.types.ts +108 -0
- package/src/components/chart/chart.variants.test.ts +893 -0
- package/src/components/chart/chart.variants.ts +726 -0
- package/src/components/chart/index.ts +103 -0
- package/src/components/chart/pie-chart-center.tsx +40 -0
- package/src/components/chart/pie-chart-label.tsx +56 -0
- package/src/components/chart/pie-chart-slice.tsx +45 -0
- package/src/components/chart/pie-chart-tooltip.tsx +85 -0
- package/src/components/chart/pie-chart.context.tsx +77 -0
- package/src/components/chart/pie-chart.tsx +247 -0
- package/src/components/chart/use-chart-font.ts +25 -0
- package/src/components/chart/use-chart-palette.ts +38 -0
- package/src/components/checkbox/AGENTS.md +185 -0
- package/src/components/checkbox/checkbox-box.tsx +152 -0
- package/src/components/checkbox/checkbox-group.tsx +107 -0
- package/src/components/checkbox/checkbox-label.tsx +34 -0
- package/src/components/checkbox/checkbox.context.tsx +136 -0
- package/src/components/checkbox/checkbox.tsx +258 -0
- package/src/components/checkbox/checkbox.types.ts +14 -0
- package/src/components/checkbox/checkbox.variants.test.ts +634 -0
- package/src/components/checkbox/checkbox.variants.ts +484 -0
- package/src/components/checkbox/index.ts +42 -0
- package/src/components/field/AGENTS.md +100 -0
- package/src/components/field/field-content.tsx +24 -0
- package/src/components/field/field-description.tsx +29 -0
- package/src/components/field/field-error.tsx +61 -0
- package/src/components/field/field-group.tsx +22 -0
- package/src/components/field/field-label.tsx +33 -0
- package/src/components/field/field-legend.tsx +34 -0
- package/src/components/field/field-separator.tsx +81 -0
- package/src/components/field/field-set.tsx +22 -0
- package/src/components/field/field.context.tsx +78 -0
- package/src/components/field/field.tsx +144 -0
- package/src/components/field/field.types.ts +15 -0
- package/src/components/field/field.variants.test.ts +216 -0
- package/src/components/field/field.variants.ts +136 -0
- package/src/components/field/index.ts +21 -0
- package/src/components/icon/AGENTS.md +104 -0
- package/src/components/icon/icon.context.tsx +29 -0
- package/src/components/icon/icon.tsx +92 -0
- package/src/components/icon/icon.variants.test.ts +111 -0
- package/src/components/icon/icon.variants.ts +79 -0
- package/src/components/icon/index.ts +11 -0
- package/src/components/input/AGENTS.md +126 -0
- package/src/components/input/index.ts +27 -0
- package/src/components/input/input-group-decorator.tsx +84 -0
- package/src/components/input/input-group-prefix.tsx +16 -0
- package/src/components/input/input-group-suffix.tsx +15 -0
- package/src/components/input/input-group.tsx +140 -0
- package/src/components/input/input.context.tsx +87 -0
- package/src/components/input/input.tsx +184 -0
- package/src/components/input/input.types.ts +10 -0
- package/src/components/input/input.variants.test.ts +369 -0
- package/src/components/input/input.variants.ts +329 -0
- package/src/components/list-group/AGENTS.md +67 -0
- package/src/components/list-group/index.ts +20 -0
- package/src/components/list-group/list-group-item-content.tsx +10 -0
- package/src/components/list-group/list-group-item-description.tsx +12 -0
- package/src/components/list-group/list-group-item-prefix.tsx +30 -0
- package/src/components/list-group/list-group-item-suffix.tsx +55 -0
- package/src/components/list-group/list-group-item-title.tsx +17 -0
- package/src/components/list-group/list-group-item.tsx +87 -0
- package/src/components/list-group/list-group.context.tsx +65 -0
- package/src/components/list-group/list-group.tsx +116 -0
- package/src/components/list-group/list-group.types.ts +14 -0
- package/src/components/list-group/list-group.variants.test.ts +223 -0
- package/src/components/list-group/list-group.variants.ts +107 -0
- package/src/components/pressable/AGENTS.md +64 -0
- package/src/components/pressable/index.ts +9 -0
- package/src/components/pressable/pressable.tsx +256 -0
- package/src/components/pressable/pressable.variants.test.ts +99 -0
- package/src/components/pressable/pressable.variants.ts +63 -0
- package/src/components/provider/AGENTS.md +90 -0
- package/src/components/provider/index.ts +1 -0
- package/src/components/provider/provider.tsx +105 -0
- package/src/components/radio/AGENTS.md +247 -0
- package/src/components/radio/index.ts +35 -0
- package/src/components/radio/radio-group.tsx +126 -0
- package/src/components/radio/radio-indicator.tsx +91 -0
- package/src/components/radio/radio-label.tsx +39 -0
- package/src/components/radio/radio.context.tsx +132 -0
- package/src/components/radio/radio.tsx +215 -0
- package/src/components/radio/radio.variants.test.ts +581 -0
- package/src/components/radio/radio.variants.ts +272 -0
- package/src/components/screen/AGENTS.md +287 -0
- package/src/components/screen/index.ts +85 -0
- package/src/components/screen/screen-chat-list.tsx +485 -0
- package/src/components/screen/screen-content.tsx +69 -0
- package/src/components/screen/screen-debug.ts +43 -0
- package/src/components/screen/screen-error.tsx +61 -0
- package/src/components/screen/screen-flat-list.tsx +88 -0
- package/src/components/screen/screen-footer-background.tsx +60 -0
- package/src/components/screen/screen-footer.tsx +170 -0
- package/src/components/screen/screen-header.tsx +36 -0
- package/src/components/screen/screen-legend-list.tsx +105 -0
- package/src/components/screen/screen-list-component.tsx +23 -0
- package/src/components/screen/screen-loading.tsx +50 -0
- package/src/components/screen/screen-navbar-back-button.tsx +79 -0
- package/src/components/screen/screen-navbar-background.tsx +45 -0
- package/src/components/screen/screen-navbar-subtitle.tsx +26 -0
- package/src/components/screen/screen-navbar-title.tsx +31 -0
- package/src/components/screen/screen-navbar.tsx +154 -0
- package/src/components/screen/screen-root.tsx +38 -0
- package/src/components/screen/screen-scroll-area.tsx +116 -0
- package/src/components/screen/screen-scroll-shadow.tsx +186 -0
- package/src/components/screen/screen-section-list.tsx +81 -0
- package/src/components/screen/screen-view.tsx +59 -0
- package/src/components/screen/screen.context.tsx +167 -0
- package/src/components/screen/screen.tsx +114 -0
- package/src/components/screen/screen.types.ts +66 -0
- package/src/components/screen/screen.variants.test.ts +602 -0
- package/src/components/screen/screen.variants.ts +475 -0
- package/src/components/screen/use-screen-scroll-insets.ts +218 -0
- package/src/components/separator/AGENTS.md +30 -0
- package/src/components/separator/index.ts +8 -0
- package/src/components/separator/separator.tsx +85 -0
- package/src/components/slider/AGENTS.md +274 -0
- package/src/components/slider/index.ts +48 -0
- package/src/components/slider/slider-fill.tsx +68 -0
- package/src/components/slider/slider-output.tsx +55 -0
- package/src/components/slider/slider-thumb.tsx +161 -0
- package/src/components/slider/slider-track.tsx +233 -0
- package/src/components/slider/slider.context.tsx +118 -0
- package/src/components/slider/slider.tsx +321 -0
- package/src/components/slider/slider.types.ts +26 -0
- package/src/components/slider/slider.variants.test.ts +804 -0
- package/src/components/slider/slider.variants.ts +607 -0
- package/src/components/spinner/AGENTS.md +73 -0
- package/src/components/spinner/index.ts +17 -0
- package/src/components/spinner/spinner-arc.tsx +90 -0
- package/src/components/spinner/spinner-content.tsx +63 -0
- package/src/components/spinner/spinner.context.tsx +52 -0
- package/src/components/spinner/spinner.tsx +128 -0
- package/src/components/spinner/spinner.variants.test.ts +273 -0
- package/src/components/spinner/spinner.variants.ts +187 -0
- package/src/components/switch/AGENTS.md +213 -0
- package/src/components/switch/index.ts +42 -0
- package/src/components/switch/switch-content.tsx +111 -0
- package/src/components/switch/switch-end-content.tsx +18 -0
- package/src/components/switch/switch-start-content.tsx +20 -0
- package/src/components/switch/switch-thumb.tsx +102 -0
- package/src/components/switch/switch.context.tsx +79 -0
- package/src/components/switch/switch.tsx +423 -0
- package/src/components/switch/switch.types.ts +14 -0
- package/src/components/switch/switch.variants.test.ts +570 -0
- package/src/components/switch/switch.variants.ts +511 -0
- package/src/components/tabs/AGENTS.md +286 -0
- package/src/components/tabs/index.ts +69 -0
- package/src/components/tabs/tabs-content.tsx +65 -0
- package/src/components/tabs/tabs-indicator.tsx +97 -0
- package/src/components/tabs/tabs-label.tsx +62 -0
- package/src/components/tabs/tabs-list.tsx +139 -0
- package/src/components/tabs/tabs-pager.tsx +59 -0
- package/src/components/tabs/tabs-scroll-view.tsx +171 -0
- package/src/components/tabs/tabs-separator.tsx +73 -0
- package/src/components/tabs/tabs-trigger.tsx +210 -0
- package/src/components/tabs/tabs.context.tsx +294 -0
- package/src/components/tabs/tabs.tsx +435 -0
- package/src/components/tabs/tabs.types.ts +13 -0
- package/src/components/tabs/tabs.variants.test.ts +1020 -0
- package/src/components/tabs/tabs.variants.ts +672 -0
- package/src/components/text/AGENTS.md +95 -0
- package/src/components/text/index.ts +25 -0
- package/src/components/text/text.context.tsx +60 -0
- package/src/components/text/text.tsx +251 -0
- package/src/components/text/text.variants.test.ts +422 -0
- package/src/components/text/text.variants.ts +282 -0
- package/src/display-name.test.ts +145 -0
- package/src/docs.test.ts +98 -0
- package/src/expo/navigation-theme.tsx +58 -0
- package/src/hooks/use-controllable-state.ts +45 -0
- package/src/hooks/use-keyboard-state-sync.tsx +147 -0
- package/src/hooks/use-navigation-theme.ts +78 -0
- package/src/hooks/use-theme-color.ts +44 -0
- package/src/icons/central.ts +1 -0
- package/src/lib/cn.test.ts +136 -0
- package/src/lib/cn.ts +29 -0
- package/src/lib/color.test.ts +80 -0
- package/src/lib/color.ts +79 -0
- package/src/lib/compose-refs.test.ts +64 -0
- package/src/lib/compose-refs.ts +37 -0
- package/src/lib/keyboard-animation.test.ts +33 -0
- package/src/lib/keyboard-animation.ts +28 -0
- package/src/lib/merge-props.test.ts +82 -0
- package/src/lib/merge-props.ts +47 -0
- package/src/lib/navigation-theme.test.ts +49 -0
- package/src/lib/navigation-theme.ts +53 -0
- package/src/lib/slot.tsx +45 -0
- package/src/lib/tv.ts +20 -0
- package/src/styles/base.css +2 -0
- package/src/styles/geometry.test.ts +117 -0
- package/src/styles/index.css +3 -0
- package/src/styles/theme-tokens.test.ts +252 -0
- package/src/styles/theme.css +447 -0
- package/src/styles/tokens.css +132 -0
- package/src/styles/tokens.test.ts +181 -0
- package/src/styles/tokens.ts +86 -0
- package/src/uniwind-env.d.ts +1 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { Children, type ReactElement, type ReactNode, useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { useControllableState } from "../../hooks/use-controllable-state";
|
|
3
|
+
import { useFieldContext } from "../field/field.context";
|
|
4
|
+
import { Pressable, type PressableProps } from "../pressable";
|
|
5
|
+
import { type CheckboxContextValue, CheckboxProvider, useCheckboxGroupContext } from "./checkbox.context";
|
|
6
|
+
import type { CheckboxLabelProps } from "./checkbox.types";
|
|
7
|
+
import {
|
|
8
|
+
type CheckboxAlignment,
|
|
9
|
+
type CheckboxColor,
|
|
10
|
+
type CheckboxSize,
|
|
11
|
+
checkboxVariants,
|
|
12
|
+
resolveCheckboxAxes,
|
|
13
|
+
resolveCheckboxHitSlop,
|
|
14
|
+
} from "./checkbox.variants";
|
|
15
|
+
import { CheckboxBox } from "./checkbox-box";
|
|
16
|
+
import { CheckboxGroup } from "./checkbox-group";
|
|
17
|
+
import { CheckboxLabel } from "./checkbox-label";
|
|
18
|
+
|
|
19
|
+
export type CheckboxProps = Omit<PressableProps, "asChild" | "busy" | "disabled" | "onPress"> & {
|
|
20
|
+
/**
|
|
21
|
+
* Identifies this box to an enclosing `Checkbox.Group`. Required inside one,
|
|
22
|
+
* and ignored outside — a lone checkbox holds its own state.
|
|
23
|
+
*/
|
|
24
|
+
value?: string;
|
|
25
|
+
/** Controlled checked state. Ignored inside a `Checkbox.Group`, which owns it. */
|
|
26
|
+
isChecked?: boolean;
|
|
27
|
+
/** Starting state while uncontrolled. */
|
|
28
|
+
defaultChecked?: boolean;
|
|
29
|
+
onCheckedChange?: (isChecked: boolean) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Draws a dash rather than a tick and reports `mixed` to a screen reader, for
|
|
32
|
+
* a parent row over children that are only partly selected.
|
|
33
|
+
*/
|
|
34
|
+
isIndeterminate?: boolean;
|
|
35
|
+
/** What a ticked box means. Inherited from a `Checkbox.Group` when unset. */
|
|
36
|
+
color?: CheckboxColor;
|
|
37
|
+
/** Inherited from a `Checkbox.Group` when unset. */
|
|
38
|
+
size?: CheckboxSize;
|
|
39
|
+
/** `end` pushes the box to the far edge and fills the row with the label. */
|
|
40
|
+
alignment?: CheckboxAlignment;
|
|
41
|
+
/** Reports an invalid value. Inherited from a group, then from a `Field`. */
|
|
42
|
+
isInvalid?: boolean;
|
|
43
|
+
/** Blocks the press and fades the row. Inherited from a group, then a `Field`. */
|
|
44
|
+
isDisabled?: boolean;
|
|
45
|
+
children?: ReactNode;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function CheckboxRoot({
|
|
49
|
+
value,
|
|
50
|
+
isChecked,
|
|
51
|
+
defaultChecked = false,
|
|
52
|
+
onCheckedChange,
|
|
53
|
+
isIndeterminate = false,
|
|
54
|
+
color,
|
|
55
|
+
size,
|
|
56
|
+
alignment,
|
|
57
|
+
isInvalid,
|
|
58
|
+
isDisabled,
|
|
59
|
+
feedback = "fade",
|
|
60
|
+
haptic = "selection",
|
|
61
|
+
className,
|
|
62
|
+
children,
|
|
63
|
+
...props
|
|
64
|
+
}: CheckboxProps): ReactElement {
|
|
65
|
+
const group = useCheckboxGroupContext();
|
|
66
|
+
const field = useFieldContext();
|
|
67
|
+
|
|
68
|
+
const axes = resolveCheckboxAxes({
|
|
69
|
+
field: field ?? undefined,
|
|
70
|
+
group: group ?? undefined,
|
|
71
|
+
own: { alignment, color, isDisabled, isInvalid, size },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Called unconditionally so hook order never depends on group membership;
|
|
75
|
+
// which state is *read* below is a plain branch. The same shape `Input` uses
|
|
76
|
+
// to share focus with an `Input.Group`.
|
|
77
|
+
const [ownChecked, setOwnChecked] = useControllableState({
|
|
78
|
+
defaultValue: defaultChecked,
|
|
79
|
+
onChange: onCheckedChange,
|
|
80
|
+
value: isChecked,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const groupValue = group ? requireValue(value) : undefined;
|
|
84
|
+
const checked = group && groupValue !== undefined ? group.checked.includes(groupValue) : ownChecked;
|
|
85
|
+
|
|
86
|
+
const toggle = useCallback(() => {
|
|
87
|
+
if (group && groupValue !== undefined) {
|
|
88
|
+
group.toggle(groupValue);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
setOwnChecked(!checked);
|
|
92
|
+
}, [checked, group, groupValue, setOwnChecked]);
|
|
93
|
+
|
|
94
|
+
// Offered to an enclosing `Field` so tapping the label or the description
|
|
95
|
+
// beside the box ticks it. A checkbox in a form is a small square next to a
|
|
96
|
+
// sentence, and the sentence is what people aim at.
|
|
97
|
+
//
|
|
98
|
+
// Registered through a ref-backed trampoline rather than `toggle` itself:
|
|
99
|
+
// `toggle` is a new function every time `checked` changes, and re-registering
|
|
100
|
+
// on each tick would re-render the whole field for nothing.
|
|
101
|
+
const toggleRef = useRef(toggle);
|
|
102
|
+
toggleRef.current = toggle;
|
|
103
|
+
const registerPress = field?.registerPress;
|
|
104
|
+
const stableToggle = useCallback(() => toggleRef.current(), []);
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (!registerPress) return;
|
|
108
|
+
registerPress(stableToggle);
|
|
109
|
+
return () => registerPress(null);
|
|
110
|
+
}, [registerPress, stableToggle]);
|
|
111
|
+
|
|
112
|
+
const context = useMemo<CheckboxContextValue>(
|
|
113
|
+
() => ({
|
|
114
|
+
alignment: axes.alignment,
|
|
115
|
+
color: axes.color,
|
|
116
|
+
isChecked: checked,
|
|
117
|
+
isDisabled: axes.isDisabled,
|
|
118
|
+
isIndeterminate,
|
|
119
|
+
isInvalid: axes.isInvalid,
|
|
120
|
+
size: axes.size,
|
|
121
|
+
}),
|
|
122
|
+
[axes.alignment, axes.color, axes.isDisabled, axes.isInvalid, axes.size, checked, isIndeterminate]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const content = useMemo(() => wrapTextChildren(children), [children]);
|
|
126
|
+
// The box draws itself, including everything the fill state changes, so the
|
|
127
|
+
// root is left with the row and nothing else.
|
|
128
|
+
const rootClassName = checkboxVariants(axes).root({ className });
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<CheckboxProvider value={context}>
|
|
132
|
+
<Pressable
|
|
133
|
+
accessibilityRole="checkbox"
|
|
134
|
+
accessibilityState={{ checked: isIndeterminate ? "mixed" : checked }}
|
|
135
|
+
className={rootClassName}
|
|
136
|
+
disabled={axes.isDisabled}
|
|
137
|
+
feedback={feedback}
|
|
138
|
+
haptic={haptic}
|
|
139
|
+
hitSlop={resolveCheckboxHitSlop({ hasLabel: Children.count(children) > 0, size: axes.size })}
|
|
140
|
+
onPress={toggle}
|
|
141
|
+
{...props}
|
|
142
|
+
>
|
|
143
|
+
<CheckboxBox />
|
|
144
|
+
{content}
|
|
145
|
+
</Pressable>
|
|
146
|
+
</CheckboxProvider>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The `value` a grouped checkbox is identified by.
|
|
152
|
+
*
|
|
153
|
+
* A checkbox inside a group with nothing to be called cannot be checked, and the
|
|
154
|
+
* group would silently hold a list that never mentions it. Group membership is
|
|
155
|
+
* not visible in the child's props at compile time, so this cannot be a type
|
|
156
|
+
* error — it throws by name instead, the way `useCheckboxPart` does.
|
|
157
|
+
*/
|
|
158
|
+
function requireValue(value: string | undefined): string {
|
|
159
|
+
if (value === undefined) {
|
|
160
|
+
throw new Error("A <Checkbox> inside a <Checkbox.Group> needs a `value` for the group to track it by.");
|
|
161
|
+
}
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Wraps bare text children in a `Checkbox.Label`.
|
|
167
|
+
*
|
|
168
|
+
* React Native cannot render a string outside a `<Text>`, so
|
|
169
|
+
* `<Checkbox>Remember me</Checkbox>` would otherwise crash. Consecutive strings
|
|
170
|
+
* and numbers are collected into a single label rather than one each — the row's
|
|
171
|
+
* gap would otherwise space the parts of one sentence apart, the same rule and
|
|
172
|
+
* the same reason as `Badge`.
|
|
173
|
+
*/
|
|
174
|
+
function wrapTextChildren(children: ReactNode): ReactNode {
|
|
175
|
+
const items = Children.toArray(children);
|
|
176
|
+
const output: ReactNode[] = [];
|
|
177
|
+
let run: (string | number)[] = [];
|
|
178
|
+
|
|
179
|
+
const flushRun = () => {
|
|
180
|
+
if (run.length === 0) return;
|
|
181
|
+
const props: CheckboxLabelProps = { children: run.join("") };
|
|
182
|
+
output.push(<CheckboxLabel key={`label-${output.length}`} {...props} />);
|
|
183
|
+
run = [];
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
for (const child of items) {
|
|
187
|
+
if (typeof child === "string" || typeof child === "number") {
|
|
188
|
+
run.push(child);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
flushRun();
|
|
192
|
+
output.push(child);
|
|
193
|
+
}
|
|
194
|
+
flushRun();
|
|
195
|
+
|
|
196
|
+
return output;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* A box that is ticked or not — on its own, or as one of a group that shares a
|
|
201
|
+
* value list.
|
|
202
|
+
*
|
|
203
|
+
* The root draws the box itself, so `<Checkbox />` is already a complete
|
|
204
|
+
* control. Anything composed inside it lands beside the box and shares its tap
|
|
205
|
+
* target: `<Checkbox.Label>` is the usual thing, and a bare string is wrapped in
|
|
206
|
+
* one automatically.
|
|
207
|
+
*
|
|
208
|
+
* **The whole `Pressable` surface comes through** — `feedback`, `haptic`,
|
|
209
|
+
* `pressedScale`, `onLongPress` and the rest — because the root *is* a
|
|
210
|
+
* `Pressable`. Two defaults differ from a bare one and only two: `fade`, since a
|
|
211
|
+
* spring on a 20pt square reads as a jitter, and a `selection` haptic, since a
|
|
212
|
+
* checkbox is a state toggle and the tick landing is the confirmation. Both are
|
|
213
|
+
* ordinary props, so `haptic={false}` silences it. `onPress` is the one handler
|
|
214
|
+
* that cannot be forwarded — the press *is* the toggle, and `onCheckedChange` is
|
|
215
|
+
* where a side effect goes.
|
|
216
|
+
*
|
|
217
|
+
* State works either way from one hook: pass `isChecked` to control it, or
|
|
218
|
+
* nothing and let it hold its own. Inside a `Checkbox.Group` the group owns it
|
|
219
|
+
* and the box needs a `value` to be tracked by.
|
|
220
|
+
*
|
|
221
|
+
* `isInvalid` and `isDisabled` cascade in from a `Checkbox.Group` and then from
|
|
222
|
+
* a `Field`, so `<Field isInvalid>` reddens the box with nothing said here — and
|
|
223
|
+
* an explicit prop still wins, in either direction.
|
|
224
|
+
*
|
|
225
|
+
* Inside a `Field` the box also hands its toggle back up, so the whole row is
|
|
226
|
+
* the target: tapping a `Field.Label` or the `Field.Description` under it ticks
|
|
227
|
+
* the box. That is what lets a checkbox in a form be a bare `<Checkbox />` with
|
|
228
|
+
* the field naming it, rather than a `Checkbox.Label` repeating the name.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* <Checkbox isChecked={agreed} onCheckedChange={setAgreed}>
|
|
232
|
+
* <Checkbox.Label>I accept the terms</Checkbox.Label>
|
|
233
|
+
* </Checkbox>
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* <Checkbox.Group checked={channels} onChecked={setChannels}>
|
|
237
|
+
* <Checkbox value="email">Email</Checkbox>
|
|
238
|
+
* <Checkbox value="sms">SMS</Checkbox>
|
|
239
|
+
* </Checkbox.Group>
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* <Checkbox
|
|
243
|
+
* alignment="end"
|
|
244
|
+
* color="success"
|
|
245
|
+
* isIndeterminate={some && !all}
|
|
246
|
+
* isChecked={all}
|
|
247
|
+
* onCheckedChange={toggleAll}
|
|
248
|
+
* >
|
|
249
|
+
* <Checkbox.Label>Select all</Checkbox.Label>
|
|
250
|
+
* </Checkbox>
|
|
251
|
+
*/
|
|
252
|
+
export const Checkbox = Object.assign(CheckboxRoot, {
|
|
253
|
+
/** The box's text, inside its tap target — tapping the words toggles it. */
|
|
254
|
+
Label: CheckboxLabel,
|
|
255
|
+
/** Owns the checked list for the checkboxes inside it, and their shared axes. */
|
|
256
|
+
Group: CheckboxGroup,
|
|
257
|
+
displayName: "DelacourUI.Checkbox",
|
|
258
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TextPresetProps } from "../text";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The shape of the checkbox's text part.
|
|
5
|
+
*
|
|
6
|
+
* Lives in a leaf rather than in `checkbox-label.tsx` because the root imports
|
|
7
|
+
* it too — it wraps bare string children in a `Checkbox.Label`, and reaching for
|
|
8
|
+
* the type through the part file would be reaching past what it is for.
|
|
9
|
+
*
|
|
10
|
+
* `variant` is already omitted by `TextPresetProps`: the part *is* one, and
|
|
11
|
+
* naming a second would let a caller turn a checkbox's label into a display
|
|
12
|
+
* heading and lose the scale the component exists to keep.
|
|
13
|
+
*/
|
|
14
|
+
export type CheckboxLabelProps = TextPresetProps;
|