@latte-macchiat-io/latte-vanilla-components 0.0.268 → 0.0.270

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.
Files changed (26) hide show
  1. package/package.json +1 -1
  2. package/src/components/Actions/styles.css.ts +1 -1
  3. package/src/components/Form/Row/styles.css.ts +1 -1
  4. package/src/components/Form/Row/theme.tsx +12 -18
  5. package/src/components/Form/TextField/Input/export.tsx +3 -6
  6. package/src/components/Form/TextField/Input/index.tsx +13 -0
  7. package/src/components/Form/TextField/Input/{Input.css.ts → styles.css.ts} +16 -54
  8. package/src/components/Form/TextField/Input/theme.tsx +57 -0
  9. package/src/components/Form/TextField/Input/types.tsx +1 -0
  10. package/src/components/Form/TextField/Label/export.tsx +2 -4
  11. package/src/components/Form/TextField/Label/{Label.tsx → index.tsx} +1 -1
  12. package/src/components/Form/TextField/Textarea/export.tsx +2 -6
  13. package/src/components/Form/TextField/Textarea/{Textarea.tsx → index.tsx} +1 -1
  14. package/src/components/Form/TextField/export.tsx +5 -3
  15. package/src/components/Form/TextField/index.tsx +51 -0
  16. package/src/components/Form/TextField/styles.css.ts +32 -0
  17. package/src/components/Form/TextField/theme.tsx +54 -0
  18. package/src/components/Form/export.tsx +1 -13
  19. package/src/components/Form/theme.tsx +0 -4
  20. package/src/theme/baseThemeValues.ts +13 -2
  21. package/src/theme/contract.css.ts +46 -6
  22. package/src/components/Form/TextField/Input/Input.tsx +0 -134
  23. package/src/components/Form/TextField/TextField.css.ts +0 -142
  24. package/src/components/Form/TextField/TextField.tsx +0 -197
  25. /package/src/components/Form/TextField/Label/{Label.css.ts → styles.css.ts} +0 -0
  26. /package/src/components/Form/TextField/Textarea/{Textarea.css.ts → styles.css.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latte-macchiat-io/latte-vanilla-components",
3
- "version": "0.0.268",
3
+ "version": "0.0.270",
4
4
  "description": "Beautiful components for amazing projects, with a touch of Vanilla 🥤",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -14,7 +14,7 @@ export const actionsRecipe = recipe(
14
14
  '@media': {
15
15
  ...generateResponsiveMedia({
16
16
  gap: themeContract.actions.gap,
17
- paddingTop: themeContract.actions.paddingBottom,
17
+ paddingTop: themeContract.actions.paddingTop,
18
18
  paddingBottom: themeContract.actions.paddingBottom,
19
19
  }),
20
20
  },
@@ -12,7 +12,7 @@ export const rowRecipe = recipe(
12
12
 
13
13
  '@media': {
14
14
  ...generateResponsiveMedia({
15
- gap: themeContract.form.row.gap,
15
+ gap: themeContract.formRow.gap,
16
16
  }),
17
17
  },
18
18
  },
@@ -1,30 +1,24 @@
1
1
  const themeFormRowBase = {
2
- form: {
3
- row: {
4
- gap: {
5
- mobile: '15px',
6
- sm: '15px',
7
- md: '30px',
8
- lg: '30px',
9
- xl: '50px',
10
- '2xl': '50px',
11
- },
2
+ row: {
3
+ gap: {
4
+ mobile: '15px',
5
+ sm: '15px',
6
+ md: '30px',
7
+ lg: '30px',
8
+ xl: '50px',
9
+ '2xl': '50px',
12
10
  },
13
11
  },
14
12
  };
15
13
 
16
14
  export const themeFormRowLight = {
17
- form: {
18
- row: {
19
- ...themeFormRowBase.form.row,
20
- },
15
+ row: {
16
+ ...themeFormRowBase.row,
21
17
  },
22
18
  };
23
19
 
24
20
  export const themeFormRowDark = {
25
- form: {
26
- row: {
27
- ...themeFormRowBase.form.row,
28
- },
21
+ row: {
22
+ ...themeFormRowBase.row,
29
23
  },
30
24
  };
@@ -1,6 +1,3 @@
1
- // export { TextFieldInput } from '.';
2
- // export type { TextFieldInputProps } from '.';
3
-
4
- // export { Type as InputType } from './types';
5
-
6
- // export { styles as InputStyles } from './styles.css';
1
+ export { Input, type InputProps } from './';
2
+ export { type InputVariants } from './styles.css';
3
+ export { type InputType as InputFieldType } from './types';
@@ -0,0 +1,13 @@
1
+ import { clsx } from 'clsx';
2
+ import { inputRecipe, type InputVariants } from './styles.css';
3
+
4
+ import { InputType } from './types';
5
+
6
+ export type InputProps = React.InputHTMLAttributes<HTMLInputElement> &
7
+ InputVariants & {
8
+ type?: InputType;
9
+ };
10
+
11
+ export const Input = ({ name, type = 'text', value, required, placeholder, className }: InputProps) => (
12
+ <input id={name} name={name} type={type} value={value} required={required} placeholder={placeholder} className={clsx(inputRecipe(), className)} />
13
+ );
@@ -1,19 +1,27 @@
1
1
  import { style } from '@vanilla-extract/css';
2
2
  import { recipe, RecipeVariants } from '@vanilla-extract/recipes';
3
3
  import { themeContract } from '../../../../theme/contract.css';
4
+ import { generateResponsiveMedia } from '../../../../utils/generateResponsiveMedia';
4
5
 
5
6
  const inputBase = style({
7
+ width: '100%',
6
8
  appearance: 'none',
7
- backgroundColor: themeContract.colors.background,
8
- border: `1px solid ${themeContract.colors.border}`,
9
- borderRadius: themeContract.radii.md,
10
- color: themeContract.colors.text,
9
+ transition: 'all 0.2s ease-in-out',
10
+
11
11
  fontFamily: themeContract.fonts.body,
12
- fontSize: themeContract.fontSizes.sm,
13
- lineHeight: themeContract.lineHeights.normal,
12
+ color: themeContract.form.textField.color,
13
+ border: themeContract.form.textField.border,
14
+ borderRadius: themeContract.form.textField.borderRadius,
15
+ backgroundColor: themeContract.form.textField.backgroundColor,
16
+
14
17
  padding: `${themeContract.space.sm} ${themeContract.space.md}`,
15
- transition: 'all 0.2s ease-in-out',
16
- width: '100%',
18
+
19
+ '@media': {
20
+ ...generateResponsiveMedia({
21
+ paddingTop: themeContract.form.textField.paddingTop,
22
+ paddingBottom: themeContract.form.textField.paddingBottom,
23
+ }),
24
+ },
17
25
 
18
26
  // '::placeholder': {
19
27
  // color: themeContract.colors.textSecondary,
@@ -56,52 +64,6 @@ const inputBase = style({
56
64
  export const inputRecipe = recipe(
57
65
  {
58
66
  base: inputBase,
59
-
60
- variants: {
61
- size: {
62
- sm: {
63
- fontSize: themeContract.fontSizes.xs,
64
- padding: `${themeContract.space.xs} ${themeContract.space.sm}`,
65
- },
66
- md: {},
67
- lg: {
68
- fontSize: themeContract.fontSizes.md,
69
- padding: `${themeContract.space.md} ${themeContract.space.lg}`,
70
- },
71
- },
72
- variant: {
73
- default: {},
74
- filled: {
75
- backgroundColor: themeContract.colors.surface,
76
- border: 'none',
77
-
78
- // ':focus': {
79
- // backgroundColor: themeContract.colors.background,
80
- // border: `1px solid ${themeContract.colors.primary}`,
81
- // },
82
- },
83
- outlined: {
84
- backgroundColor: 'transparent',
85
- },
86
- underlined: {
87
- backgroundColor: 'transparent',
88
- border: 'none',
89
- borderBottom: `1px solid ${themeContract.colors.border}`,
90
- borderRadius: 0,
91
- padding: `${themeContract.space.sm} 0`,
92
-
93
- // ':focus': {
94
- // borderBottom: `2px solid ${themeContract.colors.primary}`,
95
- // outline: 'none',
96
- // },
97
- },
98
- },
99
- },
100
-
101
- defaultVariants: {
102
- size: 'md',
103
- variant: 'default',
104
- },
105
67
  },
106
68
  'input'
107
69
  );
@@ -0,0 +1,57 @@
1
+ const themeFormTextFieldInputBase = {
2
+ form: {
3
+ textField: {
4
+ borderRadius: '30px',
5
+ paddingTop: {
6
+ mobile: '15px',
7
+ sm: '15px',
8
+ md: '30px',
9
+ lg: '30px',
10
+ xl: '50px',
11
+ '2xl': '50px',
12
+ },
13
+ paddingBottom: {
14
+ mobile: '15px',
15
+ sm: '15px',
16
+ md: '30px',
17
+ lg: '30px',
18
+ xl: '50px',
19
+ '2xl': '50px',
20
+ },
21
+ paddingRight: {
22
+ mobile: '15px',
23
+ sm: '15px',
24
+ md: '30px',
25
+ lg: '30px',
26
+ xl: '50px',
27
+ '2xl': '50px',
28
+ },
29
+ paddingLeft: {
30
+ mobile: '15px',
31
+ sm: '15px',
32
+ md: '30px',
33
+ lg: '30px',
34
+ xl: '50px',
35
+ '2xl': '50px',
36
+ },
37
+ },
38
+ },
39
+ };
40
+
41
+ export const themeFormTextFieldInputLight = {
42
+ form: {
43
+ textField: {
44
+ ...themeFormTextFieldInputBase.form,
45
+ backgroundColor: '#000000',
46
+ },
47
+ },
48
+ };
49
+
50
+ export const themeFormTextFieldInputDark = {
51
+ form: {
52
+ textField: {
53
+ ...themeFormTextFieldInputBase.form,
54
+ backgroundColor: '#000000',
55
+ },
56
+ },
57
+ };
@@ -0,0 +1 @@
1
+ export type InputType = 'text' | 'email' | 'search' | 'number' | 'hidden' | 'password' | 'tel' | 'url' | 'date' | 'time' | 'datetime-local' | 'color';
@@ -1,4 +1,2 @@
1
- // export { TextFieldLabel } from '.';
2
- // export type { TextFieldLabelProps } from '.';
3
- //
4
- // export { styles as TextFieldLabelStyles } from './styles.css';
1
+ export { Label, type LabelProps } from './index';
2
+ export { type LabelVariants } from './styles.css';
@@ -1,6 +1,6 @@
1
1
  import { clsx } from 'clsx';
2
2
  import { forwardRef } from 'react';
3
- import { labelRecipe, type LabelVariants } from './Label.css';
3
+ import { labelRecipe, type LabelVariants } from './styles.css';
4
4
  import { sprinkles, type Sprinkles } from '../../../../styles/sprinkles.css';
5
5
 
6
6
  export interface LabelProps extends Omit<React.LabelHTMLAttributes<HTMLLabelElement>, 'color'>, Sprinkles, NonNullable<LabelVariants> {
@@ -1,6 +1,2 @@
1
- // export { TextFieldTextarea } from '.';
2
- // export type { TextFieldTextareaProps } from '.';
3
-
4
- // export { Type as TextareaType } from './types';
5
-
6
- // export { styles as TextareaStyles } from './styles.css';
1
+ export { Textarea, type TextareaProps } from './';
2
+ export { type TextareaVariants } from './styles.css';
@@ -1,6 +1,6 @@
1
1
  import { clsx } from 'clsx';
2
2
  import { forwardRef } from 'react';
3
- import { textareaRecipe, type TextareaVariants } from './Textarea.css';
3
+ import { textareaRecipe, type TextareaVariants } from './styles.css';
4
4
  import { sprinkles, type Sprinkles } from '../../../../styles/sprinkles.css';
5
5
 
6
6
  export interface TextareaProps
@@ -1,4 +1,6 @@
1
- // export { TextField } from '.';
2
- // export type { TextFieldProps } from '.';
1
+ export { TextField, type TextFieldProps, type InputType } from './';
2
+ export { type TextFieldVariants } from './styles.css';
3
3
 
4
- // export { styles as TextFieldStyles } from './styles.css';
4
+ export * from './Input/export';
5
+ export * from './Label/export';
6
+ export * from './Textarea/export';
@@ -0,0 +1,51 @@
1
+ import { clsx } from 'clsx';
2
+ import { useMemo } from 'react';
3
+ import { Input } from './Input';
4
+ import { InputType } from './Input/types';
5
+ import { errorMessage, messageContainer, textFieldRecipe } from './styles.css';
6
+
7
+ export type TextFieldProps = React.HTMLAttributes<HTMLDivElement> & {
8
+ name: string;
9
+ label?: string;
10
+ value?: string;
11
+ rows?: number;
12
+ required?: boolean;
13
+ placeholder?: string;
14
+ errors?: string | string[];
15
+ type?: InputType | 'textarea';
16
+ onChange?: (e: { target: { value: string | undefined } }) => void | undefined;
17
+ };
18
+
19
+ export const TextField = (props: TextFieldProps) => {
20
+ const hasErrors = useMemo(() => {
21
+ if (!props.errors) return false;
22
+ if (Array.isArray(props.errors)) return props.errors.length > 0;
23
+ return Boolean(props.errors);
24
+ }, [props.errors]);
25
+
26
+ const isTextarea = props.type === 'textarea';
27
+
28
+ return (
29
+ <div className={clsx(textFieldRecipe(), props.className)}>
30
+ {props.label && <label htmlFor={props.name}>{props.label}</label>}
31
+
32
+ {isTextarea ? <textarea /> : <Input {...props} />}
33
+
34
+ {hasErrors && (
35
+ <div className={messageContainer}>
36
+ {Array.isArray(props.errors) ? (
37
+ props.errors.map((error, index) => (
38
+ <span key={index} className={errorMessage}>
39
+ {error}
40
+ </span>
41
+ ))
42
+ ) : (
43
+ <span className={errorMessage}>{props.errors}</span>
44
+ )}
45
+ </div>
46
+ )}
47
+ </div>
48
+ );
49
+ };
50
+
51
+ TextField.displayName = 'TextField';
@@ -0,0 +1,32 @@
1
+ import { style } from '@vanilla-extract/css';
2
+ import { recipe, RecipeVariants } from '@vanilla-extract/recipes';
3
+ import { themeContract } from '../../../theme/contract.css';
4
+
5
+ const textFieldBase = style({
6
+ width: '100%',
7
+ display: 'flex',
8
+ flexDirection: 'column',
9
+ });
10
+
11
+ const messageContainer = style({
12
+ display: 'flex',
13
+ flexDirection: 'column',
14
+ gap: themeContract.space.xs,
15
+ });
16
+
17
+ const errorMessage = style({
18
+ color: themeContract.colors.error,
19
+ fontSize: themeContract.fontSizes.xs,
20
+ lineHeight: themeContract.lineHeights.tight,
21
+ fontFamily: themeContract.fonts.body,
22
+ });
23
+
24
+ export const textFieldRecipe = recipe(
25
+ {
26
+ base: textFieldBase,
27
+ },
28
+ 'text-field'
29
+ );
30
+
31
+ export { messageContainer, errorMessage };
32
+ export type TextFieldVariants = RecipeVariants<typeof textFieldRecipe>;
@@ -0,0 +1,54 @@
1
+ const themeFormTextFieldBase = {
2
+ textField: {
3
+ border: '1px solid red',
4
+ borderRadius: '30px',
5
+ paddingTop: {
6
+ mobile: '15px',
7
+ sm: '15px',
8
+ md: '30px',
9
+ lg: '30px',
10
+ xl: '50px',
11
+ '2xl': '50px',
12
+ },
13
+ paddingBottom: {
14
+ mobile: '15px',
15
+ sm: '15px',
16
+ md: '30px',
17
+ lg: '30px',
18
+ xl: '50px',
19
+ '2xl': '50px',
20
+ },
21
+ paddingRight: {
22
+ mobile: '15px',
23
+ sm: '15px',
24
+ md: '30px',
25
+ lg: '30px',
26
+ xl: '50px',
27
+ '2xl': '50px',
28
+ },
29
+ paddingLeft: {
30
+ mobile: '15px',
31
+ sm: '15px',
32
+ md: '30px',
33
+ lg: '30px',
34
+ xl: '50px',
35
+ '2xl': '50px',
36
+ },
37
+ },
38
+ };
39
+
40
+ export const themeFormTextFieldLight = {
41
+ textField: {
42
+ ...themeFormTextFieldBase.textField,
43
+ color: '#000000',
44
+ backgroundColor: '#000000',
45
+ },
46
+ };
47
+
48
+ export const themeFormTextFieldDark = {
49
+ textField: {
50
+ ...themeFormTextFieldBase.textField,
51
+ color: '#000000',
52
+ backgroundColor: '#000000',
53
+ },
54
+ };
@@ -1,16 +1,4 @@
1
1
  export { Form, type FormProps } from '.';
2
2
 
3
3
  export * from './Row/export';
4
-
5
- export { TextField, type TextFieldProps, type InputType } from '../Form/TextField/TextField';
6
- export { type TextFieldVariants } from '../Form/TextField/TextField.css';
7
-
8
- // Form TextField Subcomponents
9
- export { Label, type LabelProps } from '../Form/TextField/Label/Label';
10
- export { type LabelVariants } from '../Form/TextField/Label/Label.css';
11
-
12
- export { Input, type InputProps, type InputType as InputFieldType } from '../Form/TextField/Input/Input';
13
- export { type InputVariants } from '../Form/TextField/Input/Input.css';
14
-
15
- export { Textarea, type TextareaProps } from '../Form/TextField/Textarea/Textarea';
16
- export { type TextareaVariants } from '../Form/TextField/Textarea/Textarea.css';
4
+ export * from './TextField/export';
@@ -1,5 +1,3 @@
1
- import { themeFormRowDark, themeFormRowLight } from './Row/theme';
2
-
3
1
  const themeFormBase = {
4
2
  form: {
5
3
  borderRadius: '30px',
@@ -57,7 +55,6 @@ const themeFormBase = {
57
55
  export const themeFormLight = {
58
56
  form: {
59
57
  ...themeFormBase.form,
60
- ...themeFormRowLight.form,
61
58
  backgroundColor: '#000000',
62
59
  },
63
60
  };
@@ -65,7 +62,6 @@ export const themeFormLight = {
65
62
  export const themeFormDark = {
66
63
  form: {
67
64
  ...themeFormBase.form,
68
- ...themeFormRowDark.form,
69
65
  backgroundColor: '#000000',
70
66
  },
71
67
  };
@@ -3,6 +3,8 @@ import { themeButtonDark, themeButtonLight } from '../components/Button/theme';
3
3
  import { themeCarouselDark, themeCarouselLight } from '../components/Carousel/theme';
4
4
  import { themeColumnsDark, themeColumnsLight } from '../components/Columns/theme';
5
5
  import { themeFooterDark, themeFooterLight } from '../components/Footer/theme';
6
+ import { themeFormRowDark, themeFormRowLight } from '../components/Form/Row/theme';
7
+ import { themeFormTextFieldDark, themeFormTextFieldLight } from '../components/Form/TextField/theme';
6
8
  import { themeFormDark, themeFormLight } from '../components/Form/theme';
7
9
  import { themeHeaderDark, themeHeaderLight } from '../components/Header/theme';
8
10
  import { themeHeadingDark, themeHeadingLight } from '../components/Heading/theme';
@@ -140,7 +142,12 @@ export const baseLightTheme = {
140
142
  ...themeNavSocialLight,
141
143
 
142
144
  ...themeVideoLight,
143
- ...themeFormLight,
145
+
146
+ form: {
147
+ ...themeFormLight.form,
148
+ ...themeFormRowLight,
149
+ ...themeFormTextFieldLight,
150
+ },
144
151
  };
145
152
 
146
153
  export const baseDarkTheme = {
@@ -269,5 +276,9 @@ export const baseDarkTheme = {
269
276
 
270
277
  ...themeVideoDark,
271
278
 
272
- ...themeFormDark,
279
+ form: {
280
+ ...themeFormDark.form,
281
+ ...themeFormRowDark,
282
+ ...themeFormTextFieldDark,
283
+ },
273
284
  };
@@ -906,14 +906,54 @@ export const themeContract = createGlobalThemeContract({
906
906
  xl: 'latte-form-paddingLeft-xl',
907
907
  '2xl': 'latte-form-paddingLeft-2xl',
908
908
  },
909
+
909
910
  row: {
910
911
  gap: {
911
- mobile: 'latte-form-row-gap-mobile',
912
- sm: 'latte-form-row-gap-sm',
913
- md: 'latte-form-row-gap-md',
914
- lg: 'latte-form-row-gap-lg',
915
- xl: 'latte-form-row-gap-xl',
916
- '2xl': 'latte-form-row-gap-2xl',
912
+ mobile: 'latte-formRow-gap-mobile',
913
+ sm: 'latte-formRow-gap-sm',
914
+ md: 'latte-formRow-gap-md',
915
+ lg: 'latte-formRow-gap-lg',
916
+ xl: 'latte-formRow-gap-xl',
917
+ '2xl': 'latte-formRow-gap-2xl',
918
+ },
919
+ },
920
+
921
+ textField: {
922
+ color: 'latte-textField-color',
923
+ border: 'latte-textField-border',
924
+ borderRadius: 'latte-textField-borderRadius',
925
+ backgroundColor: 'latte-textField-backgroundColor',
926
+ paddingTop: {
927
+ mobile: 'latte-textField-input-paddingTop-mobile',
928
+ sm: 'latte-textField-input-paddingTop-sm',
929
+ md: 'latte-textField-input-paddingTop-md',
930
+ lg: 'latte-textField-input-paddingTop-lg',
931
+ xl: 'latte-textField-input-paddingTop-xl',
932
+ '2xl': 'latte-textField-input-paddingTop-2xl',
933
+ },
934
+ paddingBottom: {
935
+ mobile: 'latte-textField-input-paddingBottom-mobile',
936
+ sm: 'latte-textField-input-paddingBottom-sm',
937
+ md: 'latte-textField-input-paddingBottom-md',
938
+ lg: 'latte-textField-input-paddingBottom-lg',
939
+ xl: 'latte-textField-input-paddingBottom-xl',
940
+ '2xl': 'latte-textField-input-paddingBottom-2xl',
941
+ },
942
+ paddingRight: {
943
+ mobile: 'latte-textField-input-paddingRight-mobile',
944
+ sm: 'latte-textField-input-paddingRight-sm',
945
+ md: 'latte-textField-input-paddingRight-md',
946
+ lg: 'latte-textField-input-paddingRight-lg',
947
+ xl: 'latte-textField-input-paddingRight-xl',
948
+ '2xl': 'latte-textField-input-paddingRight-2xl',
949
+ },
950
+ paddingLeft: {
951
+ mobile: 'latte-textField-input-paddingLeft-mobile',
952
+ sm: 'latte-textField-input-paddingLeft-sm',
953
+ md: 'latte-textField-input-paddingLeft-md',
954
+ lg: 'latte-textField-input-paddingLeft-lg',
955
+ xl: 'latte-textField-input-paddingLeft-xl',
956
+ '2xl': 'latte-textField-input-paddingLeft-2xl',
917
957
  },
918
958
  },
919
959
  },
@@ -1,134 +0,0 @@
1
- import { clsx } from 'clsx';
2
- import { forwardRef } from 'react';
3
- import { inputRecipe, type InputVariants } from './Input.css';
4
- import { sprinkles, type Sprinkles } from '../../../../styles/sprinkles.css';
5
-
6
- export type InputType = 'text' | 'email' | 'search' | 'number' | 'hidden' | 'password' | 'tel' | 'url' | 'date' | 'time' | 'datetime-local' | 'color';
7
-
8
- export interface InputProps
9
- extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'color' | 'size' | 'height' | 'width'>,
10
- Sprinkles,
11
- NonNullable<InputVariants> {
12
- name: string;
13
- type?: InputType;
14
- hasError?: boolean;
15
- }
16
-
17
- export const Input = forwardRef<HTMLInputElement, InputProps>(
18
- (
19
- {
20
- name,
21
- type = 'text',
22
- hasError = false,
23
- size,
24
- variant,
25
- className,
26
- // Extract sprinkles props
27
- margin,
28
- marginTop,
29
- marginBottom,
30
- marginLeft,
31
- marginRight,
32
- padding,
33
- paddingTop,
34
- paddingBottom,
35
- paddingLeft,
36
- paddingRight,
37
- gap,
38
- display,
39
- flexDirection,
40
- justifyContent,
41
- flexWrap,
42
- flex,
43
- width,
44
- height,
45
- minWidth,
46
- maxWidth,
47
- minHeight,
48
- position,
49
- top,
50
- bottom,
51
- left,
52
- right,
53
- zIndex,
54
- fontSize,
55
- fontFamily,
56
- lineHeight,
57
- textAlign,
58
- fontWeight,
59
- color,
60
- backgroundColor,
61
- borderRadius,
62
- borderWidth,
63
- borderStyle,
64
- borderColor,
65
- boxShadow,
66
- opacity,
67
- overflow,
68
- overflowX,
69
- overflowY,
70
- ...htmlProps
71
- },
72
- ref
73
- ) => {
74
- return (
75
- <input
76
- ref={ref}
77
- id={name}
78
- name={name}
79
- type={type}
80
- className={clsx(
81
- inputRecipe({ size, variant }),
82
- sprinkles({
83
- margin,
84
- marginTop,
85
- marginBottom,
86
- marginLeft,
87
- marginRight,
88
- padding,
89
- paddingTop,
90
- paddingBottom,
91
- paddingLeft,
92
- paddingRight,
93
- gap,
94
- display,
95
- flexDirection,
96
- justifyContent,
97
- flexWrap,
98
- flex,
99
- width,
100
- height,
101
- minWidth,
102
- maxWidth,
103
- minHeight,
104
- position,
105
- top,
106
- bottom,
107
- left,
108
- right,
109
- zIndex,
110
- fontSize,
111
- fontFamily,
112
- lineHeight,
113
- textAlign,
114
- fontWeight,
115
- color,
116
- backgroundColor,
117
- borderRadius,
118
- borderWidth,
119
- borderStyle,
120
- borderColor,
121
- boxShadow,
122
- opacity,
123
- overflow,
124
- overflowX,
125
- overflowY,
126
- }),
127
- className
128
- )}
129
- data-error={hasError}
130
- {...htmlProps}
131
- />
132
- );
133
- }
134
- );
@@ -1,142 +0,0 @@
1
- import { style } from '@vanilla-extract/css';
2
- import { recipe, RecipeVariants } from '@vanilla-extract/recipes';
3
- import { themeContract } from '../../../theme/contract.css';
4
-
5
- const textFieldBase = style({
6
- display: 'flex',
7
- flexDirection: 'column',
8
- width: '100%',
9
- gap: themeContract.space.xs,
10
- });
11
-
12
- const labelBase = style({
13
- fontFamily: themeContract.fonts.body,
14
- fontSize: themeContract.fontSizes.sm,
15
- fontWeight: themeContract.fontWeights.medium,
16
- color: themeContract.colors.text,
17
- lineHeight: themeContract.lineHeights.tight,
18
-
19
- // selectors: {
20
- // '&[data-required="true"]': {
21
- // selectors: {
22
- // '&::after': {
23
- // content: ' *',
24
- // color: themeContract.colors.error,
25
- // },
26
- // },
27
- // },
28
- // },
29
- });
30
-
31
- const inputBase = style({
32
- appearance: 'none',
33
- backgroundColor: themeContract.colors.background,
34
- border: `1px solid ${themeContract.colors.border}`,
35
- borderRadius: themeContract.radii.md,
36
- color: themeContract.colors.text,
37
- fontFamily: themeContract.fonts.body,
38
- fontSize: themeContract.fontSizes.sm,
39
- lineHeight: themeContract.lineHeights.normal,
40
- padding: `${themeContract.space.sm} ${themeContract.space.md}`,
41
- transition: 'all 0.2s ease-in-out',
42
- width: '100%',
43
-
44
- '::placeholder': {
45
- color: themeContract.colors.textSecondary,
46
- },
47
-
48
- ':hover': {
49
- borderColor: themeContract.colors.primary,
50
- },
51
-
52
- ':focus': {
53
- outline: '2px solid',
54
- outlineColor: themeContract.colors.primary,
55
- outlineOffset: '2px',
56
- borderColor: themeContract.colors.primary,
57
- },
58
-
59
- ':disabled': {
60
- backgroundColor: themeContract.colors.surface,
61
- color: themeContract.colors.textSecondary,
62
- cursor: 'not-allowed',
63
- opacity: '0.6',
64
- },
65
-
66
- // selectors: {
67
- // '&[data-error="true"]': {
68
- // borderColor: themeContract.colors.error,
69
- // },
70
- // '&[data-error="true"]:focus': {
71
- // outlineColor: themeContract.colors.error,
72
- // borderColor: themeContract.colors.error,
73
- // },
74
- // },
75
- });
76
-
77
- const textareaBase = style([
78
- inputBase,
79
- {
80
- resize: 'vertical',
81
- minHeight: '80px',
82
- },
83
- ]);
84
-
85
- const messageContainer = style({
86
- display: 'flex',
87
- flexDirection: 'column',
88
- gap: themeContract.space.xs,
89
- });
90
-
91
- const errorMessage = style({
92
- color: themeContract.colors.error,
93
- fontSize: themeContract.fontSizes.xs,
94
- lineHeight: themeContract.lineHeights.tight,
95
- fontFamily: themeContract.fonts.body,
96
- });
97
-
98
- export const textFieldRecipe = recipe(
99
- {
100
- base: textFieldBase,
101
-
102
- variants: {
103
- size: {
104
- sm: {
105
- gap: themeContract.space.xs,
106
-
107
- // selectors: {
108
- // [`& .${labelBase}`]: {
109
- // fontSize: themeContract.fontSizes.xs,
110
- // },
111
- // [`& .${inputBase}`]: {
112
- // fontSize: themeContract.fontSizes.xs,
113
- // padding: `${themeContract.space.xs} ${themeContract.space.sm}`,
114
- // },
115
- // },
116
- },
117
- md: {},
118
- lg: {
119
- gap: themeContract.space.sm,
120
-
121
- // selectors: {
122
- // [`& .${labelBase}`]: {
123
- // fontSize: themeContract.fontSizes.md,
124
- // },
125
- // [`& .${inputBase}`]: {
126
- // fontSize: themeContract.fontSizes.md,
127
- // padding: `${themeContract.space.md} ${themeContract.space.lg}`,
128
- // },
129
- // },
130
- },
131
- },
132
- },
133
-
134
- defaultVariants: {
135
- size: 'md',
136
- },
137
- },
138
- 'text-field'
139
- );
140
-
141
- export { labelBase, inputBase, textareaBase, messageContainer, errorMessage };
142
- export type TextFieldVariants = RecipeVariants<typeof textFieldRecipe>;
@@ -1,197 +0,0 @@
1
- import { clsx } from 'clsx';
2
- import { forwardRef, useMemo } from 'react';
3
- import { errorMessage, inputBase, labelBase, messageContainer, textareaBase, textFieldRecipe, type TextFieldVariants } from './TextField.css';
4
- import { sprinkles, type Sprinkles } from '../../../styles/sprinkles.css';
5
-
6
- export type InputType = 'text' | 'email' | 'search' | 'number' | 'hidden' | 'password' | 'textarea';
7
-
8
- export interface TextFieldProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'onChange'>, Sprinkles, NonNullable<TextFieldVariants> {
9
- name: string;
10
- label?: string;
11
- value?: string;
12
- rows?: number;
13
- disabled?: boolean;
14
- required?: boolean;
15
- placeholder?: string;
16
- errors?: string | string[];
17
- type?: InputType;
18
- onChange?: (e: { target: { value: string | undefined } }) => void | undefined;
19
- }
20
-
21
- export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
22
- (
23
- {
24
- name,
25
- label,
26
- value = '',
27
- rows = 3,
28
- disabled = false,
29
- required = false,
30
- placeholder = '',
31
- errors,
32
- type = 'text',
33
- onChange,
34
- size,
35
- className,
36
- // Extract sprinkles props
37
- margin,
38
- marginTop,
39
- marginBottom,
40
- marginLeft,
41
- marginRight,
42
- padding,
43
- paddingTop,
44
- paddingBottom,
45
- paddingLeft,
46
- paddingRight,
47
- gap,
48
- display,
49
- flexDirection,
50
- justifyContent,
51
- flexWrap,
52
- flex,
53
- width,
54
- height,
55
- minWidth,
56
- maxWidth,
57
- minHeight,
58
- position,
59
- top,
60
- bottom,
61
- left,
62
- right,
63
- zIndex,
64
- fontSize,
65
- fontFamily,
66
- lineHeight,
67
- textAlign,
68
- fontWeight,
69
- color,
70
- backgroundColor,
71
- borderRadius,
72
- borderWidth,
73
- borderStyle,
74
- borderColor,
75
- boxShadow,
76
- opacity,
77
- overflow,
78
- overflowX,
79
- overflowY,
80
- ...htmlProps
81
- },
82
- ref
83
- ) => {
84
- const hasErrors = useMemo(() => {
85
- if (!errors) return false;
86
- if (Array.isArray(errors)) return errors.length > 0;
87
- return Boolean(errors);
88
- }, [errors]);
89
-
90
- const isTextarea = type === 'textarea';
91
-
92
- return (
93
- <div
94
- ref={ref}
95
- className={clsx(
96
- textFieldRecipe({ size }),
97
- sprinkles({
98
- margin,
99
- marginTop,
100
- marginBottom,
101
- marginLeft,
102
- marginRight,
103
- padding,
104
- paddingTop,
105
- paddingBottom,
106
- paddingLeft,
107
- paddingRight,
108
- gap,
109
- display,
110
- flexDirection,
111
- justifyContent,
112
- flexWrap,
113
- flex,
114
- width,
115
- height,
116
- minWidth,
117
- maxWidth,
118
- minHeight,
119
- position,
120
- top,
121
- bottom,
122
- left,
123
- right,
124
- zIndex,
125
- fontSize,
126
- fontFamily,
127
- lineHeight,
128
- textAlign,
129
- fontWeight,
130
- color,
131
- backgroundColor,
132
- borderRadius,
133
- borderWidth,
134
- borderStyle,
135
- borderColor,
136
- boxShadow,
137
- opacity,
138
- overflow,
139
- overflowX,
140
- overflowY,
141
- }),
142
- className
143
- )}
144
- {...htmlProps}>
145
- {label && (
146
- <label htmlFor={name} className={labelBase} data-required={required}>
147
- {label}
148
- </label>
149
- )}
150
-
151
- {isTextarea ? (
152
- <textarea
153
- id={name}
154
- name={name}
155
- rows={rows}
156
- value={value}
157
- disabled={disabled}
158
- placeholder={placeholder}
159
- className={textareaBase}
160
- onChange={onChange}
161
- data-error={hasErrors}
162
- required={required}
163
- />
164
- ) : (
165
- <input
166
- id={name}
167
- name={name}
168
- type={type}
169
- value={value}
170
- disabled={disabled}
171
- placeholder={placeholder}
172
- className={inputBase}
173
- onChange={onChange}
174
- data-error={hasErrors}
175
- required={required}
176
- />
177
- )}
178
-
179
- {hasErrors && (
180
- <div className={messageContainer}>
181
- {Array.isArray(errors) ? (
182
- errors.map((error, index) => (
183
- <span key={index} className={errorMessage}>
184
- {error}
185
- </span>
186
- ))
187
- ) : (
188
- <span className={errorMessage}>{errors}</span>
189
- )}
190
- </div>
191
- )}
192
- </div>
193
- );
194
- }
195
- );
196
-
197
- TextField.displayName = 'TextField';