panelui-native 0.14.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/lib/module/components/bottom-sheet/index.js +38 -10
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/checkbox/index.js +24 -11
- package/lib/module/components/checkbox/index.js.map +1 -1
- package/lib/module/components/direction/index.js +86 -0
- package/lib/module/components/direction/index.js.map +1 -0
- package/lib/module/components/field/index.js +337 -0
- package/lib/module/components/field/index.js.map +1 -0
- package/lib/module/components/flow/flow-paths.js +378 -0
- package/lib/module/components/flow/flow-paths.js.map +1 -0
- package/lib/module/components/flow/index.js +1598 -0
- package/lib/module/components/flow/index.js.map +1 -0
- package/lib/module/components/form/index.js +77 -0
- package/lib/module/components/form/index.js.map +1 -0
- package/lib/module/components/form/use-field.js +50 -0
- package/lib/module/components/form/use-field.js.map +1 -0
- package/lib/module/components/form/use-form.js +217 -0
- package/lib/module/components/form/use-form.js.map +1 -0
- package/lib/module/components/frame/index.js +124 -22
- package/lib/module/components/frame/index.js.map +1 -1
- package/lib/module/components/message/index.js +16 -1
- package/lib/module/components/message/index.js.map +1 -1
- package/lib/module/components/otp-input/index.js +303 -0
- package/lib/module/components/otp-input/index.js.map +1 -0
- package/lib/module/components/signature/index.js +500 -0
- package/lib/module/components/signature/index.js.map +1 -0
- package/lib/module/components/soundwave/index.js +813 -0
- package/lib/module/components/soundwave/index.js.map +1 -0
- package/lib/module/components/typography/index.js +196 -5
- package/lib/module/components/typography/index.js.map +1 -1
- package/lib/module/icons/index.js +287 -0
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +8 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +15 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/checkbox/index.d.ts +8 -0
- package/lib/typescript/src/components/checkbox/index.d.ts.map +1 -1
- package/lib/typescript/src/components/direction/index.d.ts +64 -0
- package/lib/typescript/src/components/direction/index.d.ts.map +1 -0
- package/lib/typescript/src/components/field/index.d.ts +94 -0
- package/lib/typescript/src/components/field/index.d.ts.map +1 -0
- package/lib/typescript/src/components/flow/flow-paths.d.ts +89 -0
- package/lib/typescript/src/components/flow/flow-paths.d.ts.map +1 -0
- package/lib/typescript/src/components/flow/index.d.ts +310 -0
- package/lib/typescript/src/components/flow/index.d.ts.map +1 -0
- package/lib/typescript/src/components/form/index.d.ts +64 -0
- package/lib/typescript/src/components/form/index.d.ts.map +1 -0
- package/lib/typescript/src/components/form/use-field.d.ts +16 -0
- package/lib/typescript/src/components/form/use-field.d.ts.map +1 -0
- package/lib/typescript/src/components/form/use-form.d.ts +37 -0
- package/lib/typescript/src/components/form/use-form.d.ts.map +1 -0
- package/lib/typescript/src/components/frame/index.d.ts +39 -2
- package/lib/typescript/src/components/frame/index.d.ts.map +1 -1
- package/lib/typescript/src/components/message/index.d.ts.map +1 -1
- package/lib/typescript/src/components/otp-input/index.d.ts +141 -0
- package/lib/typescript/src/components/otp-input/index.d.ts.map +1 -0
- package/lib/typescript/src/components/signature/index.d.ts +254 -0
- package/lib/typescript/src/components/signature/index.d.ts.map +1 -0
- package/lib/typescript/src/components/soundwave/index.d.ts +91 -0
- package/lib/typescript/src/components/soundwave/index.d.ts.map +1 -0
- package/lib/typescript/src/components/typography/index.d.ts +154 -1
- package/lib/typescript/src/components/typography/index.d.ts.map +1 -1
- package/lib/typescript/src/icons/index.d.ts +23 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +9 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +9 -1
- package/src/components/bottom-sheet/index.tsx +50 -12
- package/src/components/checkbox/index.tsx +40 -12
- package/src/components/direction/index.tsx +90 -0
- package/src/components/field/index.tsx +329 -0
- package/src/components/flow/flow-paths.ts +348 -0
- package/src/components/flow/index.tsx +1924 -0
- package/src/components/form/index.tsx +85 -0
- package/src/components/form/use-field.ts +65 -0
- package/src/components/form/use-form.ts +252 -0
- package/src/components/frame/index.tsx +162 -14
- package/src/components/message/index.tsx +17 -1
- package/src/components/otp-input/index.tsx +396 -0
- package/src/components/signature/index.tsx +681 -0
- package/src/components/soundwave/index.tsx +1029 -0
- package/src/components/typography/index.tsx +231 -10
- package/src/icons/index.tsx +195 -0
- package/src/index.ts +87 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direction — reading direction for everything below it.
|
|
3
|
+
*
|
|
4
|
+
* ```tsx
|
|
5
|
+
* <Direction dir="rtl">
|
|
6
|
+
* <App />
|
|
7
|
+
* </Direction>
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* The layout flip is Yoga's, not ours: the wrapper carries the `direction`
|
|
11
|
+
* style, and every row, `start`/`end` inset and horizontal padding underneath
|
|
12
|
+
* it mirrors natively. That is the whole reason this is a component and not a
|
|
13
|
+
* bare context — `direction` is a *style*, so a subtree is the unit it applies
|
|
14
|
+
* to.
|
|
15
|
+
*
|
|
16
|
+
* It is also the reason to prefer it over flipping the process with
|
|
17
|
+
* `I18nManager.forceRTL`, which needs an app restart to take effect, cannot be
|
|
18
|
+
* scoped to part of a screen, and cannot be previewed side by side. Here the
|
|
19
|
+
* value is a prop: change it and the next frame is mirrored.
|
|
20
|
+
*
|
|
21
|
+
* ## What Yoga does not flip
|
|
22
|
+
*
|
|
23
|
+
* Anything measured in raw pixels rather than laid out — a drag translation, an
|
|
24
|
+
* indicator offset, the direction a sweep travels. Those have to consult the
|
|
25
|
+
* value and negate it themselves, which is what `useDirection()` is for:
|
|
26
|
+
*
|
|
27
|
+
* ```tsx
|
|
28
|
+
* const dir = useDirection();
|
|
29
|
+
* const delta = (dir === 'rtl' ? -event.translationX : event.translationX) / width;
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* With no provider mounted, and for a `<Direction>` given no `dir`, the value
|
|
33
|
+
* comes from the device — so an app that did force RTL the old way still reads
|
|
34
|
+
* back the truth.
|
|
35
|
+
*/
|
|
36
|
+
import { createContext, useContext, useMemo, type ReactNode } from 'react';
|
|
37
|
+
import { I18nManager, View, type ViewProps } from 'react-native';
|
|
38
|
+
import { cn } from '../../utils/cn';
|
|
39
|
+
|
|
40
|
+
export type DirectionValue = 'ltr' | 'rtl';
|
|
41
|
+
|
|
42
|
+
/** What the device is set to, for anything outside a provider. */
|
|
43
|
+
const deviceDirection = (): DirectionValue => (I18nManager.isRTL ? 'rtl' : 'ltr');
|
|
44
|
+
|
|
45
|
+
const DirectionContext = createContext<DirectionValue | null>(null);
|
|
46
|
+
|
|
47
|
+
export interface DirectionProps extends ViewProps {
|
|
48
|
+
className?: string;
|
|
49
|
+
children?: ReactNode;
|
|
50
|
+
/**
|
|
51
|
+
* Reading direction for this subtree. Defaults to the nearest enclosing
|
|
52
|
+
* `Direction`, or to the device when there is none — so a nested provider
|
|
53
|
+
* with no `dir` inherits rather than resetting to left-to-right.
|
|
54
|
+
*/
|
|
55
|
+
dir?: DirectionValue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Provider. Renders one `View` — it has to, since the flip is a style — which
|
|
60
|
+
* fills its parent by default so it can wrap an app. Pass `className` to change
|
|
61
|
+
* that: `flex-none` or a width class both win over the default.
|
|
62
|
+
*/
|
|
63
|
+
export function Direction({ className, children, dir, style, ...props }: DirectionProps) {
|
|
64
|
+
const inherited = useContext(DirectionContext);
|
|
65
|
+
const value = dir ?? inherited ?? deviceDirection();
|
|
66
|
+
|
|
67
|
+
// The style object is memoised because it is the thing that invalidates
|
|
68
|
+
// layout for the entire subtree — a fresh one each render would re-run Yoga
|
|
69
|
+
// on every parent render, for a value that almost never changes.
|
|
70
|
+
const directionStyle = useMemo(() => ({ direction: value }) as const, [value]);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<DirectionContext.Provider value={value}>
|
|
74
|
+
<View className={cn('flex-1', className)} style={[directionStyle, style]} {...props}>
|
|
75
|
+
{children}
|
|
76
|
+
</View>
|
|
77
|
+
</DirectionContext.Provider>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
Direction.displayName = 'Direction';
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The reading direction in force, for a component that has to flip maths Yoga
|
|
85
|
+
* cannot flip for it. Safe with no provider mounted: it falls back to the
|
|
86
|
+
* device's own setting.
|
|
87
|
+
*/
|
|
88
|
+
export function useDirection(): DirectionValue {
|
|
89
|
+
return useContext(DirectionContext) ?? deviceDirection();
|
|
90
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field — the layout and validation-state kit a form control composes into.
|
|
3
|
+
*
|
|
4
|
+
* PanelUI's own controls (`Input`, `Checkbox`, `Switch`, `RadioGroup`) already
|
|
5
|
+
* carry their own label, description and error slot, so the common case needs
|
|
6
|
+
* no wrapper at all — pass those props straight through. `Field` exists for
|
|
7
|
+
* what doesn't fit that shape: a horizontal row pairing a control with a
|
|
8
|
+
* label off to the side (a `Switch` in a settings list), several controls
|
|
9
|
+
* grouped under one legend, or a rule breaking a long form into sections.
|
|
10
|
+
*
|
|
11
|
+
* `invalid` / `disabled` / `required` set on the root flow to `Field.Label`
|
|
12
|
+
* and `Field.Description` through context, so a multi-field row states its
|
|
13
|
+
* validity once instead of repeating the prop at every leaf.
|
|
14
|
+
*
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <Field orientation="horizontal">
|
|
17
|
+
* <Field.Content>
|
|
18
|
+
* <Field.Title>Marketing emails</Field.Title>
|
|
19
|
+
* <Field.Description>Product updates, at most weekly.</Field.Description>
|
|
20
|
+
* </Field.Content>
|
|
21
|
+
* <Switch value={subscribed} onValueChange={setSubscribed} />
|
|
22
|
+
* </Field>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import {
|
|
26
|
+
createContext,
|
|
27
|
+
forwardRef,
|
|
28
|
+
useContext,
|
|
29
|
+
useMemo,
|
|
30
|
+
type ReactNode,
|
|
31
|
+
} from 'react';
|
|
32
|
+
import { View, type Text as RNText, type ViewProps } from 'react-native';
|
|
33
|
+
import { tv } from 'tailwind-variants';
|
|
34
|
+
import { Text, type TextProps } from '../../primitives/text';
|
|
35
|
+
import { Label, type LabelProps } from '../label';
|
|
36
|
+
import { Separator } from '../separator';
|
|
37
|
+
|
|
38
|
+
const fieldVariants = tv({
|
|
39
|
+
slots: {
|
|
40
|
+
root: 'gap-2',
|
|
41
|
+
content: 'flex-1 flex-col gap-0.5',
|
|
42
|
+
description: 'text-sm text-muted-foreground',
|
|
43
|
+
error: 'gap-1',
|
|
44
|
+
errorText: 'text-sm text-destructive',
|
|
45
|
+
bullet: 'flex-row gap-1.5',
|
|
46
|
+
bulletDot: 'text-sm text-destructive',
|
|
47
|
+
set: 'gap-4',
|
|
48
|
+
legend: 'text-foreground',
|
|
49
|
+
group: 'gap-6',
|
|
50
|
+
separator: 'flex-row items-center gap-3',
|
|
51
|
+
separatorLabel: 'text-xs text-muted-foreground',
|
|
52
|
+
title: 'text-base font-medium text-foreground',
|
|
53
|
+
},
|
|
54
|
+
variants: {
|
|
55
|
+
orientation: {
|
|
56
|
+
vertical: { root: 'flex-col' },
|
|
57
|
+
horizontal: { root: 'flex-row items-center justify-between gap-4' },
|
|
58
|
+
},
|
|
59
|
+
disabled: {
|
|
60
|
+
true: { root: 'opacity-[0.64]' },
|
|
61
|
+
},
|
|
62
|
+
legendVariant: {
|
|
63
|
+
legend: { legend: 'text-base font-semibold' },
|
|
64
|
+
label: { legend: 'text-sm font-medium' },
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
defaultVariants: {
|
|
68
|
+
orientation: 'vertical',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
interface FieldState {
|
|
73
|
+
invalid: boolean;
|
|
74
|
+
disabled: boolean;
|
|
75
|
+
required: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const FieldContext = createContext<FieldState>({
|
|
79
|
+
invalid: false,
|
|
80
|
+
disabled: false,
|
|
81
|
+
required: false,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export interface FieldProps extends ViewProps {
|
|
85
|
+
className?: string;
|
|
86
|
+
/** `horizontal` puts a label beside the control instead of above it. */
|
|
87
|
+
orientation?: 'vertical' | 'horizontal';
|
|
88
|
+
/** Marks every `Field.Label`/`Field.Description` below as invalid. */
|
|
89
|
+
invalid?: boolean;
|
|
90
|
+
disabled?: boolean;
|
|
91
|
+
/** Marks every `Field.Label` below as required, same as `Label`'s own prop. */
|
|
92
|
+
required?: boolean;
|
|
93
|
+
children?: ReactNode;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const FieldRoot = forwardRef<View, FieldProps>(
|
|
97
|
+
(
|
|
98
|
+
{
|
|
99
|
+
className,
|
|
100
|
+
orientation = 'vertical',
|
|
101
|
+
invalid = false,
|
|
102
|
+
disabled = false,
|
|
103
|
+
required = false,
|
|
104
|
+
children,
|
|
105
|
+
...props
|
|
106
|
+
},
|
|
107
|
+
ref
|
|
108
|
+
) => {
|
|
109
|
+
const { root } = fieldVariants({ orientation, disabled });
|
|
110
|
+
const state = useMemo(
|
|
111
|
+
() => ({ invalid, disabled, required }),
|
|
112
|
+
[invalid, disabled, required]
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<FieldContext.Provider value={state}>
|
|
117
|
+
<View
|
|
118
|
+
ref={ref}
|
|
119
|
+
role="group"
|
|
120
|
+
accessibilityState={{ disabled }}
|
|
121
|
+
className={root({ className })}
|
|
122
|
+
{...props}
|
|
123
|
+
>
|
|
124
|
+
{children}
|
|
125
|
+
</View>
|
|
126
|
+
</FieldContext.Provider>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
FieldRoot.displayName = 'Field';
|
|
131
|
+
|
|
132
|
+
export interface FieldContentProps extends ViewProps {
|
|
133
|
+
className?: string;
|
|
134
|
+
children?: ReactNode;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const FieldContent = forwardRef<View, FieldContentProps>(
|
|
138
|
+
({ className, ...props }, ref) => {
|
|
139
|
+
const { content } = fieldVariants();
|
|
140
|
+
return <View ref={ref} className={content({ className })} {...props} />;
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
FieldContent.displayName = 'Field.Content';
|
|
144
|
+
|
|
145
|
+
export interface FieldLabelProps extends LabelProps {}
|
|
146
|
+
|
|
147
|
+
const FieldLabel = forwardRef<View, FieldLabelProps>(
|
|
148
|
+
({ isRequired, isInvalid, isDisabled, ...props }, ref) => {
|
|
149
|
+
const ctx = useContext(FieldContext);
|
|
150
|
+
return (
|
|
151
|
+
<Label
|
|
152
|
+
ref={ref}
|
|
153
|
+
isRequired={isRequired ?? ctx.required}
|
|
154
|
+
isInvalid={isInvalid ?? ctx.invalid}
|
|
155
|
+
isDisabled={isDisabled ?? ctx.disabled}
|
|
156
|
+
{...props}
|
|
157
|
+
/>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
FieldLabel.displayName = 'Field.Label';
|
|
162
|
+
|
|
163
|
+
export interface FieldDescriptionProps extends TextProps {
|
|
164
|
+
className?: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const FieldDescription = forwardRef<RNText, FieldDescriptionProps>(
|
|
168
|
+
({ className, ...props }, ref) => {
|
|
169
|
+
const { disabled } = useContext(FieldContext);
|
|
170
|
+
const { description } = fieldVariants({ disabled });
|
|
171
|
+
return <Text ref={ref} className={description({ className })} {...props} />;
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
FieldDescription.displayName = 'Field.Description';
|
|
175
|
+
|
|
176
|
+
export interface FieldErrorProps extends Omit<ViewProps, 'children'> {
|
|
177
|
+
className?: string;
|
|
178
|
+
/**
|
|
179
|
+
* Error messages to render, deduplicated by message. A single entry renders
|
|
180
|
+
* as plain text; more than one renders as a bulleted list, since RN has no
|
|
181
|
+
* `<ul>`. Prefer this over `children` when the messages come from a form's
|
|
182
|
+
* validation state, which is naturally an array.
|
|
183
|
+
*/
|
|
184
|
+
errors?: Array<string | { message?: string } | null | undefined>;
|
|
185
|
+
children?: ReactNode;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const FieldError = forwardRef<View, FieldErrorProps>(
|
|
189
|
+
({ className, errors, children, ...props }, ref) => {
|
|
190
|
+
const messages = useMemo(() => {
|
|
191
|
+
if (!errors) return null;
|
|
192
|
+
const seen = new Set<string>();
|
|
193
|
+
const out: string[] = [];
|
|
194
|
+
for (const item of errors) {
|
|
195
|
+
const message = typeof item === 'string' ? item : item?.message;
|
|
196
|
+
if (message && !seen.has(message)) {
|
|
197
|
+
seen.add(message);
|
|
198
|
+
out.push(message);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}, [errors]);
|
|
203
|
+
|
|
204
|
+
const hasContent = messages ? messages.length > 0 : !!children;
|
|
205
|
+
if (!hasContent) return null;
|
|
206
|
+
|
|
207
|
+
const { error, errorText, bullet, bulletDot } = fieldVariants();
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<View
|
|
211
|
+
ref={ref}
|
|
212
|
+
// Web-aligned `role`, same as `Separator` already uses — RN's older
|
|
213
|
+
// accessibilityRole list has no alert, the ARIA-aligned one does.
|
|
214
|
+
role="alert"
|
|
215
|
+
accessibilityLiveRegion="polite"
|
|
216
|
+
className={error({ className })}
|
|
217
|
+
{...props}
|
|
218
|
+
>
|
|
219
|
+
{messages ? (
|
|
220
|
+
messages.length === 1 ? (
|
|
221
|
+
<Text className={errorText()}>{messages[0]}</Text>
|
|
222
|
+
) : (
|
|
223
|
+
messages.map((message) => (
|
|
224
|
+
<View key={message} className={bullet()}>
|
|
225
|
+
<Text className={bulletDot()}>{'•'}</Text>
|
|
226
|
+
<Text className={errorText()}>{message}</Text>
|
|
227
|
+
</View>
|
|
228
|
+
))
|
|
229
|
+
)
|
|
230
|
+
) : typeof children === 'string' ? (
|
|
231
|
+
<Text className={errorText()}>{children}</Text>
|
|
232
|
+
) : (
|
|
233
|
+
children
|
|
234
|
+
)}
|
|
235
|
+
</View>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
FieldError.displayName = 'Field.Error';
|
|
240
|
+
|
|
241
|
+
export interface FieldSetProps extends ViewProps {
|
|
242
|
+
className?: string;
|
|
243
|
+
children?: ReactNode;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const FieldSet = forwardRef<View, FieldSetProps>(
|
|
247
|
+
({ className, ...props }, ref) => {
|
|
248
|
+
const { set } = fieldVariants();
|
|
249
|
+
return <View ref={ref} role="group" className={set({ className })} {...props} />;
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
FieldSet.displayName = 'Field.Set';
|
|
253
|
+
|
|
254
|
+
export interface FieldLegendProps extends TextProps {
|
|
255
|
+
className?: string;
|
|
256
|
+
/** `legend` reads as a section heading; `label` sits closer to a field label. */
|
|
257
|
+
variant?: 'legend' | 'label';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const FieldLegend = forwardRef<RNText, FieldLegendProps>(
|
|
261
|
+
({ className, variant = 'legend', ...props }, ref) => {
|
|
262
|
+
const { legend } = fieldVariants({ legendVariant: variant });
|
|
263
|
+
return (
|
|
264
|
+
<Text ref={ref} role="heading" className={legend({ className })} {...props} />
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
FieldLegend.displayName = 'Field.Legend';
|
|
269
|
+
|
|
270
|
+
export interface FieldGroupProps extends ViewProps {
|
|
271
|
+
className?: string;
|
|
272
|
+
children?: ReactNode;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const FieldGroup = forwardRef<View, FieldGroupProps>(
|
|
276
|
+
({ className, ...props }, ref) => {
|
|
277
|
+
const { group } = fieldVariants();
|
|
278
|
+
return <View ref={ref} className={group({ className })} {...props} />;
|
|
279
|
+
}
|
|
280
|
+
);
|
|
281
|
+
FieldGroup.displayName = 'Field.Group';
|
|
282
|
+
|
|
283
|
+
export interface FieldSeparatorProps extends ViewProps {
|
|
284
|
+
className?: string;
|
|
285
|
+
/** Centered text, e.g. "Or" — omit for a plain rule. */
|
|
286
|
+
children?: ReactNode;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const FieldSeparator = forwardRef<View, FieldSeparatorProps>(
|
|
290
|
+
({ className, children, ...props }, ref) => {
|
|
291
|
+
const { separator, separatorLabel } = fieldVariants();
|
|
292
|
+
return (
|
|
293
|
+
<View ref={ref} className={separator({ className })} {...props}>
|
|
294
|
+
<Separator className="flex-1" />
|
|
295
|
+
{children ? (
|
|
296
|
+
<>
|
|
297
|
+
<Text className={separatorLabel()}>{children}</Text>
|
|
298
|
+
<Separator className="flex-1" />
|
|
299
|
+
</>
|
|
300
|
+
) : null}
|
|
301
|
+
</View>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
FieldSeparator.displayName = 'Field.Separator';
|
|
306
|
+
|
|
307
|
+
export interface FieldTitleProps extends TextProps {
|
|
308
|
+
className?: string;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const FieldTitle = forwardRef<RNText, FieldTitleProps>(
|
|
312
|
+
({ className, ...props }, ref) => {
|
|
313
|
+
const { title } = fieldVariants();
|
|
314
|
+
return <Text ref={ref} className={title({ className })} {...props} />;
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
FieldTitle.displayName = 'Field.Title';
|
|
318
|
+
|
|
319
|
+
export const Field = Object.assign(FieldRoot, {
|
|
320
|
+
Content: FieldContent,
|
|
321
|
+
Label: FieldLabel,
|
|
322
|
+
Description: FieldDescription,
|
|
323
|
+
Error: FieldError,
|
|
324
|
+
Set: FieldSet,
|
|
325
|
+
Legend: FieldLegend,
|
|
326
|
+
Group: FieldGroup,
|
|
327
|
+
Separator: FieldSeparator,
|
|
328
|
+
Title: FieldTitle,
|
|
329
|
+
});
|