panelui-native 0.7.0 → 0.9.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/attachment/index.js +219 -0
- package/lib/module/components/attachment/index.js.map +1 -0
- package/lib/module/components/bottom-sheet/index.js +15 -1
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/dialog/index.js +6 -2
- package/lib/module/components/dialog/index.js.map +1 -1
- package/lib/module/components/empty-state/index.js +94 -17
- package/lib/module/components/empty-state/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +1024 -0
- package/lib/module/components/line-chart/index.js.map +1 -0
- package/lib/module/components/marker/index.js +190 -0
- package/lib/module/components/marker/index.js.map +1 -0
- package/lib/module/components/message/index.js +24 -8
- package/lib/module/components/message/index.js.map +1 -1
- package/lib/module/components/message-scroller/index.js +451 -0
- package/lib/module/components/message-scroller/index.js.map +1 -0
- package/lib/module/components/popover/index.js +500 -0
- package/lib/module/components/popover/index.js.map +1 -0
- package/lib/module/components/radio-group/index.js +104 -16
- package/lib/module/components/radio-group/index.js.map +1 -1
- package/lib/module/components/separator/index.js +91 -0
- package/lib/module/components/separator/index.js.map +1 -0
- package/lib/module/components/surface/index.js +21 -2
- package/lib/module/components/surface/index.js.map +1 -1
- package/lib/module/icons/index.js +69 -0
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +7 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/scrim.js +72 -0
- package/lib/module/primitives/scrim.js.map +1 -0
- package/lib/typescript/src/components/attachment/index.d.ts +93 -0
- package/lib/typescript/src/components/attachment/index.d.ts.map +1 -0
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +6 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/dialog/index.d.ts +7 -1
- package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
- package/lib/typescript/src/components/empty-state/index.d.ts +152 -2
- package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts +247 -0
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/marker/index.d.ts +137 -0
- package/lib/typescript/src/components/marker/index.d.ts.map +1 -0
- package/lib/typescript/src/components/message/index.d.ts +8 -1
- package/lib/typescript/src/components/message/index.d.ts.map +1 -1
- package/lib/typescript/src/components/message-scroller/index.d.ts +150 -0
- package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -0
- package/lib/typescript/src/components/popover/index.d.ts +130 -0
- package/lib/typescript/src/components/popover/index.d.ts.map +1 -0
- package/lib/typescript/src/components/radio-group/index.d.ts +32 -0
- package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/separator/index.d.ts +47 -0
- package/lib/typescript/src/components/separator/index.d.ts.map +1 -0
- package/lib/typescript/src/components/surface/index.d.ts +51 -2
- package/lib/typescript/src/components/surface/index.d.ts.map +1 -1
- package/lib/typescript/src/icons/index.d.ts +6 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +7 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/scrim.d.ts +21 -0
- package/lib/typescript/src/primitives/scrim.d.ts.map +1 -0
- package/package.json +5 -1
- package/src/components/attachment/index.tsx +279 -0
- package/src/components/bottom-sheet/index.tsx +20 -0
- package/src/components/dialog/index.tsx +10 -1
- package/src/components/empty-state/index.tsx +60 -14
- package/src/components/line-chart/index.tsx +1224 -0
- package/src/components/marker/index.tsx +194 -0
- package/src/components/message/index.tsx +29 -5
- package/src/components/message-scroller/index.tsx +574 -0
- package/src/components/popover/index.tsx +599 -0
- package/src/components/radio-group/index.tsx +108 -17
- package/src/components/separator/index.tsx +97 -0
- package/src/components/surface/index.tsx +29 -3
- package/src/icons/index.tsx +48 -0
- package/src/index.ts +60 -0
- package/src/primitives/scrim.tsx +95 -0
- package/theme.css +58 -0
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RadioGroup — one choice from a set.
|
|
3
|
+
*
|
|
4
|
+
* The group owns the value and hands each item its selected state through
|
|
5
|
+
* context, so an item is a thin pressable that reports its own value up and
|
|
6
|
+
* reads selection back down.
|
|
7
|
+
*
|
|
8
|
+
* Two shapes, chosen on the group and inherited by every item: `dot` is the
|
|
9
|
+
* classic label-beside-a-disc row; `card` turns the whole surface into the
|
|
10
|
+
* target — for pickable options where the disc is a confirmation rather than
|
|
11
|
+
* the thing you aim at. The card is the same treatment `Checkbox` offers, so a
|
|
12
|
+
* form mixing single- and multi-select choices reads as one family.
|
|
13
|
+
*
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <RadioGroup value={plan} onValueChange={setPlan} variant="card">
|
|
16
|
+
* <RadioGroup.Item value="pro" label="Pro" description="For growing teams" />
|
|
17
|
+
* <RadioGroup.Item value="max" label="Max" description="Everything, uncapped" />
|
|
18
|
+
* </RadioGroup>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
1
21
|
import {
|
|
2
22
|
createContext,
|
|
3
23
|
forwardRef,
|
|
@@ -13,13 +33,50 @@ import Animated, {
|
|
|
13
33
|
withSpring,
|
|
14
34
|
withTiming,
|
|
15
35
|
} from 'react-native-reanimated';
|
|
36
|
+
import { tv, type VariantProps } from 'tailwind-variants';
|
|
16
37
|
import { cn } from '../../utils/cn';
|
|
17
38
|
import { Text } from '../../primitives/text';
|
|
18
39
|
|
|
40
|
+
type RadioVariant = 'dot' | 'card';
|
|
41
|
+
|
|
42
|
+
const itemVariants = tv({
|
|
43
|
+
slots: {
|
|
44
|
+
row: 'flex-row items-center gap-2.5',
|
|
45
|
+
indicator:
|
|
46
|
+
'h-5 w-5 items-center justify-center rounded-full border border-input bg-background',
|
|
47
|
+
label: 'text-sm text-foreground',
|
|
48
|
+
description: 'text-xs text-muted-foreground',
|
|
49
|
+
},
|
|
50
|
+
variants: {
|
|
51
|
+
variant: {
|
|
52
|
+
dot: {},
|
|
53
|
+
card: {
|
|
54
|
+
// The whole surface is the target; the disc moves to the trailing edge
|
|
55
|
+
// as a confirmation of the row, not the affordance for it.
|
|
56
|
+
row: 'w-full items-start justify-between gap-3 rounded-xl border border-border bg-card p-4',
|
|
57
|
+
label: 'text-base font-medium',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
selected: {
|
|
61
|
+
true: {},
|
|
62
|
+
},
|
|
63
|
+
disabled: {
|
|
64
|
+
true: { row: 'opacity-50' },
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
compoundVariants: [
|
|
68
|
+
{ variant: 'card', selected: true, class: { row: 'border-primary bg-accent' } },
|
|
69
|
+
],
|
|
70
|
+
defaultVariants: {
|
|
71
|
+
variant: 'dot',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
19
75
|
interface RadioGroupContextValue {
|
|
20
76
|
value: string | undefined;
|
|
21
77
|
onValueChange: (value: string) => void;
|
|
22
78
|
disabled?: boolean;
|
|
79
|
+
variant: RadioVariant;
|
|
23
80
|
}
|
|
24
81
|
|
|
25
82
|
const RadioGroupContext = createContext<RadioGroupContextValue | null>(null);
|
|
@@ -29,14 +86,20 @@ export interface RadioGroupProps extends ViewProps {
|
|
|
29
86
|
value?: string;
|
|
30
87
|
onValueChange: (value: string) => void;
|
|
31
88
|
disabled?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* `dot` is the label-beside-a-disc row. `card` makes the whole surface the
|
|
91
|
+
* target and highlights the selected option — for a plan picker or a
|
|
92
|
+
* settings choice where each option carries a description.
|
|
93
|
+
*/
|
|
94
|
+
variant?: RadioVariant;
|
|
32
95
|
children: ReactNode;
|
|
33
96
|
}
|
|
34
97
|
|
|
35
98
|
const RadioGroupRoot = forwardRef<View, RadioGroupProps>(
|
|
36
|
-
({ className, value, onValueChange, disabled, children, ...props }, ref) => {
|
|
99
|
+
({ className, value, onValueChange, disabled, variant = 'dot', children, ...props }, ref) => {
|
|
37
100
|
const context = useMemo(
|
|
38
|
-
() => ({ value, onValueChange, disabled }),
|
|
39
|
-
[value, onValueChange, disabled]
|
|
101
|
+
() => ({ value, onValueChange, disabled, variant }),
|
|
102
|
+
[value, onValueChange, disabled, variant]
|
|
40
103
|
);
|
|
41
104
|
|
|
42
105
|
return (
|
|
@@ -59,12 +122,19 @@ export interface RadioGroupItemProps {
|
|
|
59
122
|
className?: string;
|
|
60
123
|
value: string;
|
|
61
124
|
label?: string;
|
|
125
|
+
/** Secondary line under the label. Most at home in the `card` variant. */
|
|
126
|
+
description?: string;
|
|
62
127
|
disabled?: boolean;
|
|
128
|
+
/** Hide the disc entirely — for a card whose selected fill is enough. */
|
|
129
|
+
hideIndicator?: boolean;
|
|
63
130
|
children?: ReactNode;
|
|
64
131
|
}
|
|
65
132
|
|
|
66
133
|
const RadioGroupItem = forwardRef<View, RadioGroupItemProps>(
|
|
67
|
-
(
|
|
134
|
+
(
|
|
135
|
+
{ className, value, label, description, disabled: itemDisabled, hideIndicator, children },
|
|
136
|
+
ref
|
|
137
|
+
) => {
|
|
68
138
|
const context = useContext(RadioGroupContext);
|
|
69
139
|
if (!context) {
|
|
70
140
|
throw new Error('RadioGroup.Item must be used within a <RadioGroup>');
|
|
@@ -72,6 +142,7 @@ const RadioGroupItem = forwardRef<View, RadioGroupItemProps>(
|
|
|
72
142
|
|
|
73
143
|
const selected = context.value === value;
|
|
74
144
|
const disabled = itemDisabled || context.disabled;
|
|
145
|
+
const variant = context.variant;
|
|
75
146
|
const progress = useSharedValue(selected ? 1 : 0);
|
|
76
147
|
|
|
77
148
|
useEffect(() => {
|
|
@@ -85,6 +156,34 @@ const RadioGroupItem = forwardRef<View, RadioGroupItemProps>(
|
|
|
85
156
|
transform: [{ scale: progress.value }],
|
|
86
157
|
}));
|
|
87
158
|
|
|
159
|
+
const slots = itemVariants({ variant, selected, disabled: !!disabled });
|
|
160
|
+
|
|
161
|
+
const indicator = hideIndicator ? null : (
|
|
162
|
+
<View className={slots.indicator()}>
|
|
163
|
+
<Animated.View style={dotStyle} className="h-2.5 w-2.5 rounded-full bg-primary" />
|
|
164
|
+
</View>
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// The card lays its text out first so the disc sits at the trailing edge;
|
|
168
|
+
// the dot row keeps the classic disc-then-label order.
|
|
169
|
+
const body =
|
|
170
|
+
variant === 'card' ? (
|
|
171
|
+
<>
|
|
172
|
+
<View className="flex-1 gap-1">
|
|
173
|
+
{label ? <Text className={slots.label()}>{label}</Text> : children}
|
|
174
|
+
{description ? (
|
|
175
|
+
<Text className={slots.description()}>{description}</Text>
|
|
176
|
+
) : null}
|
|
177
|
+
</View>
|
|
178
|
+
{indicator}
|
|
179
|
+
</>
|
|
180
|
+
) : (
|
|
181
|
+
<>
|
|
182
|
+
{indicator}
|
|
183
|
+
{label ? <Text className={slots.label()}>{label}</Text> : children}
|
|
184
|
+
</>
|
|
185
|
+
);
|
|
186
|
+
|
|
88
187
|
return (
|
|
89
188
|
<Pressable
|
|
90
189
|
ref={ref}
|
|
@@ -93,20 +192,10 @@ const RadioGroupItem = forwardRef<View, RadioGroupItemProps>(
|
|
|
93
192
|
accessibilityLabel={label}
|
|
94
193
|
disabled={disabled}
|
|
95
194
|
onPress={() => context.onValueChange(value)}
|
|
96
|
-
hitSlop={8}
|
|
97
|
-
className={
|
|
98
|
-
'flex-row items-center gap-2.5',
|
|
99
|
-
disabled && 'opacity-50',
|
|
100
|
-
className
|
|
101
|
-
)}
|
|
195
|
+
hitSlop={variant === 'card' ? undefined : 8}
|
|
196
|
+
className={slots.row({ className })}
|
|
102
197
|
>
|
|
103
|
-
|
|
104
|
-
<Animated.View
|
|
105
|
-
style={dotStyle}
|
|
106
|
-
className="h-2.5 w-2.5 rounded-full bg-primary"
|
|
107
|
-
/>
|
|
108
|
-
</View>
|
|
109
|
-
{label ? <Text className="text-sm text-foreground">{label}</Text> : children}
|
|
198
|
+
{body}
|
|
110
199
|
</Pressable>
|
|
111
200
|
);
|
|
112
201
|
}
|
|
@@ -116,3 +205,5 @@ RadioGroupItem.displayName = 'RadioGroup.Item';
|
|
|
116
205
|
export const RadioGroup = Object.assign(RadioGroupRoot, {
|
|
117
206
|
Item: RadioGroupItem,
|
|
118
207
|
});
|
|
208
|
+
|
|
209
|
+
export type { RadioVariant };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Separator — a rule between content.
|
|
3
|
+
*
|
|
4
|
+
* React Native has no `<hr>` and no border shorthand that survives a flex row,
|
|
5
|
+
* so a separator here is a view with a size on one axis and a stretch on the
|
|
6
|
+
* other. That is why orientation is a prop rather than something the layout
|
|
7
|
+
* infers: the component has to know which axis carries the thickness.
|
|
8
|
+
*
|
|
9
|
+
* ```tsx
|
|
10
|
+
* <Separator />
|
|
11
|
+
* <Separator orientation="vertical" />
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* A vertical separator takes its length from the parent, so the parent needs a
|
|
15
|
+
* height — inside a `flex-row` with `items-stretch`, or with an explicit `h-*`
|
|
16
|
+
* on either the row or the separator itself. Without one it measures zero and
|
|
17
|
+
* nothing draws.
|
|
18
|
+
*/
|
|
19
|
+
import { forwardRef } from 'react';
|
|
20
|
+
import { View, type ViewProps } from 'react-native';
|
|
21
|
+
import { tv, type VariantProps } from 'tailwind-variants';
|
|
22
|
+
|
|
23
|
+
const separatorVariants = tv({
|
|
24
|
+
base: 'bg-border',
|
|
25
|
+
variants: {
|
|
26
|
+
orientation: {
|
|
27
|
+
horizontal: 'w-full',
|
|
28
|
+
vertical: 'h-full',
|
|
29
|
+
},
|
|
30
|
+
variant: {
|
|
31
|
+
thin: '',
|
|
32
|
+
thick: '',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
compoundVariants: [
|
|
36
|
+
// Thickness is on whichever axis is *not* the length, so it cannot be
|
|
37
|
+
// expressed by the orientation and variant classes independently.
|
|
38
|
+
{ orientation: 'horizontal', variant: 'thin', class: 'h-px' },
|
|
39
|
+
{ orientation: 'horizontal', variant: 'thick', class: 'h-1' },
|
|
40
|
+
{ orientation: 'vertical', variant: 'thin', class: 'w-px' },
|
|
41
|
+
{ orientation: 'vertical', variant: 'thick', class: 'w-1' },
|
|
42
|
+
],
|
|
43
|
+
defaultVariants: {
|
|
44
|
+
orientation: 'horizontal',
|
|
45
|
+
variant: 'thin',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export interface SeparatorProps
|
|
50
|
+
extends ViewProps,
|
|
51
|
+
VariantProps<typeof separatorVariants> {
|
|
52
|
+
className?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Thickness in pixels, overriding the variant. Sets the height of a
|
|
55
|
+
* horizontal separator and the width of a vertical one.
|
|
56
|
+
*/
|
|
57
|
+
thickness?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the separator is only visual. A decorative separator is skipped by
|
|
60
|
+
* screen readers; set false when the split itself carries meaning — between
|
|
61
|
+
* two groups of menu items, say — and it is announced instead.
|
|
62
|
+
*/
|
|
63
|
+
decorative?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const Separator = forwardRef<View, SeparatorProps>(
|
|
67
|
+
(
|
|
68
|
+
{
|
|
69
|
+
className,
|
|
70
|
+
orientation = 'horizontal',
|
|
71
|
+
variant,
|
|
72
|
+
thickness,
|
|
73
|
+
decorative = true,
|
|
74
|
+
style,
|
|
75
|
+
...props
|
|
76
|
+
},
|
|
77
|
+
ref
|
|
78
|
+
) => (
|
|
79
|
+
<View
|
|
80
|
+
ref={ref}
|
|
81
|
+
// `role` rather than `accessibilityRole` — React Native's older role list
|
|
82
|
+
// has no separator, the ARIA-aligned one does.
|
|
83
|
+
role={decorative ? 'none' : 'separator'}
|
|
84
|
+
accessible={!decorative}
|
|
85
|
+
aria-orientation={decorative ? undefined : orientation}
|
|
86
|
+
className={separatorVariants({ orientation, variant, className })}
|
|
87
|
+
style={
|
|
88
|
+
thickness !== undefined
|
|
89
|
+
? [orientation === 'horizontal' ? { height: thickness } : { width: thickness }, style]
|
|
90
|
+
: style
|
|
91
|
+
}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
Separator.displayName = 'Separator';
|
|
@@ -23,7 +23,7 @@ import { StyleSheet, View, type ViewProps } from 'react-native';
|
|
|
23
23
|
import { tv, type VariantProps } from 'tailwind-variants';
|
|
24
24
|
|
|
25
25
|
const surfaceVariants = tv({
|
|
26
|
-
base: 'overflow-hidden rounded-3xl
|
|
26
|
+
base: 'overflow-hidden rounded-3xl',
|
|
27
27
|
variants: {
|
|
28
28
|
variant: {
|
|
29
29
|
default: 'bg-surface',
|
|
@@ -31,9 +31,22 @@ const surfaceVariants = tv({
|
|
|
31
31
|
tertiary: 'bg-surface-tertiary',
|
|
32
32
|
transparent: 'bg-transparent',
|
|
33
33
|
},
|
|
34
|
+
padding: {
|
|
35
|
+
none: '',
|
|
36
|
+
sm: 'p-2.5',
|
|
37
|
+
default: 'p-4',
|
|
38
|
+
lg: 'p-6',
|
|
39
|
+
},
|
|
40
|
+
bordered: {
|
|
41
|
+
true: 'border border-border',
|
|
42
|
+
},
|
|
43
|
+
elevated: {
|
|
44
|
+
true: 'shadow-sm',
|
|
45
|
+
},
|
|
34
46
|
},
|
|
35
47
|
defaultVariants: {
|
|
36
48
|
variant: 'default',
|
|
49
|
+
padding: 'default',
|
|
37
50
|
},
|
|
38
51
|
});
|
|
39
52
|
|
|
@@ -41,17 +54,30 @@ export interface SurfaceProps
|
|
|
41
54
|
extends ViewProps,
|
|
42
55
|
VariantProps<typeof surfaceVariants> {
|
|
43
56
|
className?: string;
|
|
57
|
+
/**
|
|
58
|
+
* A hairline border. A surface the same colour as what it sits on needs one
|
|
59
|
+
* to read as a distinct plane rather than dissolving into the background.
|
|
60
|
+
*/
|
|
61
|
+
bordered?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* A soft shadow lifting the surface off the page. Off by default because a
|
|
64
|
+
* nested surface reads its depth from its fill, not from a shadow it would
|
|
65
|
+
* only cast onto its parent.
|
|
66
|
+
*/
|
|
67
|
+
elevated?: boolean;
|
|
68
|
+
/** Inner spacing. `none` is for a surface wrapping a bled image or a chart. */
|
|
69
|
+
padding?: 'none' | 'sm' | 'default' | 'lg';
|
|
44
70
|
}
|
|
45
71
|
|
|
46
72
|
export const Surface = forwardRef<View, SurfaceProps>(
|
|
47
|
-
({ className, variant, style, ...props }, ref) => (
|
|
73
|
+
({ className, variant, padding, bordered, elevated, style, ...props }, ref) => (
|
|
48
74
|
<View
|
|
49
75
|
ref={ref}
|
|
50
76
|
// `borderCurve` has no Tailwind equivalent. On iOS it gives the
|
|
51
77
|
// continuous (squircle) corner Apple uses, which is visibly smoother
|
|
52
78
|
// than a circular arc at this radius; Android ignores it.
|
|
53
79
|
style={[styles.root, style]}
|
|
54
|
-
className={surfaceVariants({ variant, className })}
|
|
80
|
+
className={surfaceVariants({ variant, padding, bordered, elevated, className })}
|
|
55
81
|
{...props}
|
|
56
82
|
/>
|
|
57
83
|
)
|
package/src/icons/index.tsx
CHANGED
|
@@ -462,3 +462,51 @@ export function SendIcon({ size = 16, color, ...props }: IconProps) {
|
|
|
462
462
|
</Svg>
|
|
463
463
|
);
|
|
464
464
|
}
|
|
465
|
+
|
|
466
|
+
/** Document with a folded corner — a file attachment. */
|
|
467
|
+
export function FileIcon({ size = 16, color, ...props }: IconProps) {
|
|
468
|
+
const resolved = useResolvedColor(color, '#737373');
|
|
469
|
+
return (
|
|
470
|
+
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
|
|
471
|
+
<Path
|
|
472
|
+
d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8zM14 3v5h5"
|
|
473
|
+
stroke={resolved}
|
|
474
|
+
strokeWidth={2}
|
|
475
|
+
strokeLinecap="round"
|
|
476
|
+
strokeLinejoin="round"
|
|
477
|
+
/>
|
|
478
|
+
</Svg>
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** Framed picture with a sun and a corner peak — an image attachment. */
|
|
483
|
+
export function ImageIcon({ size = 16, color, ...props }: IconProps) {
|
|
484
|
+
const resolved = useResolvedColor(color, '#737373');
|
|
485
|
+
return (
|
|
486
|
+
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
|
|
487
|
+
<Path
|
|
488
|
+
d="M4 4h16v16H4z M8.5 10a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zM20 15l-4.5-4.5L6 20"
|
|
489
|
+
stroke={resolved}
|
|
490
|
+
strokeWidth={2}
|
|
491
|
+
strokeLinecap="round"
|
|
492
|
+
strokeLinejoin="round"
|
|
493
|
+
/>
|
|
494
|
+
</Svg>
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/** A paperclip — attaching a file to a composer. */
|
|
499
|
+
export function PaperclipIcon({ size = 16, color, ...props }: IconProps) {
|
|
500
|
+
const resolved = useResolvedColor(color, '#737373');
|
|
501
|
+
return (
|
|
502
|
+
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
|
|
503
|
+
<Path
|
|
504
|
+
d="M21 8.5 11.3 18.2a4 4 0 0 1-5.6-5.6l9-9a2.5 2.5 0 0 1 3.6 3.6l-9 9a1 1 0 0 1-1.5-1.5l8.3-8.3"
|
|
505
|
+
stroke={resolved}
|
|
506
|
+
strokeWidth={2}
|
|
507
|
+
strokeLinecap="round"
|
|
508
|
+
strokeLinejoin="round"
|
|
509
|
+
/>
|
|
510
|
+
</Svg>
|
|
511
|
+
);
|
|
512
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -50,6 +50,18 @@ export {
|
|
|
50
50
|
type AvatarProps,
|
|
51
51
|
type AvatarBadgeProps,
|
|
52
52
|
} from './components/avatar';
|
|
53
|
+
export {
|
|
54
|
+
Attachment,
|
|
55
|
+
type AttachmentProps,
|
|
56
|
+
type AttachmentGroupProps,
|
|
57
|
+
type AttachmentMediaProps,
|
|
58
|
+
type AttachmentContentProps,
|
|
59
|
+
type AttachmentTitleProps,
|
|
60
|
+
type AttachmentDescriptionProps,
|
|
61
|
+
type AttachmentActionsProps,
|
|
62
|
+
type AttachmentActionProps,
|
|
63
|
+
type AttachmentState,
|
|
64
|
+
} from './components/attachment';
|
|
53
65
|
export { Badge, type BadgeProps } from './components/badge';
|
|
54
66
|
export {
|
|
55
67
|
BottomSheet,
|
|
@@ -104,6 +116,39 @@ export {
|
|
|
104
116
|
type ItemFooterProps,
|
|
105
117
|
} from './components/item';
|
|
106
118
|
export { Label, type LabelProps, type LabelTextProps } from './components/label';
|
|
119
|
+
export {
|
|
120
|
+
LineChart,
|
|
121
|
+
useLineChart,
|
|
122
|
+
type LineChartProps,
|
|
123
|
+
type LineChartHandle,
|
|
124
|
+
type LineChartGridProps,
|
|
125
|
+
type LineChartLineProps,
|
|
126
|
+
type LineChartAreaProps,
|
|
127
|
+
type LineChartSkeletonProps,
|
|
128
|
+
type LineChartXAxisProps,
|
|
129
|
+
type LineChartTooltipProps,
|
|
130
|
+
type LineChartLegendProps,
|
|
131
|
+
type LineChartDatum,
|
|
132
|
+
type LineChartStatus,
|
|
133
|
+
type LineChartCurve,
|
|
134
|
+
} from './components/line-chart';
|
|
135
|
+
export {
|
|
136
|
+
Marker,
|
|
137
|
+
type MarkerProps,
|
|
138
|
+
type MarkerIconProps,
|
|
139
|
+
type MarkerContentProps,
|
|
140
|
+
} from './components/marker';
|
|
141
|
+
export {
|
|
142
|
+
MessageScroller,
|
|
143
|
+
useMessageScroller,
|
|
144
|
+
useMessageScrollerVisibility,
|
|
145
|
+
type MessageScrollerProps,
|
|
146
|
+
type MessageScrollerViewportProps,
|
|
147
|
+
type MessageScrollerContentProps,
|
|
148
|
+
type MessageScrollerItemProps,
|
|
149
|
+
type MessageScrollerButtonProps,
|
|
150
|
+
type MessageScrollerPosition,
|
|
151
|
+
} from './components/message-scroller';
|
|
107
152
|
export {
|
|
108
153
|
Message,
|
|
109
154
|
type MessageProps,
|
|
@@ -116,6 +161,17 @@ export {
|
|
|
116
161
|
type MessageFooterProps,
|
|
117
162
|
type MessageActionsProps,
|
|
118
163
|
} from './components/message';
|
|
164
|
+
export {
|
|
165
|
+
Popover,
|
|
166
|
+
type PopoverProps,
|
|
167
|
+
type PopoverTriggerProps,
|
|
168
|
+
type PopoverContentProps,
|
|
169
|
+
type PopoverArrowProps,
|
|
170
|
+
type PopoverCloseProps,
|
|
171
|
+
type PopoverPlacement,
|
|
172
|
+
type PopoverAlign,
|
|
173
|
+
type PopoverPresentation,
|
|
174
|
+
} from './components/popover';
|
|
119
175
|
export { Progress, type ProgressProps } from './components/progress';
|
|
120
176
|
export {
|
|
121
177
|
RadioGroup,
|
|
@@ -126,6 +182,7 @@ export {
|
|
|
126
182
|
ScrollFade,
|
|
127
183
|
type ScrollFadeProps,
|
|
128
184
|
} from './components/scroll-fade';
|
|
185
|
+
export { Separator, type SeparatorProps } from './components/separator';
|
|
129
186
|
export { Shimmer, type ShimmerProps } from './components/shimmer';
|
|
130
187
|
export { Skeleton, type SkeletonProps } from './components/skeleton';
|
|
131
188
|
export { Spinner, type SpinnerProps } from './components/spinner';
|
|
@@ -186,11 +243,14 @@ export {
|
|
|
186
243
|
ChevronLeftIcon,
|
|
187
244
|
ChevronRightIcon,
|
|
188
245
|
FacebookIcon,
|
|
246
|
+
FileIcon,
|
|
189
247
|
GoogleIcon,
|
|
190
248
|
IconColorProvider,
|
|
191
249
|
InfoIcon,
|
|
250
|
+
ImageIcon,
|
|
192
251
|
MoonIcon,
|
|
193
252
|
PackageIcon,
|
|
253
|
+
PaperclipIcon,
|
|
194
254
|
PlusSquareIcon,
|
|
195
255
|
ReceiptIcon,
|
|
196
256
|
SearchIcon,
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrim — the layer behind an overlay.
|
|
3
|
+
*
|
|
4
|
+
* An overlay needs the screen behind it to recede. There are two honest ways to
|
|
5
|
+
* do that: dim it, or blur it. Dimming is free and always works; blurring reads
|
|
6
|
+
* as more physical — the content behind stays legible as shape and colour while
|
|
7
|
+
* losing its detail — but it needs a native view to do it.
|
|
8
|
+
*
|
|
9
|
+
* `expo-blur` is resolved lazily and only when `blur` is asked for, so a project
|
|
10
|
+
* that never blurs anything installs nothing. When it is asked for and the
|
|
11
|
+
* package is missing, this falls back to a dim rather than failing: a blur you
|
|
12
|
+
* cannot draw is better shown as a darkened screen than as a crash.
|
|
13
|
+
*
|
|
14
|
+
* It fills its parent and does not intercept touches itself — the overlay layers
|
|
15
|
+
* its own dismiss `Pressable` over it — so it is purely the visual backdrop.
|
|
16
|
+
*/
|
|
17
|
+
import { type ComponentType } from 'react';
|
|
18
|
+
import { StyleSheet, View, type ViewProps } from 'react-native';
|
|
19
|
+
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
|
|
20
|
+
|
|
21
|
+
interface BlurViewProps {
|
|
22
|
+
intensity?: number;
|
|
23
|
+
tint?: 'light' | 'dark' | 'default' | 'systemMaterial';
|
|
24
|
+
style?: unknown;
|
|
25
|
+
children?: React.ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `expo-blur`'s BlurView, or null when it is not installed. Resolved once at
|
|
30
|
+
* module load — the require is cheap and caching it avoids a try/catch on every
|
|
31
|
+
* render.
|
|
32
|
+
*/
|
|
33
|
+
const BlurView: ComponentType<BlurViewProps> | null = (() => {
|
|
34
|
+
try {
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
36
|
+
const mod = require('expo-blur');
|
|
37
|
+
return (mod?.BlurView as ComponentType<BlurViewProps>) ?? null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
|
|
43
|
+
/** True when a real blur can be drawn — for a caller that wants to know. */
|
|
44
|
+
export const hasBlur = BlurView !== null;
|
|
45
|
+
|
|
46
|
+
export interface ScrimProps extends Omit<ViewProps, 'children'> {
|
|
47
|
+
/** Frost the backdrop instead of dimming it. Falls back to a dim if it can't. */
|
|
48
|
+
blur?: boolean;
|
|
49
|
+
/** Blur strength, 0–100. */
|
|
50
|
+
intensity?: number;
|
|
51
|
+
/** Which way the blur tints. */
|
|
52
|
+
tint?: 'light' | 'dark' | 'default' | 'systemMaterial';
|
|
53
|
+
/**
|
|
54
|
+
* The dim used when not blurring — and when blurring is asked for but
|
|
55
|
+
* unavailable. A popover passes a lighter one than a dialog.
|
|
56
|
+
*/
|
|
57
|
+
dimClassName?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function Scrim({
|
|
61
|
+
blur = false,
|
|
62
|
+
intensity = 24,
|
|
63
|
+
tint = 'default',
|
|
64
|
+
dimClassName = 'bg-black/50',
|
|
65
|
+
style,
|
|
66
|
+
...props
|
|
67
|
+
}: ScrimProps) {
|
|
68
|
+
if (blur && BlurView) {
|
|
69
|
+
return (
|
|
70
|
+
<Animated.View
|
|
71
|
+
entering={FadeIn.duration(180)}
|
|
72
|
+
exiting={FadeOut.duration(150)}
|
|
73
|
+
style={[StyleSheet.absoluteFill, style]}
|
|
74
|
+
{...props}
|
|
75
|
+
>
|
|
76
|
+
{/* A faint dim under the blur so the frost has something to sit on —
|
|
77
|
+
a pure blur over a dark scene is nearly invisible. */}
|
|
78
|
+
<BlurView intensity={intensity} tint={tint} style={StyleSheet.absoluteFill} />
|
|
79
|
+
<View className="absolute inset-0 bg-black/10" />
|
|
80
|
+
</Animated.View>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Animated.View
|
|
86
|
+
entering={FadeIn.duration(150)}
|
|
87
|
+
exiting={FadeOut.duration(150)}
|
|
88
|
+
style={[StyleSheet.absoluteFill, style]}
|
|
89
|
+
className={dimClassName}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Scrim.displayName = 'Scrim';
|
package/theme.css
CHANGED
|
@@ -67,6 +67,22 @@
|
|
|
67
67
|
--color-destructive-soft: unset;
|
|
68
68
|
--color-destructive-subtle: unset;
|
|
69
69
|
|
|
70
|
+
/*
|
|
71
|
+
* Chart series. A separate ramp from the status colours because they answer
|
|
72
|
+
* a different question: a status colour means something (this failed, this
|
|
73
|
+
* is fine), a series colour only has to be told apart from the other four.
|
|
74
|
+
* Ordered by prominence — chart-1 is the series the chart is about, and each
|
|
75
|
+
* theme starts it on that family's own accent.
|
|
76
|
+
*
|
|
77
|
+
* These are the tokens to override in your app's global.css, after the
|
|
78
|
+
* `panelui-native/theme.css` import, to put a chart on brand.
|
|
79
|
+
*/
|
|
80
|
+
--color-chart-1: unset;
|
|
81
|
+
--color-chart-2: unset;
|
|
82
|
+
--color-chart-3: unset;
|
|
83
|
+
--color-chart-4: unset;
|
|
84
|
+
--color-chart-5: unset;
|
|
85
|
+
|
|
70
86
|
/*
|
|
71
87
|
* Radius scale. Themed rather than static: a theme is a shape as well as a
|
|
72
88
|
* palette, so each family sets its own corner rounding. --radius-3xl is
|
|
@@ -143,6 +159,13 @@
|
|
|
143
159
|
--color-warning-subtle: rgba(245, 158, 11, 0.08);
|
|
144
160
|
--color-destructive-soft: rgba(239, 68, 68, 0.1);
|
|
145
161
|
--color-destructive-subtle: rgba(239, 68, 68, 0.08);
|
|
162
|
+
|
|
163
|
+
/* chart series, in order of prominence */
|
|
164
|
+
--color-chart-1: #262626;
|
|
165
|
+
--color-chart-2: #3b82f6;
|
|
166
|
+
--color-chart-3: #10b981;
|
|
167
|
+
--color-chart-4: #f59e0b;
|
|
168
|
+
--color-chart-5: #8b5cf6;
|
|
146
169
|
|
|
147
170
|
/* Panel shape scale */
|
|
148
171
|
--radius-xs: 4px;
|
|
@@ -216,6 +239,13 @@
|
|
|
216
239
|
--color-warning-subtle: rgba(245, 158, 11, 0.16);
|
|
217
240
|
--color-destructive-soft: rgba(241, 87, 87, 0.06);
|
|
218
241
|
--color-destructive-subtle: rgba(241, 87, 87, 0.16);
|
|
242
|
+
|
|
243
|
+
/* chart series, in order of prominence */
|
|
244
|
+
--color-chart-1: #fafafa;
|
|
245
|
+
--color-chart-2: #60a5fa;
|
|
246
|
+
--color-chart-3: #34d399;
|
|
247
|
+
--color-chart-4: #fbbf24;
|
|
248
|
+
--color-chart-5: #a78bfa;
|
|
219
249
|
|
|
220
250
|
/* Panel shape scale */
|
|
221
251
|
--radius-xs: 4px;
|
|
@@ -298,6 +328,13 @@
|
|
|
298
328
|
--color-warning-subtle: rgba(245, 166, 35, 0.08);
|
|
299
329
|
--color-destructive-soft: rgba(229, 72, 77, 0.1);
|
|
300
330
|
--color-destructive-subtle: rgba(229, 72, 77, 0.08);
|
|
331
|
+
|
|
332
|
+
/* chart series, in order of prominence */
|
|
333
|
+
--color-chart-1: #0070f3;
|
|
334
|
+
--color-chart-2: #171717;
|
|
335
|
+
--color-chart-3: #7928ca;
|
|
336
|
+
--color-chart-4: #f5a623;
|
|
337
|
+
--color-chart-5: #0a9396;
|
|
301
338
|
|
|
302
339
|
/* Moon shape scale */
|
|
303
340
|
--radius-xs: 2px;
|
|
@@ -360,6 +397,13 @@
|
|
|
360
397
|
--color-warning-subtle: rgba(245, 166, 35, 0.16);
|
|
361
398
|
--color-destructive-soft: rgba(229, 72, 77, 0.1);
|
|
362
399
|
--color-destructive-subtle: rgba(229, 72, 77, 0.16);
|
|
400
|
+
|
|
401
|
+
/* chart series, in order of prominence */
|
|
402
|
+
--color-chart-1: #3291ff;
|
|
403
|
+
--color-chart-2: #ededed;
|
|
404
|
+
--color-chart-3: #a855f7;
|
|
405
|
+
--color-chart-4: #fbbf24;
|
|
406
|
+
--color-chart-5: #2dd4bf;
|
|
363
407
|
|
|
364
408
|
/* Moon shape scale */
|
|
365
409
|
--radius-xs: 2px;
|
|
@@ -425,6 +469,13 @@
|
|
|
425
469
|
--color-warning-subtle: rgba(245, 158, 11, 0.08);
|
|
426
470
|
--color-destructive-soft: rgba(239, 68, 68, 0.1);
|
|
427
471
|
--color-destructive-subtle: rgba(239, 68, 68, 0.08);
|
|
472
|
+
|
|
473
|
+
/* chart series, in order of prominence */
|
|
474
|
+
--color-chart-1: #24b47e;
|
|
475
|
+
--color-chart-2: #1f2937;
|
|
476
|
+
--color-chart-3: #2563eb;
|
|
477
|
+
--color-chart-4: #f59e0b;
|
|
478
|
+
--color-chart-5: #db2777;
|
|
428
479
|
|
|
429
480
|
/* Grass shape scale */
|
|
430
481
|
--radius-xs: 6px;
|
|
@@ -487,6 +538,13 @@
|
|
|
487
538
|
--color-warning-subtle: rgba(245, 158, 11, 0.16);
|
|
488
539
|
--color-destructive-soft: rgba(241, 87, 87, 0.08);
|
|
489
540
|
--color-destructive-subtle: rgba(241, 87, 87, 0.16);
|
|
541
|
+
|
|
542
|
+
/* chart series, in order of prominence */
|
|
543
|
+
--color-chart-1: #3ecf8e;
|
|
544
|
+
--color-chart-2: #ededed;
|
|
545
|
+
--color-chart-3: #60a5fa;
|
|
546
|
+
--color-chart-4: #fbbf24;
|
|
547
|
+
--color-chart-5: #f472b6;
|
|
490
548
|
|
|
491
549
|
/* Grass shape scale */
|
|
492
550
|
--radius-xs: 6px;
|