@liguelead/design-system 0.0.25 → 0.0.26

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.
@@ -15,7 +15,7 @@ export const ButtonSizes = (size: ButtonSizeTypes) => {
15
15
  font-weight: ${fontWeight.fontWeight500};
16
16
  line-height: ${lineHeight.lineHeight22}px;
17
17
  gap: ${spacing.spacing8}px;
18
- min-width: ${spacing.spacing64}px;
18
+ height: ${spacing.spacing32}px;
19
19
  border-radius: ${radius.radius4}px;
20
20
  `,
21
21
  md: `
@@ -24,7 +24,7 @@ export const ButtonSizes = (size: ButtonSizeTypes) => {
24
24
  font-weight: ${fontWeight.fontWeight500};
25
25
  line-height: ${lineHeight.lineHeight22}px;
26
26
  gap: ${spacing.spacing8}px;
27
- min-width: ${spacing.spacing76}px;
27
+ height: ${spacing.spacing36}px;
28
28
  border-radius: ${radius.radius4}px;
29
29
  `,
30
30
  lg: `
@@ -33,7 +33,7 @@ export const ButtonSizes = (size: ButtonSizeTypes) => {
33
33
  font-weight: ${fontWeight.fontWeight500};
34
34
  line-height: ${lineHeight.lineHeight24}px;
35
35
  gap: ${spacing.spacing8}px;
36
- min-width: ${spacing.spacing108}px;
36
+ height: ${spacing.spacing40}px;
37
37
  border-radius: ${radius.radius4}px;
38
38
  `
39
39
  }
@@ -5,7 +5,7 @@ import { shadow } from '@liguelead/foundation'
5
5
  export interface StyledButtonProps extends ButtonProps {
6
6
  $variant: string
7
7
  $buttonSize: string
8
- $fluid: boolean
8
+ $fullWidth: boolean
9
9
  }
10
10
 
11
11
  export const StyledButton = styled.button<StyledButtonProps>`
@@ -14,7 +14,7 @@ export const StyledButton = styled.button<StyledButtonProps>`
14
14
  outline: none !important;
15
15
  justify-content: center;
16
16
  align-items: center;
17
- width: ${({ $fluid }) => ($fluid ? '100%' : 'auto')};
17
+ width: ${({ $fullWidth }) => ($fullWidth ? '100%' : 'auto')};
18
18
  ${({ $buttonSize }) => $buttonSize}
19
19
  overflow: hidden;
20
20
  cursor: pointer;
@@ -10,7 +10,7 @@ const Button: React.FC<ButtonProps> = ({
10
10
  className,
11
11
  color = 'primary',
12
12
  disabled,
13
- fluid = false,
13
+ fullWidth = false,
14
14
  size = 'md',
15
15
  onClick,
16
16
  type = 'button',
@@ -47,7 +47,7 @@ const Button: React.FC<ButtonProps> = ({
47
47
  <StyledButton
48
48
  disabled={disabled}
49
49
  className={className}
50
- $fluid={fluid}
50
+ $fullWidth={fullWidth}
51
51
  $variant={buttonVariant}
52
52
  onClick={handleClick}
53
53
  $buttonSize={buttonSize}
@@ -8,7 +8,7 @@ export interface ButtonProps {
8
8
  children: React.ReactNode
9
9
  className?: string
10
10
  disabled?: boolean
11
- fluid?: boolean
11
+ fullWidth?: boolean
12
12
  size?: ButtonSizeTypes
13
13
  color?: colorType
14
14
  onClick?: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
@@ -10,7 +10,7 @@ const IconButton: React.FC<ButtonProps> = ({
10
10
  className,
11
11
  color = 'primary',
12
12
  disabled,
13
- fluid = false,
13
+ fullWidth = false,
14
14
  size = 'md',
15
15
  onClick,
16
16
  ...rest
@@ -42,7 +42,7 @@ const IconButton: React.FC<ButtonProps> = ({
42
42
  <StyledButton
43
43
  disabled={disabled}
44
44
  className={className}
45
- $fluid={fluid}
45
+ $fullWidth={fullWidth}
46
46
  $variant={buttonVariant}
47
47
  onClick={handleClick}
48
48
  $buttonSize={buttonSize}
@@ -32,7 +32,7 @@ export const StyledLinkButton = styled.button<StyledButtonProps>`
32
32
  display: flex;
33
33
  outline: none !important;
34
34
 
35
- width: ${({ $fluid }) => ($fluid ? '100%' : 'auto')};
35
+ width: ${({ $fullWidth }) => ($fullWidth ? '100%' : 'auto')};
36
36
  ${({ $buttonSize }) => $buttonSize}
37
37
  overflow: hidden;
38
38
  cursor: pointer;
@@ -12,7 +12,7 @@ const LinkButton: React.FC<LinkButtonProps> = ({
12
12
  disabled = false,
13
13
  color = 'primary',
14
14
  variant = 'ghost',
15
- fluid = false,
15
+ fullWidth = false,
16
16
  leftIcon,
17
17
  rightIcon,
18
18
  onClick,
@@ -47,7 +47,7 @@ const LinkButton: React.FC<LinkButtonProps> = ({
47
47
  onClick={handleClick}
48
48
  $variant={buttonVariant}
49
49
  $buttonSize={buttonSize}
50
- $fluid={fluid}
50
+ $fullWidth={fullWidth}
51
51
  aria-disabled={disabled}
52
52
  rel={rest.target === '_blank' ? 'noopener noreferrer' : rest.rel}
53
53
  {...rest}
@@ -6,7 +6,7 @@ export interface LinkButtonProps extends React.AnchorHTMLAttributes<HTMLAnchorEl
6
6
  disabled?: boolean
7
7
  color?: colorType
8
8
  variant?: 'ghost'
9
- fluid?: boolean
9
+ fullWidth?: boolean
10
10
  leftIcon?: React.ReactNode
11
11
  rightIcon?: React.ReactNode
12
12
  size?: 'sm' | 'md' | 'lg'
@@ -18,6 +18,7 @@ export const textFieldSizes = (
18
18
  input: `
19
19
  font-size: ${fontSize.fontSize14}px;
20
20
  line-height: ${lineHeight.lineHeight22}px;
21
+ height: ${spacing.spacing32}px;
21
22
  padding: ${spacing.spacing8}px ${spacing.spacing12}px;
22
23
  padding-left: ${leftIcon ? withIconPadding.sm : spacing.spacing12}px;
23
24
  padding-right: ${rightIcon ? withIconPadding.sm : spacing.spacing12}px;
@@ -32,6 +33,7 @@ export const textFieldSizes = (
32
33
  font-size: ${fontSize.fontSize14}px;
33
34
  line-height: ${lineHeight.lineHeight20}px;
34
35
  padding: ${spacing.spacing12}px ${spacing.spacing16}px;
36
+ height: ${spacing.spacing36}px;
35
37
  padding-left: ${leftIcon ? withIconPadding.md : spacing.spacing16}px;
36
38
  padding-right: ${rightIcon ? withIconPadding.md : spacing.spacing16}px;
37
39
  `,
@@ -43,6 +45,7 @@ export const textFieldSizes = (
43
45
  font-size: ${fontSize.fontSize16}px;
44
46
  line-height: ${lineHeight.lineHeight24}px;
45
47
  padding: ${spacing.spacing12}px ${spacing.spacing16}px;
48
+ height: ${spacing.spacing40}px;
46
49
  padding-left: ${leftIcon ? withIconPadding.lg : spacing.spacing16}px;
47
50
  padding-right: ${rightIcon ? withIconPadding.lg : spacing.spacing16}px;
48
51
  `,
@@ -18,8 +18,9 @@ export const textFieldSizes = (size: TextFieldSize, leftIcon: boolean, rightIcon
18
18
  const sizes = {
19
19
  sm: {
20
20
  input: `
21
- font-size: ${fontSize.fontSize14}px;
22
- line-height: ${lineHeight.lineHeight22}px;
21
+ font-size: ${fontSize.fontSize14}px;
22
+ line-height: ${lineHeight.lineHeight22}px;
23
+ height: ${spacing.spacing32}px;
23
24
  padding: ${spacing.spacing8}px ${spacing.spacing12}px;
24
25
  padding-left: ${leftIcon ? withIconPadding.sm : spacing.spacing12}px;
25
26
  padding-right: ${rightIcon ? withIconPadding.sm : spacing.spacing12}px;
@@ -31,6 +32,7 @@ export const textFieldSizes = (size: TextFieldSize, leftIcon: boolean, rightIcon
31
32
  font-size: ${fontSize.fontSize14}px;
32
33
  line-height: ${lineHeight.lineHeight20}px;
33
34
  padding: ${spacing.spacing12}px ${spacing.spacing16}px;
35
+ height: ${spacing.spacing36}px;
34
36
  padding-left: ${leftIcon ? withIconPadding.md : spacing.spacing16}px;
35
37
  padding-right: ${rightIcon ? withIconPadding.md : spacing.spacing16}px;
36
38
  `,
@@ -41,6 +43,7 @@ export const textFieldSizes = (size: TextFieldSize, leftIcon: boolean, rightIcon
41
43
  font-size: ${fontSize.fontSize16}px;
42
44
  line-height: ${lineHeight.lineHeight24}px;
43
45
  padding: ${spacing.spacing12}px ${spacing.spacing16}px;
46
+ height: ${spacing.spacing40}px;
44
47
  padding-left: ${leftIcon ? withIconPadding.lg : spacing.spacing16}px;
45
48
  padding-right: ${rightIcon ? withIconPadding.lg : spacing.spacing16}px;
46
49
  `,
@@ -5,6 +5,8 @@ export interface StateInterface {
5
5
  input: string
6
6
  label?: string
7
7
  helperText: string
8
+ fileButton?: string
9
+ fileName?: string
8
10
  }
9
11
 
10
12
  interface TextFieldStates {
@@ -27,6 +29,21 @@ export const TextFieldStates = (state: keyof TextFieldStates) => {
27
29
  `,
28
30
  label: `color: ${parseColor(theme.colors.textDark)};`,
29
31
  helperText: `color: ${parseColor(theme.colors.danger200)};`,
32
+ fileButton: `
33
+ border: 1px solid ${parseColor(theme.colors.danger200)};
34
+ background: transparent;
35
+ border-right: none;
36
+ color: ${parseColor(theme.colors.primary)};
37
+ &:hover {
38
+ background: transparent;
39
+ }
40
+ `,
41
+ fileName: `
42
+ border: 1px solid ${parseColor(theme.colors.danger200)};
43
+ color: ${parseColor(theme.colors.textDark)};
44
+ background: ${parseColor(theme.colors.white)};
45
+ border-left: none;
46
+ `,
30
47
  },
31
48
  default: {
32
49
  input: `
@@ -39,6 +56,18 @@ export const TextFieldStates = (state: keyof TextFieldStates) => {
39
56
  `,
40
57
  label: `color: ${parseColor(theme.colors.textDark)};`,
41
58
  helperText: `color: ${parseColor(theme.colors.textMedium)};`,
59
+ fileButton: `
60
+ border: 1px solid ${parseColor(theme.colors.neutral400)};
61
+ background: transparent;
62
+ border-right: none;
63
+ color: ${parseColor(theme.colors.primary)};
64
+ `,
65
+ fileName: `
66
+ border: 1px solid ${parseColor(theme.colors.neutral400)};
67
+ color: ${parseColor(theme.colors.textDark)};
68
+ background: ${parseColor(theme.colors.white)};
69
+ border-left: none;
70
+ `,
42
71
  },
43
72
  disabled: {
44
73
  input: `
@@ -48,6 +77,21 @@ export const TextFieldStates = (state: keyof TextFieldStates) => {
48
77
  `,
49
78
  label: `color: ${parseColor(theme.colors.textDark)};`,
50
79
  helperText: `color: ${parseColor(theme.colors.neutral500)};`,
80
+ fileButton: `
81
+ border: 1px solid ${parseColor(theme.colors.neutral300)};
82
+ background: transparent;
83
+ border-right: none;
84
+ color: ${parseColor(theme.colors.primary)};
85
+ cursor: not-allowed;
86
+ opacity: 0.5;
87
+ `,
88
+ fileName: `
89
+ border: 1px solid ${parseColor(theme.colors.neutral300)};
90
+ background: ${parseColor(theme.colors.neutral50)};
91
+ color: ${parseColor(theme.colors.neutral500)};
92
+ opacity: 0.5;
93
+ border-left: none;
94
+ `,
51
95
  }
52
96
  }
53
97
  return states[state]
@@ -4,6 +4,7 @@ import {
4
4
  fontSize,
5
5
  fontWeight,
6
6
  lineHeight,
7
+ shadow,
7
8
  spacing
8
9
  } from '@liguelead/foundation'
9
10
  import { StateInterface } from './TextField.states'
@@ -14,9 +15,13 @@ interface StyledInputProps {
14
15
  $themefication: StateInterface
15
16
  }
16
17
 
17
- export const InputWrapper = styled.div`
18
+ export const InputWrapper = styled.div<{ $isDragging?: boolean }>`
18
19
  position: relative;
19
20
  width: 100%;
21
+
22
+ box-shadow: ${({ $isDragging }) => ($isDragging ? shadow.focusShadow : 'none')};
23
+ border-radius: 4px;
24
+ background: transparent;
20
25
  `
21
26
 
22
27
  export const Label = styled.label`
@@ -55,15 +60,14 @@ export const StyledInput = styled.input.withConfig({
55
60
  width: 100%;
56
61
  border-radius: 4px;
57
62
  outline: none;
58
- border: 1px solid #CFCFD1;
63
+ border: 1px solid ${parseColor('neutral400')};
59
64
  transition: border-color 0.2s ease;
60
65
  background: transparent;
61
66
  `
62
67
 
63
-
64
68
  export const Wrapper = styled.div<StyledInputProps>`
65
69
  position: relative;
66
- width: 100%;
70
+ max-width: 600px;
67
71
  display: flex;
68
72
  flex-direction: column;
69
73
  gap: ${spacing.spacing4}px;
@@ -79,5 +83,44 @@ export const Wrapper = styled.div<StyledInputProps>`
79
83
  ${HelperText} {
80
84
  ${$themefication.helperText}
81
85
  }
86
+ ${FileButton} {
87
+ ${$themefication.fileButton || $themefication.input}
88
+ ${size.input}
89
+ }
90
+ ${FileName} {
91
+ ${$themefication.fileName || $themefication.input}
92
+ ${size.input}
93
+ }
94
+ ${FileInputContainer} {
95
+ ${StyledInput} {
96
+ ${$themefication.input}
97
+ ${size.input}
98
+ }
99
+ }
82
100
  `}
83
101
  `
102
+
103
+ export const FileButton = styled.span`
104
+ display: inline-flex;
105
+ align-items: center;
106
+ font-weight: 500;
107
+ white-space: nowrap;
108
+ cursor: pointer;
109
+ border-radius: 4px 0 0 4px;
110
+ transition: all 0.2s ease;
111
+ `
112
+
113
+ export const FileName = styled.span`
114
+ flex: 1;
115
+ border-radius: 0 4px 4px 0;
116
+ border-left: none;
117
+ transition: all 0.2s ease;
118
+ display: flex;
119
+ align-items: center;
120
+ `
121
+
122
+ export const FileInputContainer = styled.div`
123
+ display: flex;
124
+ width: 100%;
125
+ position: relative;
126
+ `
@@ -2,6 +2,9 @@ import React, { forwardRef, useState } from 'react'
2
2
  import { TextFieldProps } from './TextField.types'
3
3
  import { StateInterface, TextFieldStates } from './TextField.states'
4
4
  import {
5
+ FileButton,
6
+ FileInputContainer,
7
+ FileName,
5
8
  HelperText,
6
9
  IconWrapper,
7
10
  InputWrapper,
@@ -9,9 +12,10 @@ import {
9
12
  StyledInput,
10
13
  Wrapper
11
14
  } from './TextField.styles'
15
+
12
16
  import { textFieldSizes } from './TextField.sizes'
13
- import { Eye, EyeClosed } from '@phosphor-icons/react'
14
- import getState from './utils/getState'
17
+ import { EyeIcon, EyeClosedIcon } from '@phosphor-icons/react'
18
+ import getState from './utils/getState'
15
19
  import RequiredAsterisk from '../RequiredAsterisk'
16
20
 
17
21
  const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
@@ -23,6 +27,8 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
23
27
  handleLeftIcon,
24
28
  handleRightIcon,
25
29
  helperText,
30
+ filePlaceholder = 'Nenhum arquivo selecionado',
31
+ fileButtonLabel = 'Escolher arquivo',
26
32
  label,
27
33
  leftIcon,
28
34
  onChange,
@@ -37,17 +43,39 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
37
43
  },
38
44
  ref
39
45
  ) => {
40
- const [passwordVisible, setPasswordVisible] = useState(
41
- type !== 'password'
42
- )
46
+ const [passwordVisible, setPasswordVisible] = useState(false)
47
+ const [selectedFileName, setSelectedFileName] = useState('')
43
48
  const state = getState(disabled, !!error)
44
49
  const textFieldState: StateInterface = TextFieldStates(state)
45
50
  const textFieldSize = textFieldSizes(size, !!leftIcon, !!rightIcon)
51
+ const [isDragging, setIsDragging] = useState(false)
52
+
53
+ const getCurrentInputType = () => {
54
+ if (type === 'password') {
55
+ return passwordVisible ? 'text' : 'password'
56
+ }
57
+ return type
58
+ }
46
59
 
47
60
  const togglePasswordVisibility = () => {
48
61
  setPasswordVisible(!passwordVisible)
49
62
  }
50
63
 
64
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
65
+ const files = e.target.files
66
+ if (files && files.length > 0) {
67
+ const fileNames = Array.from(files)
68
+ .map(file => file.name)
69
+ .join(', ')
70
+ setSelectedFileName(fileNames)
71
+ } else {
72
+ setSelectedFileName('')
73
+ }
74
+
75
+ register?.onChange(e)
76
+ onChange?.(e)
77
+ }
78
+
51
79
  const transformRightIcon = (
52
80
  type: string,
53
81
  rightIcon: React.ReactNode
@@ -55,14 +83,97 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
55
83
  if (type === 'password') {
56
84
  return (
57
85
  <IconWrapper onClick={togglePasswordVisibility} $right>
58
- {passwordVisible ? <Eye /> : <EyeClosed />}
86
+ {passwordVisible ? <EyeIcon /> : <EyeClosedIcon />}
87
+ </IconWrapper>
88
+ )
89
+ }
90
+ if (rightIcon) {
91
+ return (
92
+ <IconWrapper onClick={handleRightIcon} $right>
93
+ {rightIcon}
59
94
  </IconWrapper>
60
95
  )
61
96
  }
97
+ return null
98
+ }
99
+
100
+ const handleDrop = (e: React.DragEvent<HTMLLabelElement>) => {
101
+ e.preventDefault()
102
+ e.stopPropagation()
103
+ setIsDragging(false)
104
+
105
+ const files = e.dataTransfer.files
106
+ if (!files || files.length === 0) return
107
+
108
+ const fileNames = Array.from(files)
109
+ .map(f => f.name)
110
+ .join(', ')
111
+ setSelectedFileName(fileNames)
112
+
113
+ const event = {
114
+ target: {
115
+ files
116
+ }
117
+ } as unknown as React.ChangeEvent<HTMLInputElement>
118
+
119
+ register?.onChange(event)
120
+ onChange?.(event)
121
+ }
122
+
123
+ const handleDragOver = (e: React.DragEvent) => {
124
+ e.preventDefault()
125
+ e.stopPropagation()
126
+ setIsDragging(true)
127
+ }
128
+
129
+ const handleDragLeave = (e: React.DragEvent) => {
130
+ e.preventDefault()
131
+ e.stopPropagation()
132
+ setIsDragging(false)
133
+ }
134
+
135
+ if (type === 'file') {
62
136
  return (
63
- <IconWrapper onClick={handleRightIcon} $right>
64
- {rightIcon}
65
- </IconWrapper>
137
+ <Wrapper
138
+ className={className}
139
+ size={textFieldSize}
140
+ $themefication={textFieldState}>
141
+ <Label>
142
+ {label} {requiredSymbol && <RequiredAsterisk />}
143
+ </Label>
144
+ <InputWrapper
145
+ as="label"
146
+ onDragOver={handleDragOver}
147
+ onDragLeave={handleDragLeave}
148
+ onDrop={handleDrop}
149
+ $isDragging={isDragging}
150
+ >
151
+ {leftIcon && (
152
+ <IconWrapper onClick={handleLeftIcon}>
153
+ {leftIcon}
154
+ </IconWrapper>
155
+ )}
156
+ <FileInputContainer>
157
+ <FileButton>{fileButtonLabel}</FileButton>
158
+ <FileName>
159
+ {selectedFileName || filePlaceholder}
160
+ </FileName>
161
+ </FileInputContainer>
162
+ {transformRightIcon(type, rightIcon)}
163
+ <StyledInput
164
+ ref={ref}
165
+ type="file"
166
+ hidden
167
+ disabled={disabled}
168
+ {...props}
169
+ {...register}
170
+ onChange={handleFileChange}
171
+ />
172
+ </InputWrapper>
173
+ {(helperText || error) && (
174
+ <HelperText>{error?.message || helperText}</HelperText>
175
+ )}
176
+ </Wrapper>
66
177
  )
67
178
  }
68
179
 
@@ -70,8 +181,7 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
70
181
  <Wrapper
71
182
  className={className}
72
183
  size={textFieldSize}
73
- $themefication={textFieldState}
74
- >
184
+ $themefication={textFieldState}>
75
185
  <Label>
76
186
  {label} {requiredSymbol && <RequiredAsterisk />}
77
187
  </Label>
@@ -84,7 +194,7 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
84
194
  {transformRightIcon(type, rightIcon)}
85
195
  <StyledInput
86
196
  ref={ref}
87
- type={passwordVisible ? 'text' : 'password'}
197
+ type={getCurrentInputType()}
88
198
  value={value}
89
199
  disabled={disabled}
90
200
  placeholder={placeholder}
@@ -10,6 +10,8 @@ export interface TextFieldProps<TFieldValues extends FieldValues = FieldValues>
10
10
  handleLeftIcon?: () => void
11
11
  handleRightIcon?: () => void
12
12
  helperText?: string
13
+ filePlaceholder?: string
14
+ fileButtonLabel?: string
13
15
  size?: TextFieldSize
14
16
  className?: string
15
17
  placeholder?: string
@@ -18,6 +20,6 @@ export interface TextFieldProps<TFieldValues extends FieldValues = FieldValues>
18
20
  rightIcon?: React.ReactNode
19
21
  error?: TFieldValues
20
22
  requiredSymbol?: boolean
21
- type?: 'text' | 'password' | 'email' | 'number'
23
+ type?: 'text' | 'password' | 'email' | 'number' | 'file'
22
24
  register?: UseFormRegisterReturn<string>
23
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liguelead/design-system",
3
- "version": "0.0.25",
3
+ "version": "0.0.26",
4
4
  "type": "module",
5
5
  "main": "components/index.ts",
6
6
  "publishConfig": {