@delacour/react-native-ui 0.1.0-alpha.20261009011521 → 0.1.0-alpha.20261009012154
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/package.json +5 -3
- package/src/components/progress-button/AGENTS.md +116 -0
- package/src/components/progress-button/index.ts +36 -0
- package/src/components/progress-button/progress-button-done.tsx +33 -0
- package/src/components/progress-button/progress-button-label.tsx +27 -0
- package/src/components/progress-button/progress-button.context.tsx +93 -0
- package/src/components/progress-button/progress-button.tsx +414 -0
- package/src/components/progress-button/progress-button.variants.test.ts +314 -0
- package/src/components/progress-button/progress-button.variants.ts +236 -0
- package/src/components/selection-mode/AGENTS.md +155 -0
- package/src/components/selection-mode/index.ts +43 -0
- package/src/components/selection-mode/selection-mode-action.tsx +65 -0
- package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
- package/src/components/selection-mode/selection-mode-group.tsx +123 -0
- package/src/components/selection-mode/selection-mode-header.tsx +85 -0
- package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
- package/src/components/selection-mode/selection-mode-item.tsx +182 -0
- package/src/components/selection-mode/selection-mode.context.tsx +89 -0
- package/src/components/selection-mode/selection-mode.tsx +207 -0
- package/src/components/selection-mode/selection-mode.types.ts +36 -0
- package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
- package/src/components/selection-mode/selection-mode.variants.ts +329 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delacour/react-native-ui",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.20261009012154",
|
|
4
4
|
"description": "React Native UI components — Uniwind, Reanimated, Gesture Handler",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,10 +64,12 @@
|
|
|
64
64
|
"./popover": "./src/components/popover/index.ts",
|
|
65
65
|
"./pressable": "./src/components/pressable/index.ts",
|
|
66
66
|
"./progress": "./src/components/progress/index.ts",
|
|
67
|
+
"./progress-button": "./src/components/progress-button/index.ts",
|
|
67
68
|
"./provider": "./src/components/provider/index.ts",
|
|
68
69
|
"./radio": "./src/components/radio/index.ts",
|
|
69
70
|
"./rating": "./src/components/rating/index.ts",
|
|
70
71
|
"./screen": "./src/components/screen/index.ts",
|
|
72
|
+
"./selection-mode": "./src/components/selection-mode/index.ts",
|
|
71
73
|
"./separator": "./src/components/separator/index.ts",
|
|
72
74
|
"./skeleton": "./src/components/skeleton/index.ts",
|
|
73
75
|
"./slide-button": "./src/components/slide-button/index.ts",
|
|
@@ -114,8 +116,8 @@
|
|
|
114
116
|
},
|
|
115
117
|
"peerDependencies": {
|
|
116
118
|
"@central-icons-react-native/round-outlined-radius-1-stroke-1.5": "^1.1",
|
|
117
|
-
"@delacour/react-native-bottom-sheet": "0.1.0-alpha.
|
|
118
|
-
"@delacour/react-native-charts": "0.1.0-alpha.
|
|
119
|
+
"@delacour/react-native-bottom-sheet": "0.1.0-alpha.20261009012154",
|
|
120
|
+
"@delacour/react-native-charts": "0.1.0-alpha.20261009012154",
|
|
119
121
|
"@legendapp/list": ">=3.3",
|
|
120
122
|
"expo-linear-gradient": ">=15",
|
|
121
123
|
"expo-router": ">=57",
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# ProgressButton
|
|
2
|
+
|
|
3
|
+
A button that has to be **held**, not tapped. A fill grows from the leading edge for
|
|
4
|
+
`holdDuration`, and the action fires when it reaches the end. Compound root plus
|
|
5
|
+
`ProgressButton.Label` and `ProgressButton.Done`. For an irreversible action that would
|
|
6
|
+
otherwise need a confirmation dialog — erasing, paying. Two taps in a row are a rhythm a hand
|
|
7
|
+
falls into; a two-second hold cannot be done by accident or by habit.
|
|
8
|
+
|
|
9
|
+
`import { ProgressButton } from "@delacour/react-native-ui/progress-button";`
|
|
10
|
+
|
|
11
|
+
## Files
|
|
12
|
+
|
|
13
|
+
| File | What it holds |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `index.ts` | → `@delacour/react-native-ui/progress-button` |
|
|
16
|
+
| `progress-button.tsx` | Root + the `Object.assign` compound surface: the gesture, the fill, the two label layers, the done mark |
|
|
17
|
+
| `progress-button-label.tsx` | `ProgressButton.Label`, coloured by the layer it renders in |
|
|
18
|
+
| `progress-button-done.tsx` | `ProgressButton.Done`, the default tick or the caller's content |
|
|
19
|
+
| `progress-button.context.tsx` | `ProgressButtonContext`, the layer context, `useProgressButton()` |
|
|
20
|
+
| `progress-button.variants.ts` | Pure `tv()` slots, colour tables, and the hold, playback, stepping and reset resolvers |
|
|
21
|
+
| `progress-button.variants.test.ts` | |
|
|
22
|
+
|
|
23
|
+
## Design
|
|
24
|
+
|
|
25
|
+
- **Variants**: `primary`, `secondary`, `destructive`, `success`. **Sizes**: `sm`, `md`, `lg`.
|
|
26
|
+
**Shapes**: `pill` (default), `rounded`. There is no square `icon-*` size: a hold has to
|
|
27
|
+
say what it does, and a glyph alone cannot carry "hold to erase".
|
|
28
|
+
- **The box is the button's box.** `h-button-*`, the button's `px-*`, `text-button-*`,
|
|
29
|
+
`size-icon-*` and, as a pill, `rounded-button-*` — the tokens `buttonVariants` reads, and a
|
|
30
|
+
test pins them equal to the button's own, so the two sit level in a row and a retune moves
|
|
31
|
+
both. `rounded` is the card corner, `rounded-lg`, so the button sits square with a
|
|
32
|
+
`Button` given the same class beside it. No token is minted.
|
|
33
|
+
- **Every variant rests on `bg-secondary` and carries its colour in the label and the fill.**
|
|
34
|
+
A resting button that is already solid red has nothing left to say when the fill arrives.
|
|
35
|
+
`secondary` fills with `foreground` and draws its inner label on `background`, because a
|
|
36
|
+
`secondary` fill on a `secondary` surface would not be seen and `secondary-foreground` on a
|
|
37
|
+
`foreground` fill is the same colour twice. The three colour tables are exported and a test
|
|
38
|
+
checks every token in them exists in both themes.
|
|
39
|
+
- **The label is drawn twice.** Once on the surface in the variant colour, once inside the
|
|
40
|
+
clipped fill in the variant's foreground, laid out at the button's **measured** width
|
|
41
|
+
(`onLayout`), so both copies wrap identically and the wipe's edge cuts through a glyph. A
|
|
42
|
+
single label under a translucent wash goes muddy exactly at the boundary, which is where
|
|
43
|
+
the eye is. A test asserts the two label slots differ in colour and nothing else.
|
|
44
|
+
`ProgressButton.Label` reads which copy it is from a layer context rather than a prop the
|
|
45
|
+
caller would have to repeat; a composed `Icon` takes the layer's colour through
|
|
46
|
+
`IconDefaultsProvider`, the same mechanism `Button` uses.
|
|
47
|
+
- **The fill's copy and the done mark are hidden from assistive technology.** The root is the
|
|
48
|
+
one accessible element; announcing the label twice would be the fill's bookkeeping leaking
|
|
49
|
+
into a screen reader.
|
|
50
|
+
- **Bare strings are wrapped in a `ProgressButton.Label`**, consecutive ones collapsing into
|
|
51
|
+
one, for the reason `Button` gives: React Native crashes on text outside a `<Text>`.
|
|
52
|
+
- **`ProgressButton.Done` is lifted out of the children by type** and drawn over the full fill.
|
|
53
|
+
With none written the root draws one, which is a full-size `IconCheckmark2` at `size-icon-*` (the `Small` glyph, alone on a button, read as a speck) in
|
|
54
|
+
the fill's foreground. A caller's `Done` replaces it whole.
|
|
55
|
+
|
|
56
|
+
## Behaviour
|
|
57
|
+
|
|
58
|
+
- **The gesture is a pan with no minimum distance**, `onBegin` to `onFinalize`. A long-press
|
|
59
|
+
gesture fires once and cannot tell "still holding" from "held long enough".
|
|
60
|
+
`shouldCancelWhenOutside(false)`, and a finger may drift 16pt
|
|
61
|
+
(`PROGRESS_BUTTON_MAX_DRIFT`) before the hold lets go, because a hand resting on a control for
|
|
62
|
+
two seconds moves.
|
|
63
|
+
- **Pressing runs the fill toward 1 over what is left** — `holdDuration × (1 − p)` — so a second
|
|
64
|
+
attempt resumes rather than restarting. **Releasing runs it back over what is filled** —
|
|
65
|
+
`holdDuration × p` — the same rate, played backwards, never a snap to empty.
|
|
66
|
+
`resolveRemainingDuration` is the tested copy; the worklets restate it inline because a
|
|
67
|
+
worklet body must be self-contained.
|
|
68
|
+
- **Completion is read from the animation**, in the timing's finish callback (`finished` at 1),
|
|
69
|
+
then crosses to JS with `scheduleOnRN`. Never a JS `setTimeout` running alongside: two clocks
|
|
70
|
+
disagree under load, and the one the user can see is the fill. There is no tolerance near the
|
|
71
|
+
end — released at 99%, nothing fires.
|
|
72
|
+
- **Completed, it is locked.** A shared `isLocked` flag makes the gesture ignore further presses
|
|
73
|
+
and stops a release from draining a finished fill, until a reset: `isAutoReset` after
|
|
74
|
+
`autoResetDelay` (a timer is fine here — it schedules the reset, not the completion), or a
|
|
75
|
+
controlled `isCompleted` going `false`. **A reset travels** back over
|
|
76
|
+
`PROGRESS_BUTTON_TRAVEL_MS` scaled by how full the fill is; it is never set to 0. A completion
|
|
77
|
+
set from outside travels forward the same way.
|
|
78
|
+
- **Controlled, the caller has to accept a hold in the same update.** A completed hold calls
|
|
79
|
+
`onCompletedChange(true)` and `onComplete`. If `isCompleted` is still `false` once those have
|
|
80
|
+
run, the fill travels back — the caller declined. The alternative, sitting full and locked
|
|
81
|
+
until the prop changes, leaves a caller who declines with no prop change to make, so the
|
|
82
|
+
button would be stuck. A caller with async work accepts at once (`isCompleted` true) and shows
|
|
83
|
+
progress in a custom `Done`, then sets `false` to rewind on failure; the `controlled` demo does
|
|
84
|
+
exactly that.
|
|
85
|
+
- **Haptics are off by default.** `haptic` plays when the hold takes, and a `success` knock
|
|
86
|
+
follows on completion, both from the UI thread through `playHaptic`.
|
|
87
|
+
- **`isDisabled`** fades the button (`opacity-50`) and turns the gesture off, so the fill never
|
|
88
|
+
starts. A hold in progress when it flips is cancelled and plays back.
|
|
89
|
+
|
|
90
|
+
## Reduced motion
|
|
91
|
+
|
|
92
|
+
- **The fill steps in fifths instead of sweeping.** `Math.floor(p × 5) / 5`, applied in the
|
|
93
|
+
animated style, rounding **down** so it never shows more than the hold has earned. It stays an
|
|
94
|
+
indicator, because a control that asks you to wait and shows nothing is broken.
|
|
95
|
+
`resolveSteppedProgress` is the tested copy.
|
|
96
|
+
- **Every timing opts out of the reduce-motion policy** (`ReduceMotion.Never`). Under the default
|
|
97
|
+
policy Reanimated completes a timing instantly when the setting is on — so a two-second hold
|
|
98
|
+
would complete on touch-down, which is the one thing this component exists to prevent. The
|
|
99
|
+
stepping is what reduces the motion; the clock underneath has to keep real time.
|
|
100
|
+
- **The done mark fades without scaling** under reduced motion.
|
|
101
|
+
|
|
102
|
+
## Accessibility
|
|
103
|
+
|
|
104
|
+
- `accessibilityRole="button"`, `accessibilityHint` (default: "Press and hold to confirm"), and
|
|
105
|
+
`accessibilityState={{ disabled, checked: isCompleted }}`.
|
|
106
|
+
- **Decision: activate completes.** The root declares an `activate` accessibility action, and a
|
|
107
|
+
screen reader's activate completes the button outright. A screen reader cannot hold, the hint
|
|
108
|
+
has already announced what the button does, and refusing would leave the action unreachable
|
|
109
|
+
for anyone using one. The slide-to-confirm control makes the same call.
|
|
110
|
+
|
|
111
|
+
## Out of scope
|
|
112
|
+
|
|
113
|
+
- An `icon-*` square size, and a loading state of its own — a caller shows work in a custom
|
|
114
|
+
`Done`.
|
|
115
|
+
- Progress that is not time-based (a fill driven by an upload). That is `Progress`.
|
|
116
|
+
- Cancelling a completed hold from inside the button. Reset is `isAutoReset` or the caller's.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export { ProgressButton, type ProgressButtonProps } from "./progress-button";
|
|
2
|
+
export {
|
|
3
|
+
type ProgressButtonContextValue,
|
|
4
|
+
type ProgressButtonLayer,
|
|
5
|
+
ProgressButtonProvider,
|
|
6
|
+
useProgressButton,
|
|
7
|
+
useProgressButtonContext,
|
|
8
|
+
} from "./progress-button.context";
|
|
9
|
+
export {
|
|
10
|
+
PROGRESS_BUTTON_CROSSFADE_MS,
|
|
11
|
+
PROGRESS_BUTTON_DEFAULT_AUTO_RESET_MS,
|
|
12
|
+
PROGRESS_BUTTON_DEFAULT_HINT,
|
|
13
|
+
PROGRESS_BUTTON_DEFAULT_HOLD_MS,
|
|
14
|
+
PROGRESS_BUTTON_FILL_FOREGROUND_TOKEN,
|
|
15
|
+
PROGRESS_BUTTON_FILL_TOKEN,
|
|
16
|
+
PROGRESS_BUTTON_LABEL_TOKEN,
|
|
17
|
+
PROGRESS_BUTTON_MAX_DRIFT,
|
|
18
|
+
PROGRESS_BUTTON_MIN_HOLD_MS,
|
|
19
|
+
PROGRESS_BUTTON_REDUCED_MOTION_STEPS,
|
|
20
|
+
PROGRESS_BUTTON_SHAPES,
|
|
21
|
+
PROGRESS_BUTTON_SIZES,
|
|
22
|
+
PROGRESS_BUTTON_TRAVEL_MS,
|
|
23
|
+
PROGRESS_BUTTON_VARIANTS,
|
|
24
|
+
type ProgressButtonShape,
|
|
25
|
+
type ProgressButtonSize,
|
|
26
|
+
type ProgressButtonVariant,
|
|
27
|
+
type ProgressButtonVariantProps,
|
|
28
|
+
progressButtonVariants,
|
|
29
|
+
resolveAutoResetDelay,
|
|
30
|
+
resolveHoldDuration,
|
|
31
|
+
resolveProgressButtonAccessibilityState,
|
|
32
|
+
resolveRemainingDuration,
|
|
33
|
+
resolveSteppedProgress,
|
|
34
|
+
} from "./progress-button.variants";
|
|
35
|
+
export type { ProgressButtonDoneProps } from "./progress-button-done";
|
|
36
|
+
export type { ProgressButtonLabelProps } from "./progress-button-label";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import { View, type ViewProps } from "react-native";
|
|
3
|
+
import { IconCheckmark2 } from "../../icons/central";
|
|
4
|
+
import { cn } from "../../lib/cn";
|
|
5
|
+
import { Icon } from "../icon";
|
|
6
|
+
import { useProgressButtonPart } from "./progress-button.context";
|
|
7
|
+
|
|
8
|
+
export type ProgressButtonDoneProps = ViewProps & {
|
|
9
|
+
/** Replaces the default tick. A bare `Icon` inherits the fill's foreground and the button's icon size. */
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* What the button shows once the hold completes.
|
|
16
|
+
*
|
|
17
|
+
* The root lifts this part out of its children by type and draws it over the
|
|
18
|
+
* full fill, fading and scaling it in as the labels fade out. With no children
|
|
19
|
+
* it draws a tick; the root draws one of these by itself when the caller writes
|
|
20
|
+
* none, so the tick is there by default.
|
|
21
|
+
*
|
|
22
|
+
* Size and colour come from the `IconDefaultsProvider` the root wraps it in, so
|
|
23
|
+
* a composed `Icon` matches the default tick with nothing said at the call site.
|
|
24
|
+
*/
|
|
25
|
+
export function ProgressButtonDone({ children, className, ...props }: ProgressButtonDoneProps): ReactElement {
|
|
26
|
+
useProgressButtonPart("ProgressButton.Done");
|
|
27
|
+
return (
|
|
28
|
+
<View className={cn("flex-row items-center justify-center gap-2", className)} {...props}>
|
|
29
|
+
{children ?? <Icon icon={IconCheckmark2} />}
|
|
30
|
+
</View>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
ProgressButtonDone.displayName = "DelacourUI.ProgressButton.Done";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import type { TextProps } from "react-native";
|
|
3
|
+
import { Text } from "../text";
|
|
4
|
+
import { useProgressButtonLayer, useProgressButtonPart } from "./progress-button.context";
|
|
5
|
+
import { progressButtonVariants } from "./progress-button.variants";
|
|
6
|
+
|
|
7
|
+
export type ProgressButtonLabelProps = Omit<TextProps, "children"> & {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The button's text.
|
|
14
|
+
*
|
|
15
|
+
* Rendered twice by the root, once per layer, and picks its colour from the
|
|
16
|
+
* layer it is in: the variant colour on the resting surface, the variant's
|
|
17
|
+
* foreground inside the fill. The two copies share every other class, so they
|
|
18
|
+
* wrap the same and the wipe's edge falls inside a glyph.
|
|
19
|
+
*/
|
|
20
|
+
export function ProgressButtonLabel({ className, ...props }: ProgressButtonLabelProps): ReactElement {
|
|
21
|
+
const { variant, size } = useProgressButtonPart("ProgressButton.Label");
|
|
22
|
+
const layer = useProgressButtonLayer();
|
|
23
|
+
const slots = progressButtonVariants({ size, variant });
|
|
24
|
+
const resolved = layer === "fill" ? slots.fillLabel({ className }) : slots.label({ className });
|
|
25
|
+
return <Text className={resolved} {...props} />;
|
|
26
|
+
}
|
|
27
|
+
ProgressButtonLabel.displayName = "DelacourUI.ProgressButton.Label";
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createContext, type ReactElement, type ReactNode, use } from "react";
|
|
2
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
3
|
+
import type { ProgressButtonSize, ProgressButtonVariant } from "./progress-button.variants";
|
|
4
|
+
|
|
5
|
+
export type ProgressButtonContextValue = {
|
|
6
|
+
variant: ProgressButtonVariant;
|
|
7
|
+
size: ProgressButtonSize;
|
|
8
|
+
/** Whether the hold has completed and not yet been reset. */
|
|
9
|
+
isCompleted: boolean;
|
|
10
|
+
isDisabled: boolean;
|
|
11
|
+
/** How far the fill has got, 0 to 1, on the UI thread. */
|
|
12
|
+
progress: SharedValue<number>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Which copy of the children a part is rendering in.
|
|
17
|
+
*
|
|
18
|
+
* The children are drawn twice — once on the resting surface and once inside
|
|
19
|
+
* the fill — and a `ProgressButton.Label` picks its colour from this rather
|
|
20
|
+
* than from a prop the caller would have to repeat.
|
|
21
|
+
*/
|
|
22
|
+
export type ProgressButtonLayer = "surface" | "fill";
|
|
23
|
+
|
|
24
|
+
const ProgressButtonContext = createContext<ProgressButtonContextValue | null>(null);
|
|
25
|
+
const ProgressButtonLayerContext = createContext<ProgressButtonLayer>("surface");
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Supplies the enclosing button's state to its subtree.
|
|
29
|
+
*
|
|
30
|
+
* In a leaf of its own, importing nothing but types, so a part can read it
|
|
31
|
+
* without importing `./progress-button` and closing a cycle (package AGENTS.md
|
|
32
|
+
* rule 3).
|
|
33
|
+
*/
|
|
34
|
+
export function ProgressButtonProvider({
|
|
35
|
+
value,
|
|
36
|
+
children,
|
|
37
|
+
}: {
|
|
38
|
+
value: ProgressButtonContextValue;
|
|
39
|
+
children: ReactNode;
|
|
40
|
+
}): ReactElement {
|
|
41
|
+
return <ProgressButtonContext value={value}>{children}</ProgressButtonContext>;
|
|
42
|
+
}
|
|
43
|
+
ProgressButtonProvider.displayName = "DelacourUI.ProgressButton.Provider";
|
|
44
|
+
|
|
45
|
+
/** Marks which copy of the children — surface or fill — its subtree is. */
|
|
46
|
+
export function ProgressButtonLayerProvider({
|
|
47
|
+
value,
|
|
48
|
+
children,
|
|
49
|
+
}: {
|
|
50
|
+
value: ProgressButtonLayer;
|
|
51
|
+
children: ReactNode;
|
|
52
|
+
}): ReactElement {
|
|
53
|
+
return <ProgressButtonLayerContext value={value}>{children}</ProgressButtonLayerContext>;
|
|
54
|
+
}
|
|
55
|
+
ProgressButtonLayerProvider.displayName = "DelacourUI.ProgressButton.LayerProvider";
|
|
56
|
+
|
|
57
|
+
/** The enclosing button's state, or null outside a `<ProgressButton>`. */
|
|
58
|
+
export function useProgressButtonContext(): ProgressButtonContextValue | null {
|
|
59
|
+
return use(ProgressButtonContext);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Reads the enclosing button's state.
|
|
64
|
+
*
|
|
65
|
+
* For a custom child that changes with the hold — `progress` is a shared value,
|
|
66
|
+
* so a child can drive an animated style off it without a render per frame.
|
|
67
|
+
* Throws outside a `<ProgressButton>`.
|
|
68
|
+
*/
|
|
69
|
+
export function useProgressButton(): ProgressButtonContextValue {
|
|
70
|
+
const context = useProgressButtonContext();
|
|
71
|
+
if (!context) {
|
|
72
|
+
throw new Error("useProgressButton must be called inside a <ProgressButton>.");
|
|
73
|
+
}
|
|
74
|
+
return context;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The enclosing button's state, for a compound part that cannot work without it.
|
|
79
|
+
*
|
|
80
|
+
* Internal: deliberately not re-exported from `index.ts`.
|
|
81
|
+
*/
|
|
82
|
+
export function useProgressButtonPart(component: string): ProgressButtonContextValue {
|
|
83
|
+
const context = useProgressButtonContext();
|
|
84
|
+
if (!context) {
|
|
85
|
+
throw new Error(`${component} must be rendered inside a <ProgressButton>.`);
|
|
86
|
+
}
|
|
87
|
+
return context;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Which copy of the children this part is in. `surface` outside a button. */
|
|
91
|
+
export function useProgressButtonLayer(): ProgressButtonLayer {
|
|
92
|
+
return use(ProgressButtonLayerContext);
|
|
93
|
+
}
|