Commit 30f65443 authored by tom's avatar tom

styles for floating input

parent 3a55206a
import { Field as ChakraField } from '@chakra-ui/react';
import * as React from 'react';
export interface FieldProps extends Omit<ChakraField.RootProps, 'label'> {
import { space } from 'lib/html-entities';
import type { InputProps } from './input';
export interface FieldProps extends Omit<ChakraField.RootProps, 'label' | 'children'> {
label?: React.ReactNode;
helperText?: React.ReactNode;
errorText?: React.ReactNode;
optionalText?: React.ReactNode;
children: React.ReactElement<InputProps>;
}
export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
function Field(props, ref) {
const { label, children, helperText, errorText, optionalText, ...rest } =
props;
const { label, children, helperText, errorText, optionalText, ...rest } = props;
// A floating field cannot be without a label.
if (props.floating && label) {
const child = React.Children.only<React.ReactElement<InputProps>>(children);
const clonedChild = React.cloneElement(child, {
className: 'peer',
placeholder: ' ',
size: props.size,
floating: props.floating,
});
return (
<ChakraField.Root pos="relative" w="full" ref={ ref } { ...rest }>
{ clonedChild }
<ChakraField.Label>
{ label }
<ChakraField.RequiredIndicator fallback={ optionalText }/>
{ errorText && (
<ChakraField.ErrorText ml="2px">-{ space }{ errorText }</ChakraField.ErrorText>
) }
</ChakraField.Label>
{ helperText && (
<ChakraField.HelperText>{ helperText }</ChakraField.HelperText>
) }
</ChakraField.Root>
);
}
return (
<ChakraField.Root ref={ ref } { ...rest }>
{ label && (
......
import type { InputProps as ChakraInputProps } from '@chakra-ui/react';
import { Input as ChakraInput } from '@chakra-ui/react';
export interface InputProps extends ChakraInputProps {}
export const Input = ChakraInput;
......@@ -138,12 +138,39 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
neutral: { value: { _light: '{colors.blackAlpha.50}', _dark: '{colors.whiteAlpha.100}' } },
},
},
input: {
fg: {
DEFAULT: { value: { _light: '{colors.blue.800}', _dark: '{colors.gray.50}' } },
error: { value: '{colors.text.error}' },
},
bg: {
DEFAULT: { value: { _light: '{colors.white}', _dark: '{colors.black}' } },
readOnly: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.800}' } },
},
border: {
DEFAULT: { value: { _light: '{colors.gray.100}', _dark: '{colors.gray.700}' } },
hover: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.500}' } },
focus: { value: '{colors.blue.400}' },
filled: { value: { _light: '{colors.gray.300}', _dark: '{colors.gray.600}' } },
readOnly: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.800}' } },
error: { value: '{colors.red.500}' },
},
},
field: {
placeholder: {
DEFAULT: { value: '{colors.gray.500}' },
disabled: { value: '{colors.gray.500/20}' },
error: { value: '{colors.red.500}' },
},
},
text: {
primary: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } },
secondary: { value: { _light: '{colors.gray.500}', _dark: '{colors.gray.400}' } },
error: { value: '{colors.red.500}' },
},
border: {
divider: { value: { _light: '{colors.blackAlpha.200}', _dark: '{colors.whiteAlpha.200}' } },
error: { value: '{colors.red.500}' },
},
global: {
body: {
......
import { defineSlotRecipe } from '@chakra-ui/react';
export const recipe = defineSlotRecipe({
className: 'chakra-field',
slots: [ 'root', 'label', 'requiredIndicator', 'errorText', 'helperText' ],
base: {
requiredIndicator: {
color: 'inherit',
lineHeight: 'inherit',
},
root: {
display: 'flex',
width: '100%',
position: 'relative',
gap: '1.5',
},
label: {
display: 'flex',
alignItems: 'center',
textAlign: 'start',
textStyle: 'sm',
fontWeight: '500',
gap: '0',
userSelect: 'none',
_disabled: {
opacity: '0.5',
},
_invalid: {
color: 'input.fg.error',
},
},
errorText: {
display: 'inline-flex',
alignItems: 'center',
fontWeight: 'medium',
gap: '1',
color: 'input.fg.error',
textStyle: 'xs',
},
helperText: {
color: 'fg.muted',
textStyle: 'xs',
},
},
variants: {
floating: {
'true': {
label: {
pos: 'absolute',
bg: 'bg',
top: '2px',
left: '2px',
w: 'calc(100% - 4px)',
borderRadius: 'base',
pointerEvents: 'none',
transformOrigin: 'top left',
transitionProperty: 'font-size, line-height, padding, background-color',
transitionDuration: 'fast',
transitionTimingFunction: 'ease',
},
},
},
size: {
sm: {
label: {
fontSize: 'sm',
},
},
md: {
label: {
fontSize: 'md',
},
},
lg: {
label: {
fontSize: 'md',
},
},
xl: {
label: {
fontSize: 'md',
},
},
},
orientation: {
vertical: {
root: {
flexDirection: 'column',
alignItems: 'flex-start',
},
},
horizontal: {
root: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
label: {
flex: '0 0 var(--field-label-width, 80px)',
},
},
},
},
compoundVariants: [
{
size: 'xl',
floating: true,
css: {
label: {
padding: '10px 16px 0px 16px',
textStyle: 'xs',
_peerPlaceholderShown: {
padding: '16px',
textStyle: 'md',
},
_peerFocusVisible: {
padding: '10px 16px 0px 16px',
textStyle: 'xs',
},
_readOnly: {
bg: 'input.bg.readOnly',
},
},
errorText: {
fontSize: 'inherit',
lineHeight: 'inherit',
},
},
},
],
defaultVariants: {
floating: false,
orientation: 'vertical',
},
});
import { recipe as alert } from './alert.recipe';
import { recipe as button } from './button.recipe';
import { recipe as field } from './field.recipe';
import { recipe as input } from './input.recipe';
import { recipe as link } from './link.recipe';
import { recipe as popover } from './popover.recipe';
import { recipe as progressCircle } from './progress-circle.recipe';
......@@ -11,12 +13,14 @@ import { recipe as tooltip } from './tooltip.recipe';
export const recipes = {
button,
input,
link,
skeleton,
};
export const slotRecipes = {
alert,
field,
popover,
progressCircle,
'switch': switchRecipe,
......
import { defineRecipe } from '@chakra-ui/react';
export const recipe = defineRecipe({
base: {
width: '100%',
minWidth: '0',
outline: '0',
position: 'relative',
appearance: 'none',
textAlign: 'start',
borderRadius: 'base',
height: 'var(--input-height)',
minW: 'var(--input-height)',
color: 'input.fg',
'--focus-color': 'colors.border.error',
'--error-color': 'colors.border.error',
_invalid: {
focusRingColor: 'var(--error-color)',
borderColor: 'var(--error-color)',
},
_autofill: {
// FIXME: this is not working
// WebkitTextFillColor: '{colors.input.fg}',
},
},
variants: {
size: {
sm: {
textStyle: 'sm',
px: '2',
'--input-height': 'sizes.8',
},
md: {
textStyle: 'md',
px: '2',
'--input-height': 'sizes.10',
},
lg: {
textStyle: 'md',
px: '3',
'--input-height': 'sizes.12',
},
xl: {
textStyle: 'md',
px: '4',
'--input-height': '60px',
},
},
variant: {
outline: {
bg: 'input.bg',
borderWidth: '2px',
borderColor: 'input.border',
focusVisibleRing: 'none',
_hover: {
borderColor: 'input.border.hover',
},
_focus: {
borderColor: 'input.border.focus',
_hover: {
borderColor: 'input.border.focus',
},
},
_readOnly: {
userSelect: 'all',
pointerEvents: 'none',
bg: 'input.bg.readOnly',
borderColor: 'input.border.readOnly',
_focus: {
borderColor: 'input.border.readOnly',
},
_hover: {
borderColor: 'input.border.readOnly',
},
},
_disabled: {
bg: 'input.bg.disabled',
borderColor: 'input.border.disabled',
},
_invalid: {
borderColor: 'input.border.error',
},
},
},
floating: {
'true': {},
},
},
compoundVariants: [
{
size: 'xl',
floating: true,
css: {
padding: '26px 10px 10px 16px',
},
},
],
defaultVariants: {
size: 'md',
variant: 'outline',
floating: false,
},
});
......@@ -5,6 +5,8 @@ import React from 'react';
import { Alert } from 'toolkit/chakra/alert';
import { Button } from 'toolkit/chakra/button';
import { useColorMode } from 'toolkit/chakra/color-mode';
import { Field } from 'toolkit/chakra/field';
import { Input } from 'toolkit/chakra/input';
import { ProgressCircleRing, ProgressCircleRoot } from 'toolkit/chakra/progress-circle';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { Switch } from 'toolkit/chakra/switch';
......@@ -37,6 +39,62 @@ const ChakraShowcases = () => {
</HStack>
</section>
<section>
<Heading textStyle="heading.md" mb={ 2 }>Inputs</Heading>
<Heading textStyle="heading.sm" mb={ 2 }>Regular</Heading>
<HStack gap={ 4 } whiteSpace="nowrap">
<Field label="Email" required>
<Input type="email"/>
</Field>
<Field label="Email">
<Input value="me@example.com"/>
</Field>
<Field label="Email" invalid>
<Input value="duck"/>
</Field>
<Field label="Email" readOnly>
<Input value="duck"/>
</Field>
<Field label="Email" disabled>
<Input value="duck"/>
</Field>
</HStack>
<HStack gap={ 4 } whiteSpace="nowrap" mt={ 4 } alignItems="flex-start">
<Field label="Email" required size="sm">
<Input/>
</Field>
<Field label="Email" required size="md">
<Input/>
</Field>
<Field label="Email" required size="lg">
<Input/>
</Field>
<Field label="Email" required size="xl">
<Input/>
</Field>
</HStack>
<Heading textStyle="heading.sm" mb={ 2 } mt={ 6 }>Floating (only XL size)</Heading>
<HStack gap={ 4 } mt={ 4 } alignItems="flex-start">
<Field label="Email" required floating size="xl" w="300px">
<Input type="email"/>
</Field>
<Field label="Email" required floating invalid errorText="Something went wrong" size="xl" w="300px">
<Input type="email"/>
</Field>
</HStack>
<HStack p={ 6 } mt={ 4 } gap={ 4 } bgColor={{ _light: 'blackAlpha.200', _dark: 'whiteAlpha.200' }} >
<Field label="Email" required floating size="xl" w="300px">
<Input type="email"/>
</Field>
<Field label="Email" required floating disabled size="xl" w="300px">
<Input type="email" value="me@example.com"/>
</Field>
<Field label="Email" required floating readOnly size="xl" w="300px">
<Input type="email" value="me@example.com"/>
</Field>
</HStack>
</section>
<section>
<Heading textStyle="heading.md" mb={ 2 }>Links</Heading>
<HStack gap={ 4 }>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment