@thewhileloop/whileui 3.0.0 → 4.1.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 +244 -80
- package/dist/blocks/chat/chat-conversation.d.ts +8 -0
- package/dist/blocks/chat/chat-conversation.js +36 -0
- package/dist/blocks/chat/chat.js +5 -11
- package/dist/blocks/chat/index.d.ts +1 -0
- package/dist/blocks/chat/index.js +1 -0
- package/dist/blocks/datepicker/index.d.ts +1 -0
- package/dist/blocks/datepicker/index.js +1 -0
- package/dist/blocks/datepicker/time-picker-modal.d.ts +43 -0
- package/dist/blocks/datepicker/time-picker-modal.js +187 -0
- package/dist/blocks/layout/smart-input.js +2 -1
- package/dist/code.d.ts +1 -0
- package/dist/code.js +1 -0
- package/dist/components/card/card.d.ts +1 -1
- package/dist/components/card/card.js +4 -25
- package/dist/components/code-block/code-block.d.ts +12 -0
- package/dist/components/code-block/code-block.js +13 -0
- package/dist/components/code-block/index.d.ts +1 -0
- package/dist/components/code-block/index.js +1 -0
- package/dist/components/fab/fab.d.ts +87 -0
- package/dist/components/fab/fab.js +59 -0
- package/dist/components/fab/index.d.ts +1 -0
- package/dist/components/fab/index.js +1 -0
- package/dist/components/form-field/form-field.d.ts +8 -0
- package/dist/components/form-field/form-field.js +3 -0
- package/dist/components/form-field/index.d.ts +1 -1
- package/dist/components/form-field/index.js +1 -1
- package/dist/components/input/input.d.ts +7 -0
- package/dist/components/input/input.js +26 -9
- package/dist/components/markdown/index.d.ts +1 -0
- package/dist/components/markdown/index.js +1 -0
- package/dist/components/markdown/markdown.d.ts +14 -0
- package/dist/components/markdown/markdown.js +94 -0
- package/dist/components/numeric-input/numeric-input.d.ts +1 -0
- package/dist/components/numeric-input/numeric-input.js +19 -6
- package/dist/components/select/select.d.ts +2 -1
- package/dist/components/select/select.js +18 -15
- package/dist/components/surface/index.d.ts +1 -0
- package/dist/components/surface/index.js +1 -0
- package/dist/components/surface/surface.d.ts +14 -0
- package/dist/components/surface/surface.js +35 -0
- package/dist/components/text/text.d.ts +42 -42
- package/dist/components/text/text.js +39 -17
- package/dist/components/textarea/textarea.d.ts +1 -0
- package/dist/components/textarea/textarea.js +8 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/lib/focus-ring.d.ts +10 -0
- package/dist/lib/focus-ring.js +10 -0
- package/dist/lib/interaction-tokens.d.ts +24 -1
- package/dist/lib/interaction-tokens.js +26 -44
- package/dist/lib/motion-policy.js +2 -2
- package/dist/lib/theme-presets.d.ts +26 -0
- package/dist/lib/theme-presets.js +28 -0
- package/dist/lib/web-autofill.d.ts +9 -0
- package/dist/lib/web-autofill.js +9 -0
- package/dist/markdown.d.ts +1 -0
- package/dist/markdown.js +1 -0
- package/package.json +16 -2
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type StyleProp, type TextStyle, type ViewProps } from 'react-native';
|
|
3
|
+
export interface MarkdownProps extends Omit<ViewProps, 'children'> {
|
|
4
|
+
children: string;
|
|
5
|
+
onLinkPress?: (url: string) => void;
|
|
6
|
+
codeActions?: (code: string, language: string | undefined) => React.ReactNode;
|
|
7
|
+
textStyle?: StyleProp<TextStyle>;
|
|
8
|
+
selectable?: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare function Markdown({ children, onLinkPress, codeActions, textStyle, selectable, className, ...props }: MarkdownProps): React.JSX.Element;
|
|
11
|
+
declare namespace Markdown {
|
|
12
|
+
var displayName: string;
|
|
13
|
+
}
|
|
14
|
+
export { Markdown };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
import MarkdownIt from 'markdown-it';
|
|
5
|
+
import { CodeBlock } from '../code-block';
|
|
6
|
+
import { Text } from '../text';
|
|
7
|
+
import { cn } from '../../lib/cn';
|
|
8
|
+
import { useVisualTokens } from '../../lib/visual-tokens';
|
|
9
|
+
const parser = new MarkdownIt({ html: false, linkify: true, typographer: true });
|
|
10
|
+
function toTree(tokens) {
|
|
11
|
+
const root = [];
|
|
12
|
+
const stack = [root];
|
|
13
|
+
for (const token of tokens) {
|
|
14
|
+
if (token.nesting === -1) {
|
|
15
|
+
stack.pop();
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const node = { token, children: [] };
|
|
19
|
+
stack[stack.length - 1]?.push(node);
|
|
20
|
+
if (token.nesting === 1)
|
|
21
|
+
stack.push(node.children);
|
|
22
|
+
}
|
|
23
|
+
return root;
|
|
24
|
+
}
|
|
25
|
+
function getAttribute(token, name) {
|
|
26
|
+
return token.attrGet(name) ?? undefined;
|
|
27
|
+
}
|
|
28
|
+
function Markdown({ children, onLinkPress, codeActions, textStyle, selectable = true, className, ...props }) {
|
|
29
|
+
const visual = useVisualTokens();
|
|
30
|
+
const nodes = useMemo(() => toTree(parser.parse(children, {})), [children]);
|
|
31
|
+
const renderInline = (node, key) => {
|
|
32
|
+
const { token } = node;
|
|
33
|
+
if (token.type === 'text')
|
|
34
|
+
return token.content;
|
|
35
|
+
if (token.type === 'softbreak' || token.type === 'hardbreak')
|
|
36
|
+
return '\n';
|
|
37
|
+
if (token.type === 'code_inline') {
|
|
38
|
+
return (_jsx(Text, { variant: "code", selectable: selectable, children: token.content }, key));
|
|
39
|
+
}
|
|
40
|
+
if (token.type === 'image')
|
|
41
|
+
return token.content;
|
|
42
|
+
const content = node.children.map((child, index) => renderInline(child, `${key}-${index}`));
|
|
43
|
+
if (token.type === 'strong_open')
|
|
44
|
+
return (_jsx(Text, { className: "font-bold", children: content }, key));
|
|
45
|
+
if (token.type === 'em_open')
|
|
46
|
+
return (_jsx(Text, { style: { fontStyle: 'italic' }, children: content }, key));
|
|
47
|
+
if (token.type === 's_open')
|
|
48
|
+
return (_jsx(Text, { style: { textDecorationLine: 'line-through' }, children: content }, key));
|
|
49
|
+
if (token.type === 'link_open') {
|
|
50
|
+
const href = getAttribute(token, 'href');
|
|
51
|
+
return (_jsx(Text, { className: "text-primary underline", accessibilityRole: onLinkPress ? 'link' : undefined, onPress: href && onLinkPress ? () => onLinkPress(href) : undefined, children: content }, key));
|
|
52
|
+
}
|
|
53
|
+
return content;
|
|
54
|
+
};
|
|
55
|
+
const renderNode = (node, key, listIndex) => {
|
|
56
|
+
const { token } = node;
|
|
57
|
+
if (token.type === 'inline') {
|
|
58
|
+
return toTree(token.children ?? []).map((child, index) => renderInline(child, `${key}-${index}`));
|
|
59
|
+
}
|
|
60
|
+
if (token.type === 'paragraph_open') {
|
|
61
|
+
return (_jsx(Text, { variant: "body", style: textStyle, selectable: selectable, children: node.children.map((child, index) => renderNode(child, `${key}-${index}`)) }, key));
|
|
62
|
+
}
|
|
63
|
+
if (token.type === 'heading_open') {
|
|
64
|
+
const level = Number(token.tag.slice(1));
|
|
65
|
+
const variant = level === 1 ? 'heading' : level === 2 ? 'title' : 'subtitle';
|
|
66
|
+
return (_jsx(Text, { variant: variant, accessibilityRole: "header", style: textStyle, selectable: selectable, children: node.children.map((child, index) => renderNode(child, `${key}-${index}`)) }, key));
|
|
67
|
+
}
|
|
68
|
+
if (token.type === 'fence' || token.type === 'code_block') {
|
|
69
|
+
const language = token.info.trim().split(/\s+/)[0] || undefined;
|
|
70
|
+
return (_jsx(CodeBlock, { code: token.content.replace(/\n$/, ''), language: language, actions: codeActions?.(token.content, language) }, key));
|
|
71
|
+
}
|
|
72
|
+
if (token.type === 'bullet_list_open' || token.type === 'ordered_list_open') {
|
|
73
|
+
const ordered = token.type === 'ordered_list_open';
|
|
74
|
+
const start = Number(getAttribute(token, 'start') ?? 1);
|
|
75
|
+
return (_jsx(View, { className: "gap-2", children: node.children.map((child, index) => renderNode(child, `${key}-${index}`, ordered ? start + index : undefined)) }, key));
|
|
76
|
+
}
|
|
77
|
+
if (token.type === 'list_item_open') {
|
|
78
|
+
return (_jsxs(View, { className: "flex-row items-start gap-2", children: [_jsx(Text, { variant: "body", children: listIndex === undefined ? '•' : `${listIndex}.` }), _jsx(View, { className: "min-w-0 flex-1 gap-1", children: node.children.map((child, index) => renderNode(child, `${key}-${index}`)) })] }, key));
|
|
79
|
+
}
|
|
80
|
+
if (token.type === 'blockquote_open') {
|
|
81
|
+
return (_jsx(View, { className: "border-l border-border bg-muted", style: {
|
|
82
|
+
borderLeftWidth: visual.borderWidthEmphasis,
|
|
83
|
+
paddingHorizontal: visual.surfacePaddingSm,
|
|
84
|
+
paddingVertical: visual.controlPaddingYSm,
|
|
85
|
+
}, children: node.children.map((child, index) => renderNode(child, `${key}-${index}`)) }, key));
|
|
86
|
+
}
|
|
87
|
+
if (token.type === 'hr')
|
|
88
|
+
return _jsx(View, { className: "h-px bg-border" }, key);
|
|
89
|
+
return node.children.map((child, index) => renderNode(child, `${key}-${index}`));
|
|
90
|
+
};
|
|
91
|
+
return (_jsx(View, { className: cn('gap-3', className), ...props, children: nodes.map((node, index) => renderNode(node, String(index))) }));
|
|
92
|
+
}
|
|
93
|
+
Markdown.displayName = 'Markdown';
|
|
94
|
+
export { Markdown };
|
|
@@ -47,6 +47,7 @@ declare const numericInputTextVariants: import("tailwind-variants").TVReturnType
|
|
|
47
47
|
};
|
|
48
48
|
}, undefined>>;
|
|
49
49
|
export interface NumericInputProps extends Omit<TextInputProps, 'value' | 'defaultValue' | 'onChangeText' | 'keyboardType'>, VariantProps<typeof numericInputVariants> {
|
|
50
|
+
'aria-invalid'?: boolean;
|
|
50
51
|
className?: string;
|
|
51
52
|
inputClassName?: string;
|
|
52
53
|
prefix?: React.ReactNode;
|
|
@@ -2,12 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import React, { useCallback, useMemo, useState } from 'react';
|
|
3
3
|
import { Platform, Pressable, Text, TextInput, View, } from 'react-native';
|
|
4
4
|
import { cn } from '../../lib/cn';
|
|
5
|
-
import {
|
|
5
|
+
import { focusFieldWrapper } from '../../lib/focus-ring';
|
|
6
6
|
import { tv } from '../../lib/tv';
|
|
7
7
|
import { useThemeColors } from '../../lib/theme-colors';
|
|
8
8
|
import { useInteractionTokens, withInteractivePressableStyle } from '../../lib/interaction-tokens';
|
|
9
9
|
import { useVisualTokens } from '../../lib/visual-tokens';
|
|
10
10
|
import { typographyStyle } from '../../lib/recipes';
|
|
11
|
+
import { webAutofillField } from '../../lib/web-autofill';
|
|
12
|
+
import { useFormFieldState } from '../form-field/form-field';
|
|
11
13
|
const numericInputVariants = tv({
|
|
12
14
|
base: 'w-full flex-row items-center border-border bg-muted',
|
|
13
15
|
variants: {
|
|
@@ -135,17 +137,22 @@ function NumericSteppers({ visible, inset, size, radius, model, interaction, vis
|
|
|
135
137
|
return null;
|
|
136
138
|
return (_jsxs(View, { className: "shrink-0 flex-row items-center", style: { gap: inset, paddingLeft: Math.max(6, inset) }, children: [_jsx(NumericStepperButton, { delta: -1, enabled: model.canDecrease, label: "Decrease value", symbol: "\u2212", size: size, radius: radius, model: model, interaction: interaction, visual: visual }), _jsx(NumericStepperButton, { delta: 1, enabled: model.canIncrease, label: "Increase value", symbol: "+", size: size, radius: radius, model: model, interaction: interaction, visual: visual })] }));
|
|
137
139
|
}
|
|
138
|
-
const NumericInput = React.forwardRef(({ className, inputClassName, prefix, suffix, value, defaultValue, onValueChange, min, max, step = 1, showSteppers = false, unstyled = false, variant
|
|
140
|
+
const NumericInput = React.forwardRef(({ className, inputClassName, prefix, suffix, value, defaultValue, onValueChange, min, max, step = 1, showSteppers = false, unstyled = false, variant, size = 'default', editable, 'aria-invalid': ariaInvalid, placeholderTextColor, onFocus, onBlur, style: styleProp, ...props }, ref) => {
|
|
139
141
|
const colors = useThemeColors();
|
|
140
142
|
const interaction = useInteractionTokens();
|
|
141
143
|
const visual = useVisualTokens();
|
|
144
|
+
const field = useFormFieldState();
|
|
145
|
+
const effectiveEditable = editable ?? !field.disabled;
|
|
146
|
+
const effectiveVariant = variant ?? (field.invalid ? 'error' : 'default');
|
|
147
|
+
// The border is on the wrapper and the focus on the field inside it.
|
|
148
|
+
const [focused, setFocused] = useState(false);
|
|
142
149
|
const model = useNumericInputModel({
|
|
143
150
|
value,
|
|
144
151
|
defaultValue,
|
|
145
152
|
min,
|
|
146
153
|
max,
|
|
147
154
|
step,
|
|
148
|
-
editable,
|
|
155
|
+
editable: effectiveEditable,
|
|
149
156
|
onValueChange,
|
|
150
157
|
onBlur,
|
|
151
158
|
});
|
|
@@ -154,7 +161,7 @@ const NumericInput = React.forwardRef(({ className, inputClassName, prefix, suff
|
|
|
154
161
|
const stepInset = Math.max(3, Math.round(controlHeight * 0.1));
|
|
155
162
|
const stepSize = controlHeight - stepInset * 2;
|
|
156
163
|
const stepRadius = Math.max(3, Math.min(visual.radiusMd, Math.round(stepSize / 3)));
|
|
157
|
-
return (_jsxs(View, { className: cn(numericInputVariants({ variant, size }), unstyled && 'bg-transparent', className), style: [
|
|
164
|
+
return (_jsxs(View, { className: cn(numericInputVariants({ variant: effectiveVariant, size }), unstyled && 'bg-transparent', focused && !unstyled && focusFieldWrapper, className), style: [
|
|
158
165
|
{
|
|
159
166
|
// Definite height (not minHeight) so inner elements can center reliably.
|
|
160
167
|
height: controlHeight,
|
|
@@ -163,8 +170,14 @@ const NumericInput = React.forwardRef(({ className, inputClassName, prefix, suff
|
|
|
163
170
|
paddingLeft: !unstyled && prefix ? visual.controlPaddingXDefault : 0,
|
|
164
171
|
paddingRight: unstyled ? 0 : showSteppers ? stepInset : 0,
|
|
165
172
|
},
|
|
166
|
-
!
|
|
167
|
-
], children: [_jsx(NumericAffix, { side: "prefix", visual: visual, children: prefix }), _jsx(View, { className: "min-w-0 flex-1", style: Platform.OS === 'web' ? { minWidth: 0, flex: 1 } : undefined, children: _jsx(TextInput, { ref: ref, className: cn(numericInputTextVariants({ size }), 'w-full',
|
|
173
|
+
!effectiveEditable ? { opacity: interaction.disabledOpacity } : undefined,
|
|
174
|
+
], children: [_jsx(NumericAffix, { side: "prefix", visual: visual, children: prefix }), _jsx(View, { className: "min-w-0 flex-1", style: Platform.OS === 'web' ? { minWidth: 0, flex: 1 } : undefined, children: _jsx(TextInput, { ref: ref, className: cn(numericInputTextVariants({ size }), 'w-full web:outline-none', !unstyled && webAutofillField, inputClassName), style: numericInputTextStyle(visual, size, unstyled, styleProp), value: model.textValue, onChangeText: model.handleTextChange, onFocus: (event) => {
|
|
175
|
+
setFocused(true);
|
|
176
|
+
onFocus?.(event);
|
|
177
|
+
}, onBlur: (event) => {
|
|
178
|
+
setFocused(false);
|
|
179
|
+
model.handleBlur(event);
|
|
180
|
+
}, keyboardType: "decimal-pad", editable: effectiveEditable, "aria-invalid": ariaInvalid ?? field.invalid, placeholderTextColor: placeholderTextColor ?? colors.placeholder, ...props }) }), _jsx(NumericAffix, { side: "suffix", visual: visual, children: suffix }), _jsx(NumericSteppers, { visible: showSteppers, inset: stepInset, size: stepSize, radius: stepRadius, model: model, interaction: interaction, visual: visual })] }));
|
|
168
181
|
});
|
|
169
182
|
NumericInput.displayName = 'NumericInput';
|
|
170
183
|
export { NumericInput, numericInputVariants, numericInputTextVariants };
|
|
@@ -15,6 +15,7 @@ export interface SelectProps {
|
|
|
15
15
|
}
|
|
16
16
|
export interface SelectTriggerProps extends Omit<React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>, 'style'> {
|
|
17
17
|
className?: string;
|
|
18
|
+
'aria-invalid'?: boolean;
|
|
18
19
|
/**
|
|
19
20
|
* Object form only. On web this primitive is merged through a Slot that spreads
|
|
20
21
|
* `style`, which cannot carry a state callback — a function silently becomes `{}` and
|
|
@@ -56,7 +57,7 @@ export interface SelectLabelProps extends React.ComponentPropsWithoutRef<typeof
|
|
|
56
57
|
export interface SelectSeparatorProps extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> {
|
|
57
58
|
className?: string;
|
|
58
59
|
}
|
|
59
|
-
declare
|
|
60
|
+
declare function Select({ disabled, ...props }: SelectProps): React.JSX.Element;
|
|
60
61
|
declare const SelectGroup: typeof SelectPrimitive.Group;
|
|
61
62
|
declare const SelectTrigger: ForwardRefComponent<SelectTriggerRef, SelectTriggerProps>;
|
|
62
63
|
declare function SelectValue({ className, placeholder }: SelectValueProps): React.JSX.Element;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { Platform, ScrollView, StyleSheet, View, } from 'react-native';
|
|
4
4
|
import * as SelectPrimitive from '@rn-primitives/select';
|
|
@@ -11,19 +11,20 @@ import { useFrostedSurface } from '../../lib/frosted-surface';
|
|
|
11
11
|
import { useVisualTokens } from '../../lib/visual-tokens';
|
|
12
12
|
import { useThemeColors } from '../../lib/theme-colors';
|
|
13
13
|
import { fieldRecipe, shadowStyle, surfaceRadius, typographyStyle } from '../../lib/recipes';
|
|
14
|
+
import { useFormFieldState } from '../form-field/form-field';
|
|
14
15
|
// iOS needs FullWindowOverlay to render above everything
|
|
15
16
|
const FullWindowOverlay = Platform.OS === 'ios' ? RNFullWindowOverlay : React.Fragment;
|
|
16
|
-
|
|
17
|
+
function Select({ disabled, ...props }) {
|
|
18
|
+
const field = useFormFieldState();
|
|
19
|
+
return _jsx(SelectPrimitive.Root, { disabled: disabled ?? field.disabled, ...props });
|
|
20
|
+
}
|
|
17
21
|
const SelectGroup = SelectPrimitive.Group;
|
|
18
|
-
const SelectTrigger = React.forwardRef(({ className, children, style: styleProp, ...props }, ref) => {
|
|
22
|
+
const SelectTrigger = React.forwardRef(({ className, children, style: styleProp, disabled, 'aria-invalid': ariaInvalid, ...props }, ref) => {
|
|
19
23
|
const interaction = useInteractionTokens();
|
|
20
24
|
const visual = useVisualTokens();
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// function into `{}` and silently drops every style — the trigger rendered 24px
|
|
25
|
-
// tall with no padding, border or radius. A callback is still honoured on native,
|
|
26
|
-
// where dropping it was the original bug.
|
|
25
|
+
const field = useFormFieldState();
|
|
26
|
+
const effectiveDisabled = disabled ?? field.disabled;
|
|
27
|
+
return (_jsxs(SelectPrimitive.Trigger, { ref: ref, className: cn('border-border bg-muted flex w-full flex-row items-center justify-between gap-2 shadow-sm active:bg-state-pressed', field.invalid && 'border-destructive', focusRing, effectiveDisabled && '', className),
|
|
27
28
|
// Flattened, not an array. On web this primitive is merged through a Slot that
|
|
28
29
|
// spreads `style`, and spreading an array yields { 0: …, 1: … } — numeric keys
|
|
29
30
|
// that reach the DOM as indexed style properties and throw. The Select rendered
|
|
@@ -31,8 +32,8 @@ const SelectTrigger = React.forwardRef(({ className, children, style: styleProp,
|
|
|
31
32
|
style: StyleSheet.flatten([
|
|
32
33
|
fieldRecipe(visual, 'default'),
|
|
33
34
|
styleProp,
|
|
34
|
-
|
|
35
|
-
]), ...props, children: [_jsx(_Fragment, { children: children }), _jsx(View, { className: "border-muted-foreground h-2 w-2 rotate-45 border-b-2 border-r-2" })] }));
|
|
35
|
+
effectiveDisabled ? { opacity: interaction.disabledOpacity } : null,
|
|
36
|
+
]), disabled: effectiveDisabled, "aria-invalid": ariaInvalid ?? field.invalid, ...props, children: [_jsx(_Fragment, { children: children }), _jsx(View, { testID: "select-chevron", className: "border-muted-foreground h-2 w-2 shrink-0 rotate-45 border-b-2 border-r-2" })] }));
|
|
36
37
|
});
|
|
37
38
|
SelectTrigger.displayName = 'SelectTrigger';
|
|
38
39
|
function SelectValue({ className, placeholder = 'Select...' }) {
|
|
@@ -44,7 +45,9 @@ function SelectValue({ className, placeholder = 'Select...' }) {
|
|
|
44
45
|
// user-agent default black — 1.2:1 on a dark surface. Rendering the label ourselves
|
|
45
46
|
// instead would fix the colour and lose the value: Radix owns the selection on web,
|
|
46
47
|
// and `useRootContext().value` is not populated there.
|
|
47
|
-
|
|
48
|
+
// Radix renders the selected ItemText as a nested RNW div, which resets white-space.
|
|
49
|
+
// The descendant utilities keep that generated node on the same truncated line.
|
|
50
|
+
return (_jsx(Text, { testID: "select-value", className: cn('min-w-0 flex-1 text-foreground web:overflow-hidden web:text-ellipsis web:whitespace-nowrap web:[&_*]:max-w-full web:[&_*]:overflow-hidden web:[&_*]:text-ellipsis web:[&_*]:whitespace-nowrap', !value && 'text-muted-foreground', className), style: typographyStyle(visual, 'body'), numberOfLines: 1, children: _jsx(SelectPrimitive.Value, { placeholder: placeholder }) }));
|
|
48
51
|
}
|
|
49
52
|
const SelectContent = React.forwardRef(({ className, children, portalHost, position = 'popper', insets, style: styleProp, frosted = false, blurIntensity, blurTintToken, ...props }, ref) => {
|
|
50
53
|
const frostedSurface = useFrostedSurface({
|
|
@@ -76,8 +79,8 @@ SelectContent.displayName = 'SelectContent';
|
|
|
76
79
|
const SelectItem = React.forwardRef(({ className, children, style: styleProp, ...props }, ref) => {
|
|
77
80
|
const interaction = useInteractionTokens();
|
|
78
81
|
const visual = useVisualTokens();
|
|
79
|
-
return (_jsxs(SelectPrimitive.Item, { ref: ref, className: cn('active:bg-
|
|
80
|
-
web: 'focus:bg-
|
|
82
|
+
return (_jsxs(SelectPrimitive.Item, { ref: ref, className: cn('active:bg-state-pressed group relative flex w-full flex-row items-center gap-2 pr-8', Platform.select({
|
|
83
|
+
web: 'focus:bg-state-hover focus:text-foreground cursor-default outline-none',
|
|
81
84
|
}), props.disabled && '', className), style: StyleSheet.flatten([
|
|
82
85
|
{
|
|
83
86
|
borderRadius: visual.radiusSm,
|
|
@@ -87,7 +90,7 @@ const SelectItem = React.forwardRef(({ className, children, style: styleProp, ..
|
|
|
87
90
|
},
|
|
88
91
|
styleProp,
|
|
89
92
|
props.disabled ? { opacity: interaction.disabledOpacity } : null,
|
|
90
|
-
]), ...props, children: [_jsx(View, { className: "absolute right-3 flex h-4 w-4 items-center justify-center", children: _jsx(SelectPrimitive.ItemIndicator, { children: _jsx(View, { className: "border-
|
|
93
|
+
]), ...props, children: [_jsx(View, { className: "absolute right-3 flex h-4 w-4 items-center justify-center", children: _jsx(SelectPrimitive.ItemIndicator, { children: _jsx(View, { className: "border-primary h-2.5 w-1.5 rotate-45 border-b-2 border-r-2 -mt-0.5" }) }) }), children, _jsx(SelectPrimitive.ItemText, { className: "text-foreground", style: typographyStyle(visual, 'body') })] }));
|
|
91
94
|
});
|
|
92
95
|
SelectItem.displayName = 'SelectItem';
|
|
93
96
|
function SelectItemIcon({ className, children }) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Surface, type SurfaceLevel, type SurfaceProps } from './surface';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Surface } from './surface';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ViewInstance, type ViewProps } from 'react-native';
|
|
2
|
+
import { type FrostedSurfaceProps } from '../../lib/frosted-surface';
|
|
3
|
+
import { type ShadowTier, type SurfacePaddingSize } from '../../lib/recipes';
|
|
4
|
+
import type { ForwardRefComponent } from '../../lib/ref-types';
|
|
5
|
+
export type SurfaceLevel = 'base' | 'raised' | 'overlay';
|
|
6
|
+
export interface SurfaceProps extends ViewProps, FrostedSurfaceProps {
|
|
7
|
+
level?: SurfaceLevel;
|
|
8
|
+
padding?: SurfacePaddingSize;
|
|
9
|
+
radius?: 'sm' | 'md' | 'lg' | 'xl';
|
|
10
|
+
bordered?: boolean;
|
|
11
|
+
shadow?: ShadowTier;
|
|
12
|
+
}
|
|
13
|
+
declare const Surface: ForwardRefComponent<ViewInstance, SurfaceProps>;
|
|
14
|
+
export { Surface };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
import { cn } from '../../lib/cn';
|
|
5
|
+
import { useFrostedSurface } from '../../lib/frosted-surface';
|
|
6
|
+
import { useShadowStyle, useSurfaceRecipe, } from '../../lib/recipes';
|
|
7
|
+
const LEVEL_CLASS = {
|
|
8
|
+
base: 'bg-card',
|
|
9
|
+
raised: 'bg-surface-elevated',
|
|
10
|
+
overlay: 'bg-popover',
|
|
11
|
+
};
|
|
12
|
+
const LEVEL_SHADOW = {
|
|
13
|
+
base: 'none',
|
|
14
|
+
raised: 'sm',
|
|
15
|
+
overlay: 'lg',
|
|
16
|
+
};
|
|
17
|
+
const LEVEL_TINT = {
|
|
18
|
+
base: 'card',
|
|
19
|
+
raised: 'surfaceElevated',
|
|
20
|
+
overlay: 'popover',
|
|
21
|
+
};
|
|
22
|
+
const Surface = React.forwardRef(({ level = 'base', padding = 'default', radius = 'xl', bordered = true, shadow, frosted = false, blurIntensity, blurTintToken, className, style, children, ...props }, ref) => {
|
|
23
|
+
const recipe = useSurfaceRecipe({ padding, radius, bordered });
|
|
24
|
+
const depth = useShadowStyle(shadow ?? LEVEL_SHADOW[level]);
|
|
25
|
+
const frost = useFrostedSurface({
|
|
26
|
+
frosted,
|
|
27
|
+
blurIntensity,
|
|
28
|
+
blurTintToken,
|
|
29
|
+
defaultTintToken: LEVEL_TINT[level],
|
|
30
|
+
defaultBlurPreset: 'subtle',
|
|
31
|
+
});
|
|
32
|
+
return (_jsxs(View, { ref: ref, className: cn('border-border', LEVEL_CLASS[level], frosted && 'relative overflow-hidden bg-transparent', className), style: [recipe, depth, frost.surfaceStyle, style], ...props, children: [frost.overlay, children] }));
|
|
33
|
+
});
|
|
34
|
+
Surface.displayName = 'Surface';
|
|
35
|
+
export { Surface };
|
|
@@ -4,62 +4,62 @@ import type { ForwardRefComponent } from '../../lib/ref-types';
|
|
|
4
4
|
declare const textVariants: import("tailwind-variants").TVReturnType<{
|
|
5
5
|
variant: {
|
|
6
6
|
default: "font-normal";
|
|
7
|
-
heading: "
|
|
8
|
-
title: "
|
|
9
|
-
subtitle: "
|
|
10
|
-
body: "
|
|
11
|
-
caption: "text-
|
|
12
|
-
label: "
|
|
13
|
-
code: "font-mono
|
|
7
|
+
heading: "font-bold tracking-tight";
|
|
8
|
+
title: "font-semibold";
|
|
9
|
+
subtitle: "font-medium";
|
|
10
|
+
body: "font-normal";
|
|
11
|
+
caption: "text-muted-foreground font-normal";
|
|
12
|
+
label: "font-medium leading-tight";
|
|
13
|
+
code: "font-mono bg-muted rounded px-1 py-0.5";
|
|
14
14
|
};
|
|
15
15
|
size: {
|
|
16
|
-
xs: "
|
|
17
|
-
sm: "
|
|
18
|
-
base: "
|
|
19
|
-
lg: "
|
|
20
|
-
xl: "
|
|
21
|
-
'2xl': "
|
|
22
|
-
'3xl': "
|
|
16
|
+
xs: "";
|
|
17
|
+
sm: "";
|
|
18
|
+
base: "";
|
|
19
|
+
lg: "";
|
|
20
|
+
xl: "";
|
|
21
|
+
'2xl': "";
|
|
22
|
+
'3xl': "";
|
|
23
23
|
};
|
|
24
24
|
}, undefined, "text-foreground", {
|
|
25
25
|
variant: {
|
|
26
26
|
default: "font-normal";
|
|
27
|
-
heading: "
|
|
28
|
-
title: "
|
|
29
|
-
subtitle: "
|
|
30
|
-
body: "
|
|
31
|
-
caption: "text-
|
|
32
|
-
label: "
|
|
33
|
-
code: "font-mono
|
|
27
|
+
heading: "font-bold tracking-tight";
|
|
28
|
+
title: "font-semibold";
|
|
29
|
+
subtitle: "font-medium";
|
|
30
|
+
body: "font-normal";
|
|
31
|
+
caption: "text-muted-foreground font-normal";
|
|
32
|
+
label: "font-medium leading-tight";
|
|
33
|
+
code: "font-mono bg-muted rounded px-1 py-0.5";
|
|
34
34
|
};
|
|
35
35
|
size: {
|
|
36
|
-
xs: "
|
|
37
|
-
sm: "
|
|
38
|
-
base: "
|
|
39
|
-
lg: "
|
|
40
|
-
xl: "
|
|
41
|
-
'2xl': "
|
|
42
|
-
'3xl': "
|
|
36
|
+
xs: "";
|
|
37
|
+
sm: "";
|
|
38
|
+
base: "";
|
|
39
|
+
lg: "";
|
|
40
|
+
xl: "";
|
|
41
|
+
'2xl': "";
|
|
42
|
+
'3xl': "";
|
|
43
43
|
};
|
|
44
44
|
}, undefined, import("tailwind-variants").TVReturnTypeLike<{
|
|
45
45
|
variant: {
|
|
46
46
|
default: "font-normal";
|
|
47
|
-
heading: "
|
|
48
|
-
title: "
|
|
49
|
-
subtitle: "
|
|
50
|
-
body: "
|
|
51
|
-
caption: "text-
|
|
52
|
-
label: "
|
|
53
|
-
code: "font-mono
|
|
47
|
+
heading: "font-bold tracking-tight";
|
|
48
|
+
title: "font-semibold";
|
|
49
|
+
subtitle: "font-medium";
|
|
50
|
+
body: "font-normal";
|
|
51
|
+
caption: "text-muted-foreground font-normal";
|
|
52
|
+
label: "font-medium leading-tight";
|
|
53
|
+
code: "font-mono bg-muted rounded px-1 py-0.5";
|
|
54
54
|
};
|
|
55
55
|
size: {
|
|
56
|
-
xs: "
|
|
57
|
-
sm: "
|
|
58
|
-
base: "
|
|
59
|
-
lg: "
|
|
60
|
-
xl: "
|
|
61
|
-
'2xl': "
|
|
62
|
-
'3xl': "
|
|
56
|
+
xs: "";
|
|
57
|
+
sm: "";
|
|
58
|
+
base: "";
|
|
59
|
+
lg: "";
|
|
60
|
+
xl: "";
|
|
61
|
+
'2xl': "";
|
|
62
|
+
'3xl': "";
|
|
63
63
|
};
|
|
64
64
|
}, undefined>>;
|
|
65
65
|
export interface TextProps extends RNTextProps, VariantProps<typeof textVariants> {
|
|
@@ -4,37 +4,59 @@ import { Text as RNText } from 'react-native';
|
|
|
4
4
|
import { cn } from '../../lib/cn';
|
|
5
5
|
import { tv } from '../../lib/tv';
|
|
6
6
|
import { useResolveFontFamily } from '../../lib/font-context';
|
|
7
|
+
import { useTypography } from '../../lib/recipes';
|
|
7
8
|
const textVariants = tv({
|
|
8
9
|
base: 'text-foreground',
|
|
9
10
|
variants: {
|
|
10
11
|
variant: {
|
|
11
12
|
default: 'font-normal',
|
|
12
|
-
heading: '
|
|
13
|
-
title: '
|
|
14
|
-
subtitle: '
|
|
15
|
-
body: '
|
|
16
|
-
caption: 'text-
|
|
17
|
-
label: '
|
|
18
|
-
code: 'font-mono
|
|
13
|
+
heading: 'font-bold tracking-tight',
|
|
14
|
+
title: 'font-semibold',
|
|
15
|
+
subtitle: 'font-medium',
|
|
16
|
+
body: 'font-normal',
|
|
17
|
+
caption: 'text-muted-foreground font-normal',
|
|
18
|
+
label: 'font-medium leading-tight',
|
|
19
|
+
code: 'font-mono bg-muted rounded px-1 py-0.5',
|
|
19
20
|
},
|
|
20
21
|
size: {
|
|
21
|
-
xs: '
|
|
22
|
-
sm: '
|
|
23
|
-
base: '
|
|
24
|
-
lg: '
|
|
25
|
-
xl: '
|
|
26
|
-
'2xl': '
|
|
27
|
-
'3xl': '
|
|
22
|
+
xs: '',
|
|
23
|
+
sm: '',
|
|
24
|
+
base: '',
|
|
25
|
+
lg: '',
|
|
26
|
+
xl: '',
|
|
27
|
+
'2xl': '',
|
|
28
|
+
'3xl': '',
|
|
28
29
|
},
|
|
29
30
|
},
|
|
30
31
|
defaultVariants: {
|
|
31
32
|
variant: 'default',
|
|
32
33
|
},
|
|
33
34
|
});
|
|
34
|
-
const
|
|
35
|
-
|
|
35
|
+
const VARIANT_ROLES = {
|
|
36
|
+
default: 'body',
|
|
37
|
+
heading: 'headline',
|
|
38
|
+
title: 'title',
|
|
39
|
+
subtitle: 'emphasis',
|
|
40
|
+
body: 'body',
|
|
41
|
+
caption: 'caption',
|
|
42
|
+
label: 'label',
|
|
43
|
+
code: 'label',
|
|
44
|
+
};
|
|
45
|
+
const SIZE_ROLES = {
|
|
46
|
+
xs: 'caption',
|
|
47
|
+
sm: 'label',
|
|
48
|
+
base: 'body',
|
|
49
|
+
lg: 'emphasis',
|
|
50
|
+
xl: 'title',
|
|
51
|
+
'2xl': 'headline',
|
|
52
|
+
'3xl': 'headline',
|
|
53
|
+
};
|
|
54
|
+
const Text = React.forwardRef(({ className, variant, size, style, accessibilityRole, ...props }, ref) => {
|
|
55
|
+
const effectiveVariant = variant ?? 'default';
|
|
56
|
+
const resolved = cn(textVariants({ variant: effectiveVariant, size }), className);
|
|
36
57
|
const font = useResolveFontFamily(resolved);
|
|
37
|
-
|
|
58
|
+
const semanticStyle = useTypography(size ? SIZE_ROLES[size] : VARIANT_ROLES[effectiveVariant]);
|
|
59
|
+
return (_jsx(RNText, { ref: ref, className: font ? font.className : resolved, accessibilityRole: accessibilityRole ?? (effectiveVariant === 'heading' ? 'header' : undefined), style: [variant || size ? semanticStyle : undefined, font?.style, style], ...props }));
|
|
38
60
|
});
|
|
39
61
|
Text.displayName = 'Text';
|
|
40
62
|
export { Text, textVariants };
|
|
@@ -21,6 +21,7 @@ declare const textareaVariants: import("tailwind-variants").TVReturnType<{
|
|
|
21
21
|
};
|
|
22
22
|
}, undefined>>;
|
|
23
23
|
export interface TextareaProps extends Omit<TextInputProps, 'style'>, VariantProps<typeof textareaVariants> {
|
|
24
|
+
'aria-invalid'?: boolean;
|
|
24
25
|
className?: string;
|
|
25
26
|
}
|
|
26
27
|
declare const Textarea: ForwardRefComponent<TextInputInstance, TextareaProps>;
|
|
@@ -2,12 +2,14 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { TextInput, } from 'react-native';
|
|
4
4
|
import { cn } from '../../lib/cn';
|
|
5
|
-
import {
|
|
5
|
+
import { focusField } from '../../lib/focus-ring';
|
|
6
6
|
import { tv } from '../../lib/tv';
|
|
7
7
|
import { useThemeColors } from '../../lib/theme-colors';
|
|
8
8
|
import { useInteractionTokens } from '../../lib/interaction-tokens';
|
|
9
9
|
import { useVisualTokens } from '../../lib/visual-tokens';
|
|
10
10
|
import { typographyStyle } from '../../lib/recipes';
|
|
11
|
+
import { webAutofillField } from '../../lib/web-autofill';
|
|
12
|
+
import { useFormFieldState } from '../form-field/form-field';
|
|
11
13
|
const textareaVariants = tv({
|
|
12
14
|
base: 'w-full border-border bg-muted text-foreground',
|
|
13
15
|
variants: {
|
|
@@ -26,10 +28,12 @@ const TEXTAREA_TEXT_ROLE = {
|
|
|
26
28
|
sm: 'caption',
|
|
27
29
|
lg: 'body',
|
|
28
30
|
};
|
|
29
|
-
const Textarea = React.forwardRef(({ className, size = 'default', placeholderTextColor, ...props }, ref) => {
|
|
31
|
+
const Textarea = React.forwardRef(({ className, size = 'default', placeholderTextColor, editable, 'aria-invalid': ariaInvalid, ...props }, ref) => {
|
|
30
32
|
const colors = useThemeColors();
|
|
31
33
|
const interaction = useInteractionTokens();
|
|
32
34
|
const visual = useVisualTokens();
|
|
35
|
+
const field = useFormFieldState();
|
|
36
|
+
const effectiveEditable = editable ?? !field.disabled;
|
|
33
37
|
const minHeight = size === 'sm'
|
|
34
38
|
? visual.textareaMinHeightSm
|
|
35
39
|
: size === 'lg'
|
|
@@ -42,9 +46,9 @@ const Textarea = React.forwardRef(({ className, size = 'default', placeholderTex
|
|
|
42
46
|
borderRadius: visual.radiusLg,
|
|
43
47
|
paddingHorizontal: visual.controlPaddingXDefault,
|
|
44
48
|
paddingVertical: visual.controlPaddingYDefault,
|
|
45
|
-
...(
|
|
49
|
+
...(!effectiveEditable ? { opacity: interaction.disabledOpacity } : null),
|
|
46
50
|
};
|
|
47
|
-
return (_jsx(TextInput, { ref: ref, className: cn(textareaVariants({ size }),
|
|
51
|
+
return (_jsx(TextInput, { ref: ref, className: cn(textareaVariants({ size }), field.invalid && 'border-destructive', focusField, webAutofillField, className), style: style, multiline: true, textAlignVertical: "top", placeholderTextColor: placeholderTextColor ?? colors.placeholder, editable: effectiveEditable, "aria-invalid": ariaInvalid ?? field.invalid, ...props }));
|
|
48
52
|
});
|
|
49
53
|
Textarea.displayName = 'Textarea';
|
|
50
54
|
export { Textarea, textareaVariants };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { PortalHost } from './lib/portal';
|
|
|
7
7
|
export { ThemeBridge, applyThemeMode, resolveThemeMode, syncThemeMode, useThemeBridge, type ThemeMode, type ThemeBridgeAdapter, type ThemeBridgeProps, type UseThemeBridgeOptions, type UseThemeBridgeResult, } from './lib/theme-bridge';
|
|
8
8
|
export { useThemeColors, useThemeTokens, useIconColors, type ThemeColors, } from './lib/theme-colors';
|
|
9
9
|
export { THEME_TOKEN_SPECS, THEME_TOKEN_GROUPS, WHILEUI_LIGHT_THEME_COLORS, WHILEUI_DARK_THEME_COLORS, type ThemeTokenGroup, type ThemeTokenKey, type ThemeTokenSpec, } from './lib/theme-contract';
|
|
10
|
-
export { useInteractionTokens, withInteractivePressableStyle, type InteractionTokens, type InteractivePressableOptions, type PressedVariant, type DisabledVariant, } from './lib/interaction-tokens';
|
|
10
|
+
export { INTERACTION_TOKENS, useInteractionTokens, withInteractivePressableStyle, type InteractionTokens, type InteractivePressableOptions, type PressedVariant, type DisabledVariant, } from './lib/interaction-tokens';
|
|
11
11
|
export { MotionProvider, useMotionPolicy, type MotionMode, type MotionDuration, type MotionSpring, type MotionSpringConfig, type MotionPolicy, type MotionProviderProps, } from './lib/motion-policy';
|
|
12
12
|
export { useVisualTokens, type VisualTokens } from './lib/visual-tokens';
|
|
13
13
|
export { VISUAL_TOKEN_SPECS, VISUAL_TOKEN_GROUPS, type VisualTokenGroup, type VisualTokenKey, type VisualTokenKind, type VisualTokenSpec, } from './lib/visual-token-contract';
|
|
@@ -16,6 +16,7 @@ export { controlRecipe, fieldRecipe, surfaceRecipe, surfacePadding, surfaceRadiu
|
|
|
16
16
|
export { WHILEUI_PRESET_SOFT, WHILEUI_PRESET_ATELIER, WHILEUI_THEME_PRESETS, themePresetToCssVariables, themePresetToCss, type ThemePreset, } from './lib/theme-presets';
|
|
17
17
|
export { deriveThemeColors, THEME_BASE_TOKEN_KEYS, type ThemeBaseColors } from './lib/theme-derive';
|
|
18
18
|
export { Text, textVariants, type TextProps } from './components/text';
|
|
19
|
+
export { Surface, type SurfaceLevel, type SurfaceProps } from './components/surface';
|
|
19
20
|
export { View, type ViewProps } from './components/view';
|
|
20
21
|
export { Pressable, type PressableProps } from './components/pressable';
|
|
21
22
|
export { Stack, stackVariants, type StackProps } from './components/stack';
|
|
@@ -25,9 +26,10 @@ export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter,
|
|
|
25
26
|
export { Input, inputVariants, type InputProps } from './components/input';
|
|
26
27
|
export { NumericInput, numericInputVariants, numericInputTextVariants, type NumericInputProps, } from './components/numeric-input';
|
|
27
28
|
export { OTPInput, otpCellVariants, type OTPInputProps } from './components/otp-input';
|
|
28
|
-
export { FormField, FormLabel, FormControl, FormHint, FormMessage, formFieldVariants, formControlVariants, type FormFieldProps, type FormLabelProps, type FormControlProps, type FormHintProps, type FormMessageProps, } from './components/form-field';
|
|
29
|
+
export { FormField, FormLabel, FormControl, FormHint, FormMessage, useFormFieldState, formFieldVariants, formControlVariants, type FormFieldProps, type FormLabelProps, type FormControlProps, type FormHintProps, type FormMessageProps, type FormFieldState, } from './components/form-field';
|
|
29
30
|
export { LabeledField, LabeledFieldControl, type LabeledFieldProps, type LabeledFieldControlProps, } from './components/labeled-field';
|
|
30
31
|
export { Badge, BadgeText, badgeVariants, badgeTextVariants, type BadgeProps, type BadgeTextProps, } from './components/badge';
|
|
32
|
+
export { FAB, FABGroup, fabVariants, type FabProps, type FabGroupProps } from './components/fab';
|
|
31
33
|
export { Alert, AlertTitle, AlertDescription, alertVariants, type AlertProps, type AlertTitleProps, type AlertDescriptionProps, } from './components/alert';
|
|
32
34
|
export { Switch, type SwitchProps } from './components/switch';
|
|
33
35
|
export { Checkbox, type CheckboxProps } from './components/checkbox';
|