more usage of tamagui primitives
This commit is contained in:
@@ -2,6 +2,8 @@ import React from 'react';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { YStack, XStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { Input, TextArea } from 'tamagui';
|
||||
import { Select } from '@tamagui/select';
|
||||
import { withAlpha } from './colors';
|
||||
import { useAdminTheme } from '../theme';
|
||||
|
||||
@@ -40,41 +42,41 @@ type ControlProps = {
|
||||
compact?: boolean;
|
||||
};
|
||||
|
||||
export const MobileInput = React.forwardRef<HTMLInputElement, React.ComponentPropsWithoutRef<'input'> & ControlProps>(
|
||||
function MobileInput({ hasError = false, compact = false, style, ...props }, ref) {
|
||||
const { border, surface, text, primary, danger } = useAdminTheme();
|
||||
const [focused, setFocused] = React.useState(false);
|
||||
type MobileSelectProps = React.ComponentPropsWithoutRef<'select'> & ControlProps & {
|
||||
placeholder?: string;
|
||||
containerStyle?: React.CSSProperties;
|
||||
};
|
||||
|
||||
const height = compact ? 36 : 44;
|
||||
const borderColor = hasError ? danger : focused ? primary : border;
|
||||
export const MobileInput = React.forwardRef<HTMLInputElement, React.ComponentPropsWithoutRef<'input'> & ControlProps>(
|
||||
function MobileInput({ hasError = false, compact = false, style, onChange, ...props }, ref) {
|
||||
const { border, surface, text, primary, danger } = useAdminTheme();
|
||||
const borderColor = hasError ? danger : border;
|
||||
const ringColor = hasError ? withAlpha(danger, 0.18) : withAlpha(primary, 0.18);
|
||||
|
||||
return (
|
||||
<input
|
||||
ref={ref}
|
||||
<Input
|
||||
ref={ref as React.Ref<any>}
|
||||
{...props}
|
||||
onFocus={(event) => {
|
||||
setFocused(true);
|
||||
props.onFocus?.(event);
|
||||
onChangeText={(value) => {
|
||||
onChange?.({ target: { value } } as React.ChangeEvent<HTMLInputElement>);
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
setFocused(false);
|
||||
props.onBlur?.(event);
|
||||
size={compact ? '$3' : '$4'}
|
||||
height={compact ? 36 : 44}
|
||||
paddingHorizontal="$3"
|
||||
borderRadius={12}
|
||||
width="100%"
|
||||
fontSize={compact ? 13 : 14}
|
||||
backgroundColor={surface}
|
||||
color={text}
|
||||
borderColor={borderColor}
|
||||
focusStyle={{
|
||||
borderColor: hasError ? danger : primary,
|
||||
boxShadow: `0 0 0 3px ${ringColor}`,
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height,
|
||||
borderRadius: 12,
|
||||
border: `1px solid ${borderColor}`,
|
||||
padding: '0 12px',
|
||||
fontSize: compact ? 13 : 14,
|
||||
background: surface,
|
||||
color: text,
|
||||
outline: 'none',
|
||||
boxShadow: focused || hasError ? `0 0 0 3px ${ringColor}` : 'none',
|
||||
transition: 'border-color 150ms ease, box-shadow 150ms ease',
|
||||
...style,
|
||||
hoverStyle={{
|
||||
borderColor,
|
||||
}}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
},
|
||||
@@ -83,40 +85,35 @@ export const MobileInput = React.forwardRef<HTMLInputElement, React.ComponentPro
|
||||
export const MobileTextArea = React.forwardRef<
|
||||
HTMLTextAreaElement,
|
||||
React.ComponentPropsWithoutRef<'textarea'> & ControlProps
|
||||
>(function MobileTextArea({ hasError = false, compact = false, style, ...props }, ref) {
|
||||
>(function MobileTextArea({ hasError = false, compact = false, style, onChange, ...props }, ref) {
|
||||
const { border, surface, text, primary, danger } = useAdminTheme();
|
||||
const [focused, setFocused] = React.useState(false);
|
||||
|
||||
const borderColor = hasError ? danger : focused ? primary : border;
|
||||
const borderColor = hasError ? danger : border;
|
||||
const ringColor = hasError ? withAlpha(danger, 0.18) : withAlpha(primary, 0.18);
|
||||
|
||||
return (
|
||||
<textarea
|
||||
ref={ref}
|
||||
<TextArea
|
||||
ref={ref as React.Ref<any>}
|
||||
{...props}
|
||||
onFocus={(event) => {
|
||||
setFocused(true);
|
||||
props.onFocus?.(event);
|
||||
onChangeText={(value) => {
|
||||
onChange?.({ target: { value } } as React.ChangeEvent<HTMLTextAreaElement>);
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
setFocused(false);
|
||||
props.onBlur?.(event);
|
||||
size={compact ? '$3' : '$4'}
|
||||
minHeight={compact ? 72 : 96}
|
||||
borderRadius={12}
|
||||
padding="$3"
|
||||
width="100%"
|
||||
fontSize={compact ? 13 : 14}
|
||||
backgroundColor={surface}
|
||||
color={text}
|
||||
borderColor={borderColor}
|
||||
focusStyle={{
|
||||
borderColor: hasError ? danger : primary,
|
||||
boxShadow: `0 0 0 3px ${ringColor}`,
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
borderRadius: 12,
|
||||
border: `1px solid ${borderColor}`,
|
||||
padding: '10px 12px',
|
||||
fontSize: compact ? 13 : 14,
|
||||
background: surface,
|
||||
color: text,
|
||||
outline: 'none',
|
||||
minHeight: compact ? 72 : 96,
|
||||
boxShadow: focused || hasError ? `0 0 0 3px ${ringColor}` : 'none',
|
||||
transition: 'border-color 150ms ease, box-shadow 150ms ease',
|
||||
resize: 'vertical',
|
||||
...style,
|
||||
hoverStyle={{
|
||||
borderColor,
|
||||
}}
|
||||
style={{ resize: 'vertical', ...style }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -125,50 +122,92 @@ export function MobileSelect({
|
||||
children,
|
||||
hasError = false,
|
||||
compact = false,
|
||||
containerStyle,
|
||||
style,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<'select'> & ControlProps) {
|
||||
}: MobileSelectProps) {
|
||||
const { border, surface, text, primary, danger, subtle } = useAdminTheme();
|
||||
const [focused, setFocused] = React.useState(false);
|
||||
|
||||
const height = compact ? 36 : 44;
|
||||
const borderColor = hasError ? danger : focused ? primary : border;
|
||||
const borderColor = hasError ? danger : border;
|
||||
const ringColor = hasError ? withAlpha(danger, 0.18) : withAlpha(primary, 0.18);
|
||||
const hasSizing =
|
||||
typeof containerStyle === 'object' &&
|
||||
containerStyle !== null &&
|
||||
('width' in containerStyle ||
|
||||
'maxWidth' in containerStyle ||
|
||||
'minWidth' in containerStyle ||
|
||||
'flex' in containerStyle ||
|
||||
'flexGrow' in containerStyle);
|
||||
const options = React.Children.toArray(children).flatMap((child) => {
|
||||
if (!React.isValidElement(child)) return [];
|
||||
const { value, children: label, disabled } = child.props as {
|
||||
value?: string | number;
|
||||
children?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
};
|
||||
return [
|
||||
{
|
||||
value: value === undefined ? '' : String(value),
|
||||
label: label ?? '',
|
||||
disabled: Boolean(disabled),
|
||||
},
|
||||
];
|
||||
});
|
||||
const emptyOption = options.find((option) => option.value === '');
|
||||
const selectValue = props.value === undefined ? undefined : String(props.value ?? '');
|
||||
const selectDefault = props.defaultValue === undefined ? undefined : String(props.defaultValue ?? '');
|
||||
|
||||
return (
|
||||
<XStack position="relative" alignItems="center">
|
||||
<select
|
||||
{...props}
|
||||
onFocus={(event) => {
|
||||
setFocused(true);
|
||||
props.onFocus?.(event);
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
setFocused(false);
|
||||
props.onBlur?.(event);
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height,
|
||||
borderRadius: 12,
|
||||
border: `1px solid ${borderColor}`,
|
||||
padding: '0 36px 0 12px',
|
||||
fontSize: compact ? 13 : 14,
|
||||
background: surface,
|
||||
color: text,
|
||||
outline: 'none',
|
||||
appearance: 'none',
|
||||
WebkitAppearance: 'none',
|
||||
boxShadow: focused || hasError ? `0 0 0 3px ${ringColor}` : 'none',
|
||||
transition: 'border-color 150ms ease, box-shadow 150ms ease',
|
||||
...style,
|
||||
<XStack position="relative" alignItems="center" width={hasSizing ? undefined : '100%'} style={containerStyle}>
|
||||
<Select
|
||||
value={selectValue}
|
||||
defaultValue={selectValue === undefined ? selectDefault : undefined}
|
||||
onValueChange={(next) => {
|
||||
props.onChange?.({ target: { value: next } } as React.ChangeEvent<HTMLSelectElement>);
|
||||
}}
|
||||
size={compact ? '$3' : '$4'}
|
||||
>
|
||||
{children}
|
||||
</select>
|
||||
<XStack position="absolute" right={12} pointerEvents="none">
|
||||
<ChevronDown size={16} color={subtle} />
|
||||
</XStack>
|
||||
<Select.Trigger
|
||||
width="100%"
|
||||
borderRadius={12}
|
||||
borderWidth={1}
|
||||
borderColor={borderColor}
|
||||
backgroundColor={surface}
|
||||
paddingVertical={compact ? 6 : 10}
|
||||
paddingHorizontal="$3"
|
||||
disabled={props.disabled}
|
||||
onFocus={props.onFocus}
|
||||
onBlur={props.onBlur}
|
||||
iconAfter={<ChevronDown size={16} color={subtle} />}
|
||||
focusStyle={{
|
||||
borderColor: hasError ? danger : primary,
|
||||
boxShadow: `0 0 0 3px ${ringColor}`,
|
||||
}}
|
||||
hoverStyle={{
|
||||
borderColor,
|
||||
}}
|
||||
style={style}
|
||||
>
|
||||
<Select.Value placeholder={props.placeholder ?? emptyOption?.label ?? ''} color={text} />
|
||||
</Select.Trigger>
|
||||
<Select.Content
|
||||
zIndex={200000}
|
||||
borderRadius={14}
|
||||
borderWidth={1}
|
||||
borderColor={border}
|
||||
backgroundColor={surface}
|
||||
>
|
||||
<Select.Viewport padding="$2">
|
||||
<Select.Group>
|
||||
{options.map((option, index) => (
|
||||
<Select.Item key={`${option.value}-${index}`} value={option.value} disabled={option.disabled}>
|
||||
<Select.ItemText>{option.label}</Select.ItemText>
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Group>
|
||||
</Select.Viewport>
|
||||
</Select.Content>
|
||||
</Select>
|
||||
{props.name ? <input type="hidden" name={props.name} value={selectValue ?? selectDefault ?? ''} /> : null}
|
||||
</XStack>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user