@thewhileloop/whileui 4.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 +127 -15
- 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/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/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 +1 -0
- package/dist/components/input/input.js +11 -6
- 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 +11 -6
- package/dist/components/select/select.d.ts +2 -1
- package/dist/components/select/select.js +18 -10
- 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 +7 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- 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,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;
|
|
@@ -8,6 +8,8 @@ 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,10 +137,13 @@ 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');
|
|
142
147
|
// The border is on the wrapper and the focus on the field inside it.
|
|
143
148
|
const [focused, setFocused] = useState(false);
|
|
144
149
|
const model = useNumericInputModel({
|
|
@@ -147,7 +152,7 @@ const NumericInput = React.forwardRef(({ className, inputClassName, prefix, suff
|
|
|
147
152
|
min,
|
|
148
153
|
max,
|
|
149
154
|
step,
|
|
150
|
-
editable,
|
|
155
|
+
editable: effectiveEditable,
|
|
151
156
|
onValueChange,
|
|
152
157
|
onBlur,
|
|
153
158
|
});
|
|
@@ -156,7 +161,7 @@ const NumericInput = React.forwardRef(({ className, inputClassName, prefix, suff
|
|
|
156
161
|
const stepInset = Math.max(3, Math.round(controlHeight * 0.1));
|
|
157
162
|
const stepSize = controlHeight - stepInset * 2;
|
|
158
163
|
const stepRadius = Math.max(3, Math.min(visual.radiusMd, Math.round(stepSize / 3)));
|
|
159
|
-
return (_jsxs(View, { className: cn(numericInputVariants({ variant, size }), unstyled && 'bg-transparent', focused && !unstyled && focusFieldWrapper, className), style: [
|
|
164
|
+
return (_jsxs(View, { className: cn(numericInputVariants({ variant: effectiveVariant, size }), unstyled && 'bg-transparent', focused && !unstyled && focusFieldWrapper, className), style: [
|
|
160
165
|
{
|
|
161
166
|
// Definite height (not minHeight) so inner elements can center reliably.
|
|
162
167
|
height: controlHeight,
|
|
@@ -165,14 +170,14 @@ const NumericInput = React.forwardRef(({ className, inputClassName, prefix, suff
|
|
|
165
170
|
paddingLeft: !unstyled && prefix ? visual.controlPaddingXDefault : 0,
|
|
166
171
|
paddingRight: unstyled ? 0 : showSteppers ? stepInset : 0,
|
|
167
172
|
},
|
|
168
|
-
!
|
|
169
|
-
], 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', inputClassName), style: numericInputTextStyle(visual, size, unstyled, styleProp), value: model.textValue, onChangeText: model.handleTextChange, onFocus: (event) => {
|
|
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) => {
|
|
170
175
|
setFocused(true);
|
|
171
176
|
onFocus?.(event);
|
|
172
177
|
}, onBlur: (event) => {
|
|
173
178
|
setFocused(false);
|
|
174
179
|
model.handleBlur(event);
|
|
175
|
-
}, keyboardType: "decimal-pad", editable:
|
|
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 })] }));
|
|
176
181
|
});
|
|
177
182
|
NumericInput.displayName = 'NumericInput';
|
|
178
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,14 +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
|
-
|
|
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),
|
|
22
28
|
// Flattened, not an array. On web this primitive is merged through a Slot that
|
|
23
29
|
// spreads `style`, and spreading an array yields { 0: …, 1: … } — numeric keys
|
|
24
30
|
// that reach the DOM as indexed style properties and throw. The Select rendered
|
|
@@ -26,8 +32,8 @@ const SelectTrigger = React.forwardRef(({ className, children, style: styleProp,
|
|
|
26
32
|
style: StyleSheet.flatten([
|
|
27
33
|
fieldRecipe(visual, 'default'),
|
|
28
34
|
styleProp,
|
|
29
|
-
|
|
30
|
-
]), ...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" })] }));
|
|
31
37
|
});
|
|
32
38
|
SelectTrigger.displayName = 'SelectTrigger';
|
|
33
39
|
function SelectValue({ className, placeholder = 'Select...' }) {
|
|
@@ -39,7 +45,9 @@ function SelectValue({ className, placeholder = 'Select...' }) {
|
|
|
39
45
|
// user-agent default black — 1.2:1 on a dark surface. Rendering the label ourselves
|
|
40
46
|
// instead would fix the colour and lose the value: Radix owns the selection on web,
|
|
41
47
|
// and `useRootContext().value` is not populated there.
|
|
42
|
-
|
|
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 }) }));
|
|
43
51
|
}
|
|
44
52
|
const SelectContent = React.forwardRef(({ className, children, portalHost, position = 'popper', insets, style: styleProp, frosted = false, blurIntensity, blurTintToken, ...props }, ref) => {
|
|
45
53
|
const frostedSurface = useFrostedSurface({
|
|
@@ -71,8 +79,8 @@ SelectContent.displayName = 'SelectContent';
|
|
|
71
79
|
const SelectItem = React.forwardRef(({ className, children, style: styleProp, ...props }, ref) => {
|
|
72
80
|
const interaction = useInteractionTokens();
|
|
73
81
|
const visual = useVisualTokens();
|
|
74
|
-
return (_jsxs(SelectPrimitive.Item, { ref: ref, className: cn('active:bg-
|
|
75
|
-
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',
|
|
76
84
|
}), props.disabled && '', className), style: StyleSheet.flatten([
|
|
77
85
|
{
|
|
78
86
|
borderRadius: visual.radiusSm,
|
|
@@ -82,7 +90,7 @@ const SelectItem = React.forwardRef(({ className, children, style: styleProp, ..
|
|
|
82
90
|
},
|
|
83
91
|
styleProp,
|
|
84
92
|
props.disabled ? { opacity: interaction.disabledOpacity } : null,
|
|
85
|
-
]), ...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') })] }));
|
|
86
94
|
});
|
|
87
95
|
SelectItem.displayName = 'SelectItem';
|
|
88
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>;
|
|
@@ -8,6 +8,8 @@ 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 }), focusField, className), style: style, multiline: true, textAlignVertical: "top", placeholderTextColor: placeholderTextColor ?? colors.placeholder, ...props }));
|
|
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
|
@@ -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,7 +26,7 @@ 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';
|
|
31
32
|
export { FAB, FABGroup, fabVariants, type FabProps, type FabGroupProps } from './components/fab';
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export { controlRecipe, fieldRecipe, surfaceRecipe, surfacePadding, surfaceRadiu
|
|
|
16
16
|
export { WHILEUI_PRESET_SOFT, WHILEUI_PRESET_ATELIER, WHILEUI_THEME_PRESETS, themePresetToCssVariables, themePresetToCss, } from './lib/theme-presets';
|
|
17
17
|
export { deriveThemeColors, THEME_BASE_TOKEN_KEYS } from './lib/theme-derive';
|
|
18
18
|
export { Text, textVariants } from './components/text';
|
|
19
|
+
export { Surface } from './components/surface';
|
|
19
20
|
export { View } from './components/view';
|
|
20
21
|
export { Pressable } from './components/pressable';
|
|
21
22
|
export { Stack, stackVariants } from './components/stack';
|
|
@@ -25,7 +26,7 @@ export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter,
|
|
|
25
26
|
export { Input, inputVariants } from './components/input';
|
|
26
27
|
export { NumericInput, numericInputVariants, numericInputTextVariants, } from './components/numeric-input';
|
|
27
28
|
export { OTPInput, otpCellVariants } from './components/otp-input';
|
|
28
|
-
export { FormField, FormLabel, FormControl, FormHint, FormMessage, formFieldVariants, formControlVariants, } from './components/form-field';
|
|
29
|
+
export { FormField, FormLabel, FormControl, FormHint, FormMessage, useFormFieldState, formFieldVariants, formControlVariants, } from './components/form-field';
|
|
29
30
|
export { LabeledField, LabeledFieldControl, } from './components/labeled-field';
|
|
30
31
|
export { Badge, BadgeText, badgeVariants, badgeTextVariants, } from './components/badge';
|
|
31
32
|
export { FAB, FABGroup, fabVariants } from './components/fab';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser autofill paints its own field background and text colours. The large inset
|
|
3
|
+
* shadow covers that paint without changing the native style contract; the other two
|
|
4
|
+
* declarations restore the active theme's text and caret.
|
|
5
|
+
*
|
|
6
|
+
* Apply only to fields whose own surface is `bg-muted`. Transparent/unstyled inputs
|
|
7
|
+
* must keep inheriting the surface chosen by their composition.
|
|
8
|
+
*/
|
|
9
|
+
export declare const webAutofillField = "web:autofill:shadow-[inset_0_0_0_1000px_var(--color-muted)] web:autofill:[-webkit-text-fill-color:var(--color-foreground)] web:autofill:caret-foreground";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser autofill paints its own field background and text colours. The large inset
|
|
3
|
+
* shadow covers that paint without changing the native style contract; the other two
|
|
4
|
+
* declarations restore the active theme's text and caret.
|
|
5
|
+
*
|
|
6
|
+
* Apply only to fields whose own surface is `bg-muted`. Transparent/unstyled inputs
|
|
7
|
+
* must keep inheriting the surface chosen by their composition.
|
|
8
|
+
*/
|
|
9
|
+
export const webAutofillField = 'web:autofill:shadow-[inset_0_0_0_1000px_var(--color-muted)] web:autofill:[-webkit-text-fill-color:var(--color-foreground)] web:autofill:caret-foreground';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Markdown, type MarkdownProps } from './components/markdown';
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Markdown } from './components/markdown';
|