panelui-native 0.75.0 → 0.76.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/lib/module/components/ai-input/ai-input-growth.js +62 -0
- package/lib/module/components/ai-input/ai-input-growth.js.map +1 -0
- package/lib/module/components/ai-input/index.js +1568 -0
- package/lib/module/components/ai-input/index.js.map +1 -0
- package/lib/module/components/alert/index.js +1 -1
- package/lib/module/components/alert/index.js.map +1 -1
- package/lib/module/components/avatar/avatar-source.js +15 -0
- package/lib/module/components/avatar/avatar-source.js.map +1 -0
- package/lib/module/components/avatar/index.js +32 -31
- package/lib/module/components/avatar/index.js.map +1 -1
- package/lib/module/components/bottom-sheet/index.js +3 -2
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/button-group/index.js +2 -3
- package/lib/module/components/button-group/index.js.map +1 -1
- package/lib/module/components/carousel/index.js +10 -7
- package/lib/module/components/carousel/index.js.map +1 -1
- package/lib/module/components/collapsible/index.js +1 -1
- package/lib/module/components/collapsible/index.js.map +1 -1
- package/lib/module/components/drawer/index.js +18 -14
- package/lib/module/components/drawer/index.js.map +1 -1
- package/lib/module/components/field/index.js +7 -8
- package/lib/module/components/field/index.js.map +1 -1
- package/lib/module/components/label/index.js +1 -1
- package/lib/module/components/label/index.js.map +1 -1
- package/lib/module/components/marquee/index.js +36 -11
- package/lib/module/components/marquee/index.js.map +1 -1
- package/lib/module/components/marquee/marquee-math.js +22 -6
- package/lib/module/components/marquee/marquee-math.js.map +1 -1
- package/lib/module/components/plan/index.js +2 -1
- package/lib/module/components/plan/index.js.map +1 -1
- package/lib/module/components/plot/index.js +27 -9
- package/lib/module/components/plot/index.js.map +1 -1
- package/lib/module/components/search-bar/index.js +11 -3
- package/lib/module/components/search-bar/index.js.map +1 -1
- package/lib/module/components/split-view/index.js +26 -27
- package/lib/module/components/split-view/index.js.map +1 -1
- package/lib/module/components/split-view/split-view-lifecycle.js +32 -0
- package/lib/module/components/split-view/split-view-lifecycle.js.map +1 -0
- package/lib/module/components/splitter/index.js +7 -13
- package/lib/module/components/splitter/index.js.map +1 -1
- package/lib/module/components/splitter/splitter-math.js +14 -0
- package/lib/module/components/splitter/splitter-math.js.map +1 -1
- package/lib/module/components/tabs/index.js +1 -1
- package/lib/module/components/tabs/index.js.map +1 -1
- package/lib/module/components/timeline/index.js +156 -42
- package/lib/module/components/timeline/index.js.map +1 -1
- package/lib/module/components/timeline/timeline-geometry.js +11 -0
- package/lib/module/components/timeline/timeline-geometry.js.map +1 -1
- package/lib/module/components/toast/index.js +1 -1
- package/lib/module/components/toast/index.js.map +1 -1
- package/lib/module/components/tooltip/index.js +2 -2
- package/lib/module/components/tooltip/index.js.map +1 -1
- package/lib/module/components/tree/index.js +2 -2
- package/lib/module/components/tree/index.js.map +1 -1
- package/lib/module/components/typography/index.js +2 -1
- package/lib/module/components/typography/index.js.map +1 -1
- package/lib/module/components/waterfall-chart/index.js +22 -3
- package/lib/module/components/waterfall-chart/index.js.map +1 -1
- package/lib/module/hooks/use-disclosure.js +10 -8
- package/lib/module/hooks/use-disclosure.js.map +1 -1
- package/lib/module/icons/index.js +135 -0
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +5 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/controllable-state.js +61 -0
- package/lib/module/primitives/controllable-state.js.map +1 -0
- package/lib/module/primitives/finite-chart.js +15 -0
- package/lib/module/primitives/finite-chart.js.map +1 -0
- package/lib/module/primitives/glass.js +136 -0
- package/lib/module/primitives/glass.js.map +1 -0
- package/lib/module/primitives/scrim.js +8 -1
- package/lib/module/primitives/scrim.js.map +1 -1
- package/lib/typescript/src/components/ai-input/ai-input-growth.d.ts +59 -0
- package/lib/typescript/src/components/ai-input/ai-input-growth.d.ts.map +1 -0
- package/lib/typescript/src/components/ai-input/index.d.ts +423 -0
- package/lib/typescript/src/components/ai-input/index.d.ts.map +1 -0
- package/lib/typescript/src/components/avatar/avatar-source.d.ts +4 -0
- package/lib/typescript/src/components/avatar/avatar-source.d.ts.map +1 -0
- package/lib/typescript/src/components/avatar/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +10 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/carousel/index.d.ts.map +1 -1
- package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/marquee/index.d.ts +15 -6
- package/lib/typescript/src/components/marquee/index.d.ts.map +1 -1
- package/lib/typescript/src/components/marquee/marquee-math.d.ts +6 -0
- package/lib/typescript/src/components/marquee/marquee-math.d.ts.map +1 -1
- package/lib/typescript/src/components/plan/index.d.ts.map +1 -1
- package/lib/typescript/src/components/plot/index.d.ts +2 -1
- package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
- package/lib/typescript/src/components/search-bar/index.d.ts.map +1 -1
- package/lib/typescript/src/components/split-view/index.d.ts.map +1 -1
- package/lib/typescript/src/components/split-view/split-view-lifecycle.d.ts +14 -0
- package/lib/typescript/src/components/split-view/split-view-lifecycle.d.ts.map +1 -0
- package/lib/typescript/src/components/splitter/index.d.ts +2 -2
- package/lib/typescript/src/components/splitter/index.d.ts.map +1 -1
- package/lib/typescript/src/components/splitter/splitter-math.d.ts +2 -0
- package/lib/typescript/src/components/splitter/splitter-math.d.ts.map +1 -1
- package/lib/typescript/src/components/timeline/index.d.ts +24 -2
- package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
- package/lib/typescript/src/components/timeline/timeline-geometry.d.ts +2 -0
- package/lib/typescript/src/components/timeline/timeline-geometry.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
- package/lib/typescript/src/components/waterfall-chart/index.d.ts +2 -1
- package/lib/typescript/src/components/waterfall-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-disclosure.d.ts.map +1 -1
- package/lib/typescript/src/icons/index.d.ts +18 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/controllable-state.d.ts +33 -0
- package/lib/typescript/src/primitives/controllable-state.d.ts.map +1 -0
- package/lib/typescript/src/primitives/finite-chart.d.ts +5 -0
- package/lib/typescript/src/primitives/finite-chart.d.ts.map +1 -0
- package/lib/typescript/src/primitives/glass.d.ts +43 -0
- package/lib/typescript/src/primitives/glass.d.ts.map +1 -0
- package/lib/typescript/src/primitives/scrim.d.ts +7 -0
- package/lib/typescript/src/primitives/scrim.d.ts.map +1 -1
- package/package.json +7 -2
- package/src/components/ai-input/ai-input-growth.ts +65 -0
- package/src/components/ai-input/index.tsx +1937 -0
- package/src/components/alert/index.tsx +1 -1
- package/src/components/avatar/avatar-source.ts +18 -0
- package/src/components/avatar/index.tsx +35 -27
- package/src/components/bottom-sheet/index.tsx +15 -3
- package/src/components/button-group/index.tsx +1 -1
- package/src/components/carousel/index.tsx +10 -11
- package/src/components/collapsible/index.tsx +1 -1
- package/src/components/drawer/index.tsx +24 -20
- package/src/components/field/index.tsx +4 -4
- package/src/components/label/index.tsx +1 -1
- package/src/components/marquee/index.tsx +56 -12
- package/src/components/marquee/marquee-math.ts +26 -3
- package/src/components/plan/index.tsx +6 -2
- package/src/components/plot/index.tsx +37 -11
- package/src/components/search-bar/index.tsx +9 -3
- package/src/components/split-view/index.tsx +24 -33
- package/src/components/split-view/split-view-lifecycle.ts +43 -0
- package/src/components/splitter/index.tsx +7 -11
- package/src/components/splitter/splitter-math.ts +28 -0
- package/src/components/tabs/index.tsx +1 -1
- package/src/components/timeline/index.tsx +189 -34
- package/src/components/timeline/timeline-geometry.ts +11 -0
- package/src/components/toast/index.tsx +6 -1
- package/src/components/tooltip/index.tsx +1 -1
- package/src/components/tree/index.tsx +2 -2
- package/src/components/typography/index.tsx +2 -2
- package/src/components/waterfall-chart/index.tsx +37 -4
- package/src/hooks/use-disclosure.ts +7 -12
- package/src/icons/index.tsx +106 -0
- package/src/index.ts +27 -0
- package/src/primitives/controllable-state.ts +112 -0
- package/src/primitives/finite-chart.ts +14 -0
- package/src/primitives/glass.tsx +171 -0
- package/src/primitives/scrim.tsx +7 -1
package/src/index.ts
CHANGED
|
@@ -20,6 +20,9 @@ export { Portal, PortalHost, PortalProvider } from './primitives/portal';
|
|
|
20
20
|
// recede. Public so a custom overlay gets the same backdrop — and the same
|
|
21
21
|
// graceful fall back from blur to dim — as the built-in ones.
|
|
22
22
|
export { Scrim, hasBlur, type ScrimProps } from './primitives/scrim';
|
|
23
|
+
// The material iOS draws its floating controls in, with a solid fallback where
|
|
24
|
+
// there is none. Public so a surface of your own gets the same treatment.
|
|
25
|
+
export { Glass, hasGlass, type GlassProps, type GlassVariant, type GlassRadius } from './primitives/glass';
|
|
23
26
|
export { Text, type TextProps } from './primitives/text';
|
|
24
27
|
export { Collapse, type CollapseProps } from './primitives/collapse';
|
|
25
28
|
export {
|
|
@@ -935,6 +938,25 @@ export {
|
|
|
935
938
|
type SignatureToolbarProps,
|
|
936
939
|
type SignatureButtonProps,
|
|
937
940
|
} from './components/signature';
|
|
941
|
+
export {
|
|
942
|
+
AIInput,
|
|
943
|
+
type AIInputProps,
|
|
944
|
+
type AIInputStatus,
|
|
945
|
+
type AIInputFieldProps,
|
|
946
|
+
type AIInputRowProps,
|
|
947
|
+
type AIInputToolbarProps,
|
|
948
|
+
type AIInputActionProps,
|
|
949
|
+
type AIInputPillProps,
|
|
950
|
+
type AIInputSubmitProps,
|
|
951
|
+
type AIInputRecordingProps,
|
|
952
|
+
type AIInputSheetProps,
|
|
953
|
+
type AIInputSheetScreenProps,
|
|
954
|
+
type AIInputSheetGroupProps,
|
|
955
|
+
type AIInputSheetRowProps,
|
|
956
|
+
type AIInputSheetToggleProps,
|
|
957
|
+
type AIInputSheetChoiceProps,
|
|
958
|
+
type AIInputVoiceModeProps,
|
|
959
|
+
} from './components/ai-input';
|
|
938
960
|
export { Shimmer, type ShimmerProps } from './components/shimmer';
|
|
939
961
|
export {
|
|
940
962
|
Reasoning,
|
|
@@ -1190,17 +1212,20 @@ export {
|
|
|
1190
1212
|
ArrowDownIcon,
|
|
1191
1213
|
ArrowUpIcon,
|
|
1192
1214
|
ArrowUpRightIcon,
|
|
1215
|
+
AudioLinesIcon,
|
|
1193
1216
|
BadgeCheckIcon,
|
|
1194
1217
|
BellIcon,
|
|
1195
1218
|
BookmarkIcon,
|
|
1196
1219
|
BoldIcon,
|
|
1197
1220
|
CalendarIcon,
|
|
1221
|
+
CameraIcon,
|
|
1198
1222
|
CardIcon,
|
|
1199
1223
|
CheckCircleIcon,
|
|
1200
1224
|
CheckIcon,
|
|
1201
1225
|
ChevronDownIcon,
|
|
1202
1226
|
ChevronLeftIcon,
|
|
1203
1227
|
ChevronRightIcon,
|
|
1228
|
+
ChevronsUpDownIcon,
|
|
1204
1229
|
ChevronUpIcon,
|
|
1205
1230
|
CircleIcon,
|
|
1206
1231
|
CodeIcon,
|
|
@@ -1215,6 +1240,7 @@ export {
|
|
|
1215
1240
|
FileIcon,
|
|
1216
1241
|
FolderIcon,
|
|
1217
1242
|
FolderOpenIcon,
|
|
1243
|
+
GlobeIcon,
|
|
1218
1244
|
GoogleIcon,
|
|
1219
1245
|
HeartIcon,
|
|
1220
1246
|
HeadingIcon,
|
|
@@ -1247,6 +1273,7 @@ export {
|
|
|
1247
1273
|
RotateCcwIcon,
|
|
1248
1274
|
RotateCwIcon,
|
|
1249
1275
|
SearchIcon,
|
|
1276
|
+
SendArrowIcon,
|
|
1250
1277
|
SendIcon,
|
|
1251
1278
|
ShareNodesIcon,
|
|
1252
1279
|
ShieldAlertIcon,
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useLayoutEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type Dispatch,
|
|
8
|
+
type SetStateAction,
|
|
9
|
+
} from 'react';
|
|
10
|
+
|
|
11
|
+
const identity = <T,>(value: T): T => value;
|
|
12
|
+
const objectIs = <T,>(left: T, right: T): boolean => Object.is(left, right);
|
|
13
|
+
|
|
14
|
+
export interface ControllableStateOptions<T> {
|
|
15
|
+
/** Accepted owner state. `undefined` selects uncontrolled ownership. */
|
|
16
|
+
value: T | undefined;
|
|
17
|
+
/** Initial state used only while uncontrolled. */
|
|
18
|
+
defaultValue: T;
|
|
19
|
+
/** Reports a distinct requested value exactly once per setter invocation. */
|
|
20
|
+
onChange?: (value: T) => void;
|
|
21
|
+
/** Reports accepted state changes, whether requested or externally reset. */
|
|
22
|
+
onSettled?: (value: T) => void;
|
|
23
|
+
/** Normalize defaults, controlled values, and requests through one boundary. */
|
|
24
|
+
normalize?: (value: T) => T;
|
|
25
|
+
/** Equality used for no-op requests and accepted settlement. */
|
|
26
|
+
isEqual?: (left: T, right: T) => boolean;
|
|
27
|
+
/** Stable semantic identity for controlled objects recreated by their owner. */
|
|
28
|
+
getValueKey?: (value: T) => unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ControllableStateResult<T> {
|
|
32
|
+
/** State accepted by the current owner; controlled requests never render optimistically. */
|
|
33
|
+
value: T;
|
|
34
|
+
setValue: Dispatch<SetStateAction<T>>;
|
|
35
|
+
isControlled: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Owner-driven controlled/uncontrolled state with an explicit settlement edge.
|
|
40
|
+
*
|
|
41
|
+
* Switching from controlled to uncontrolled retains the last accepted owner
|
|
42
|
+
* value instead of resurrecting a stale default. Switching into controlled
|
|
43
|
+
* mode adopts the prop immediately. `undefined` is the ownership sentinel and
|
|
44
|
+
* therefore cannot itself be a controlled value; use `null` for empty state.
|
|
45
|
+
*/
|
|
46
|
+
export function useControllableState<T>({
|
|
47
|
+
value: valueProp,
|
|
48
|
+
defaultValue,
|
|
49
|
+
onChange,
|
|
50
|
+
onSettled,
|
|
51
|
+
normalize = identity,
|
|
52
|
+
isEqual = objectIs,
|
|
53
|
+
getValueKey,
|
|
54
|
+
}: ControllableStateOptions<T>): ControllableStateResult<T> {
|
|
55
|
+
const [internalValue, setInternalValue] = useState(() =>
|
|
56
|
+
normalize(defaultValue)
|
|
57
|
+
);
|
|
58
|
+
const isControlled = valueProp !== undefined;
|
|
59
|
+
const controlledKey = isControlled
|
|
60
|
+
? getValueKey
|
|
61
|
+
? getValueKey(valueProp as T)
|
|
62
|
+
: valueProp
|
|
63
|
+
: undefined;
|
|
64
|
+
const controlledValue = useMemo(
|
|
65
|
+
() => (isControlled ? normalize(valueProp as T) : undefined),
|
|
66
|
+
// The semantic key deliberately replaces object identity for equivalent owner values.
|
|
67
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
68
|
+
[controlledKey, isControlled, normalize]
|
|
69
|
+
);
|
|
70
|
+
const wasControlled = useRef(isControlled);
|
|
71
|
+
const lastControlledValue = useRef<T | undefined>(controlledValue);
|
|
72
|
+
if (isControlled) lastControlledValue.current = controlledValue;
|
|
73
|
+
|
|
74
|
+
const leavingControlled = wasControlled.current && !isControlled;
|
|
75
|
+
const value = isControlled
|
|
76
|
+
? (controlledValue as T)
|
|
77
|
+
: leavingControlled
|
|
78
|
+
? (lastControlledValue.current as T)
|
|
79
|
+
: internalValue;
|
|
80
|
+
const valueRef = useRef(value);
|
|
81
|
+
valueRef.current = value;
|
|
82
|
+
|
|
83
|
+
useLayoutEffect(() => {
|
|
84
|
+
if (wasControlled.current && !isControlled) {
|
|
85
|
+
setInternalValue(lastControlledValue.current as T);
|
|
86
|
+
}
|
|
87
|
+
wasControlled.current = isControlled;
|
|
88
|
+
}, [isControlled]);
|
|
89
|
+
|
|
90
|
+
const previousSettled = useRef(value);
|
|
91
|
+
useLayoutEffect(() => {
|
|
92
|
+
if (isEqual(previousSettled.current, value)) return;
|
|
93
|
+
previousSettled.current = value;
|
|
94
|
+
onSettled?.(value);
|
|
95
|
+
}, [isEqual, onSettled, value]);
|
|
96
|
+
|
|
97
|
+
const setValue = useCallback<Dispatch<SetStateAction<T>>>(
|
|
98
|
+
(next) => {
|
|
99
|
+
const requested = normalize(
|
|
100
|
+
typeof next === 'function'
|
|
101
|
+
? (next as (current: T) => T)(valueRef.current)
|
|
102
|
+
: next
|
|
103
|
+
);
|
|
104
|
+
if (isEqual(valueRef.current, requested)) return;
|
|
105
|
+
if (!isControlled) setInternalValue(requested);
|
|
106
|
+
onChange?.(requested);
|
|
107
|
+
},
|
|
108
|
+
[isControlled, isEqual, normalize, onChange]
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
return { value, setValue, isControlled };
|
|
112
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Returns a number only when it is safe to use in chart arithmetic. */
|
|
2
|
+
export function finiteChartNumber(value: unknown): number | undefined {
|
|
3
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/** Accepts an explicit domain only when both ends are finite. */
|
|
7
|
+
export function finiteChartDomain(
|
|
8
|
+
domain: readonly [number, number] | undefined
|
|
9
|
+
): [number, number] | undefined {
|
|
10
|
+
if (!domain) return undefined;
|
|
11
|
+
const low = finiteChartNumber(domain[0]);
|
|
12
|
+
const high = finiteChartNumber(domain[1]);
|
|
13
|
+
return low === undefined || high === undefined ? undefined : [low, high];
|
|
14
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glass — the material iOS draws its own floating controls in.
|
|
3
|
+
*
|
|
4
|
+
* A control that floats over content needs to read as *over* it, and a flat
|
|
5
|
+
* fill cannot say that: it either hides what is behind it or disappears into
|
|
6
|
+
* it. The system material does both jobs at once — it refracts what is behind,
|
|
7
|
+
* lifts its own edge, and stays legible over anything.
|
|
8
|
+
*
|
|
9
|
+
* ```tsx
|
|
10
|
+
* <Glass radius={28} className="px-4 py-3">
|
|
11
|
+
* <Text>Over whatever is behind it.</Text>
|
|
12
|
+
* </Glass>
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* ## Where it is real, and where it is not
|
|
16
|
+
*
|
|
17
|
+
* The material exists on iOS 26 and above. Below that, on Android, on web, and
|
|
18
|
+
* for anyone who has switched Reduce Transparency on, this draws
|
|
19
|
+
* `fallbackClassName` instead — a solid token surface. That is deliberate
|
|
20
|
+
* rather than a gap: a hand-drawn approximation of a system material is a
|
|
21
|
+
* near-miss on the one platform that has the real thing, and on Android it is
|
|
22
|
+
* an iOS look pasted onto a platform that never asked for it.
|
|
23
|
+
*
|
|
24
|
+
* So `Glass` is not a promise that the screen will be glass. It is a promise
|
|
25
|
+
* that the container will be drawn correctly either way, and callers should
|
|
26
|
+
* pick a `fallbackClassName` that stands on its own.
|
|
27
|
+
*
|
|
28
|
+
* ## The material is a layer, not the box
|
|
29
|
+
*
|
|
30
|
+
* The native view is rendered as a non-interactive layer filling this
|
|
31
|
+
* container, with the content above it, for the same reason `Scrim` layers a
|
|
32
|
+
* blur: it keeps every layout class, every token and every touch target on an
|
|
33
|
+
* ordinary `View` that behaves the way the rest of the library does.
|
|
34
|
+
*
|
|
35
|
+
* `radius` is a number rather than a class because the material has to round
|
|
36
|
+
* its own corners — clipping a square one to a rounded parent throws away the
|
|
37
|
+
* lit edge that makes it read as glass.
|
|
38
|
+
*
|
|
39
|
+
* ## Do not fade it
|
|
40
|
+
*
|
|
41
|
+
* Setting `opacity` to `0` on the material or on anything above it stops it
|
|
42
|
+
* rendering at all, and it does not come back when the opacity does. Move it,
|
|
43
|
+
* or unmount it; never animate it out.
|
|
44
|
+
*/
|
|
45
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
46
|
+
import { Platform, StyleSheet, View, type StyleProp, type ViewProps, type ViewStyle } from 'react-native';
|
|
47
|
+
import { cn } from '../utils/cn';
|
|
48
|
+
import { useReduceTransparency } from './scrim';
|
|
49
|
+
|
|
50
|
+
/** How the material treats what is behind it. */
|
|
51
|
+
export type GlassVariant = 'regular' | 'clear';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Corner radius in points — one number for all four, or a top and a bottom.
|
|
55
|
+
*
|
|
56
|
+
* The two-sided form is for a surface with an edge that is not a real edge: a
|
|
57
|
+
* sheet docked to the bottom of the screen rounds its top and leaves its
|
|
58
|
+
* bottom square, because the screen edge is where it ends.
|
|
59
|
+
*/
|
|
60
|
+
export type GlassRadius = number | { top?: number; bottom?: number };
|
|
61
|
+
|
|
62
|
+
function shapeOf(radius: GlassRadius | undefined) {
|
|
63
|
+
if (radius === undefined) return null;
|
|
64
|
+
if (typeof radius === 'number') return { borderRadius: radius };
|
|
65
|
+
return {
|
|
66
|
+
borderTopLeftRadius: radius.top ?? 0,
|
|
67
|
+
borderTopRightRadius: radius.top ?? 0,
|
|
68
|
+
borderBottomLeftRadius: radius.bottom ?? 0,
|
|
69
|
+
borderBottomRightRadius: radius.bottom ?? 0,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface GlassViewProps {
|
|
74
|
+
glassEffectStyle?: GlassVariant | 'none';
|
|
75
|
+
tintColor?: string;
|
|
76
|
+
colorScheme?: 'auto' | 'light' | 'dark';
|
|
77
|
+
style?: StyleProp<ViewStyle>;
|
|
78
|
+
pointerEvents?: ViewProps['pointerEvents'];
|
|
79
|
+
children?: ReactNode;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* `expo-glass-effect`'s GlassView, or null when the material cannot be drawn
|
|
84
|
+
* here.
|
|
85
|
+
*
|
|
86
|
+
* Resolved once at module load, behind three gates that all have to pass. The
|
|
87
|
+
* package is optional, so the require can fail; the API is missing from some
|
|
88
|
+
* iOS 26 builds, and reaching for it there crashes; and the design itself is
|
|
89
|
+
* only present when the app was compiled for it. Asking all three once is
|
|
90
|
+
* cheaper than a try/catch on every render, and there is no answer that can
|
|
91
|
+
* change while the process is alive.
|
|
92
|
+
*/
|
|
93
|
+
const GlassView: ComponentType<GlassViewProps> | null = (() => {
|
|
94
|
+
if (Platform.OS !== 'ios') return null;
|
|
95
|
+
try {
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
97
|
+
const mod = require('expo-glass-effect');
|
|
98
|
+
if (typeof mod?.isGlassEffectAPIAvailable === 'function' && !mod.isGlassEffectAPIAvailable()) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
if (typeof mod?.isLiquidGlassAvailable === 'function' && !mod.isLiquidGlassAvailable()) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return (mod?.GlassView as ComponentType<GlassViewProps>) ?? null;
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* True when the real material can be drawn — for a caller that wants to know
|
|
112
|
+
* before it commits to a look. It says nothing about Reduce Transparency, which
|
|
113
|
+
* is a live preference and belongs to the hook.
|
|
114
|
+
*/
|
|
115
|
+
export const hasGlass = GlassView !== null;
|
|
116
|
+
|
|
117
|
+
export interface GlassProps extends ViewProps {
|
|
118
|
+
/**
|
|
119
|
+
* How much of what is behind shows through. `regular` is the everyday
|
|
120
|
+
* material; `clear` is thinner, for something over its own artwork.
|
|
121
|
+
*/
|
|
122
|
+
variant?: GlassVariant;
|
|
123
|
+
/** Tints the material. Takes a colour, not a token name. */
|
|
124
|
+
tint?: string;
|
|
125
|
+
/**
|
|
126
|
+
* Corner radius in points. The material rounds itself to this, so give it
|
|
127
|
+
* the same shape the container is drawn with — clipping a square material
|
|
128
|
+
* to a rounded parent throws away the lit edge that makes it read as glass.
|
|
129
|
+
*/
|
|
130
|
+
radius?: GlassRadius;
|
|
131
|
+
/** Applied only when the material cannot be drawn. Give it a real surface. */
|
|
132
|
+
fallbackClassName?: string;
|
|
133
|
+
className?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function Glass({
|
|
137
|
+
variant = 'regular',
|
|
138
|
+
tint,
|
|
139
|
+
radius,
|
|
140
|
+
fallbackClassName = 'bg-card',
|
|
141
|
+
className,
|
|
142
|
+
children,
|
|
143
|
+
style,
|
|
144
|
+
...props
|
|
145
|
+
}: GlassProps) {
|
|
146
|
+
const reduceTransparency = useReduceTransparency();
|
|
147
|
+
// Not knowing yet counts as "do not draw it": the material arriving a frame
|
|
148
|
+
// late is invisible, and one flashing at somebody who opted out is not.
|
|
149
|
+
const material = GlassView !== null && reduceTransparency === false;
|
|
150
|
+
const shape = shapeOf(radius);
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<View
|
|
154
|
+
className={cn('overflow-hidden', material ? null : fallbackClassName, className)}
|
|
155
|
+
style={[shape, style]}
|
|
156
|
+
{...props}
|
|
157
|
+
>
|
|
158
|
+
{material && GlassView ? (
|
|
159
|
+
<GlassView
|
|
160
|
+
glassEffectStyle={variant}
|
|
161
|
+
tintColor={tint}
|
|
162
|
+
pointerEvents="none"
|
|
163
|
+
style={[StyleSheet.absoluteFill, shape]}
|
|
164
|
+
/>
|
|
165
|
+
) : null}
|
|
166
|
+
{children}
|
|
167
|
+
</View>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
Glass.displayName = 'Glass';
|
package/src/primitives/scrim.tsx
CHANGED
|
@@ -113,7 +113,13 @@ function observeReduceTransparency(
|
|
|
113
113
|
*/
|
|
114
114
|
let knownReduceTransparency: boolean | null = null;
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Whether the device asks for Reduce Transparency: `true`, `false`, or `null`
|
|
118
|
+
* while the platform has not answered yet. Exported because every material in
|
|
119
|
+
* the library has to respect the same preference, and two copies of this would
|
|
120
|
+
* be two answers.
|
|
121
|
+
*/
|
|
122
|
+
export function useReduceTransparency() {
|
|
117
123
|
// Not knowing is deliberately conservative: draw opaque until the platform
|
|
118
124
|
// says blur is allowed, rather than flashing blur at someone who opted out.
|
|
119
125
|
const [enabled, setEnabled] = useState<boolean | null>(knownReduceTransparency);
|