@liguelead/design-system 0.0.36 → 0.0.38

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 (34) hide show
  1. package/components/Alert/Alert.style.ts +1 -1
  2. package/components/Alert/Alert.tsx +3 -1
  3. package/components/Alert/Alert.variants.ts +2 -4
  4. package/components/Button/Button.appearance.ts +3 -4
  5. package/components/Button/Button.styles.ts +0 -1
  6. package/components/Button/Button.tsx +4 -7
  7. package/components/Button/Button.types.ts +1 -1
  8. package/components/Checkbox/Checkbox.tsx +5 -5
  9. package/components/Combobox/Combobox.styles.ts +1 -1
  10. package/components/Combobox/Combobox.tsx +1 -1
  11. package/components/Dialog/Dialog.style.ts +2 -1
  12. package/components/Dialog/Dialog.tsx +6 -8
  13. package/components/IconButton/IconButton.tsx +3 -1
  14. package/components/LinkButton/LinkButton.tsx +3 -1
  15. package/components/PageWrapper/PageWrapper.tsx +1 -1
  16. package/components/RadioCardGroup/RadioCardGroup.stories.tsx +203 -0
  17. package/components/RadioCardGroup/RadioCardGroup.styles.ts +198 -0
  18. package/components/RadioCardGroup/RadioCardGroup.tsx +159 -0
  19. package/components/RadioCardGroup/RadioCardGroup.types.ts +29 -0
  20. package/components/RadioCardGroup/index.ts +2 -0
  21. package/components/SplitButton/SplitButton.tsx +1 -1
  22. package/components/Stepper/Stepper.appearance.ts +57 -0
  23. package/components/Stepper/Stepper.stories.tsx +300 -0
  24. package/components/Stepper/Stepper.styles.ts +179 -0
  25. package/components/Stepper/Stepper.tsx +118 -0
  26. package/components/Stepper/Stepper.types.ts +27 -0
  27. package/components/Stepper/index.ts +7 -0
  28. package/components/Tabs/Tabs.tsx +2 -0
  29. package/components/TextField/TextField.stories.tsx +109 -2
  30. package/components/TextField/TextField.styles.ts +44 -0
  31. package/components/TextField/TextField.tsx +130 -8
  32. package/components/TextField/TextField.types.ts +11 -1
  33. package/components/Toaster/Toaster.ts +5 -19
  34. package/package.json +1 -1
@@ -1,7 +1,8 @@
1
- import React, { forwardRef, useState } from 'react'
1
+ import React, { forwardRef, useEffect, useRef, useState } from 'react'
2
2
  import { TextFieldProps } from './TextField.types'
3
3
  import { StateInterface, TextFieldStates } from './TextField.states'
4
4
  import {
5
+ CharCount,
5
6
  FileButton,
6
7
  FileInputContainer,
7
8
  FileName,
@@ -10,6 +11,9 @@ import {
10
11
  InputWrapper,
11
12
  Label,
12
13
  StyledInput,
14
+ StyledTextArea,
15
+ TextAreaFooter,
16
+ TextAreaWrapper,
13
17
  Wrapper
14
18
  } from './TextField.styles'
15
19
 
@@ -17,6 +21,7 @@ import { textFieldSizes } from './TextField.sizes'
17
21
  import { EyeIcon, EyeClosedIcon } from '@phosphor-icons/react'
18
22
  import getState from './utils/getState'
19
23
  import RequiredAsterisk from '../RequiredAsterisk'
24
+ import Button from '../Button'
20
25
 
21
26
  const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
22
27
  (
@@ -39,12 +44,22 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
39
44
  register,
40
45
  requiredSymbol = false,
41
46
  placeholder,
47
+ showVariableButton = false,
48
+ variableButtonLabel = 'Variável',
49
+ maxLength,
50
+ htmlMaxLength = false,
51
+ onTextAreaChange,
42
52
  ...props
43
53
  },
44
54
  ref
45
55
  ) => {
46
56
  const [passwordVisible, setPasswordVisible] = useState(false)
47
57
  const [selectedFileName, setSelectedFileName] = useState('')
58
+ const [textAreaValue, setTextAreaValue] = useState(
59
+ (value as string) ?? ''
60
+ )
61
+ const textAreaRef = useRef<HTMLTextAreaElement>(null)
62
+ const varCountRef = useRef(0)
48
63
  const state = getState(disabled, !!error)
49
64
  const textFieldState: StateInterface = TextFieldStates(state)
50
65
  const textFieldSize = textFieldSizes(size, !!leftIcon, !!rightIcon)
@@ -131,6 +146,108 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
131
146
  e.stopPropagation()
132
147
  setIsDragging(false)
133
148
  }
149
+ useEffect(() => {
150
+ if (value !== undefined) {
151
+ setTextAreaValue(value as string)
152
+ }
153
+ }, [value])
154
+
155
+ const handleInsertVariable = () => {
156
+ const el = textAreaRef.current
157
+ if (!el) return
158
+
159
+ const matches = textAreaValue.match(/\{\{(\d+)\}\}/g) ?? []
160
+ const usedNumbers = matches.map(m => parseInt(m.replace(/\D/g, ''), 10))
161
+ let next = 1
162
+ while (usedNumbers.includes(next)) next++
163
+ varCountRef.current = next
164
+ const variable = `{{${next}}}`
165
+ const start = el.selectionStart ?? textAreaValue.length
166
+ const end = el.selectionEnd ?? textAreaValue.length
167
+ const newValue =
168
+ textAreaValue.slice(0, start) +
169
+ variable +
170
+ textAreaValue.slice(end)
171
+
172
+ if (maxLength !== undefined && newValue.length > maxLength) return
173
+
174
+ setTextAreaValue(newValue)
175
+
176
+ requestAnimationFrame(() => {
177
+ el.focus()
178
+ el.setSelectionRange(
179
+ start + variable.length,
180
+ start + variable.length
181
+ )
182
+ })
183
+ }
184
+
185
+ const handleTextAreaChange = (
186
+ e: React.ChangeEvent<HTMLTextAreaElement>
187
+ ) => {
188
+ setTextAreaValue(e.target.value)
189
+ onTextAreaChange?.(e)
190
+ }
191
+
192
+ if (type === 'textarea') {
193
+ const charCount = textAreaValue.length
194
+ const isOver = maxLength !== undefined && charCount > maxLength
195
+
196
+ return (
197
+ <Wrapper
198
+ className={className}
199
+ size={textFieldSize}
200
+ $themefication={textFieldState}
201
+ >
202
+ {label && (
203
+ <Label>
204
+ {label}{' '}
205
+ {requiredSymbol && <RequiredAsterisk />}
206
+ </Label>
207
+ )}
208
+ <TextAreaWrapper>
209
+ <StyledTextArea
210
+ ref={textAreaRef}
211
+ value={textAreaValue}
212
+ disabled={disabled}
213
+ placeholder={placeholder}
214
+ maxLength={htmlMaxLength ? maxLength : undefined}
215
+ onChange={handleTextAreaChange}
216
+ aria-invalid={!!error}
217
+ aria-describedby={
218
+ helperText || error
219
+ ? `${label}-helper`
220
+ : undefined
221
+ }
222
+ />
223
+ </TextAreaWrapper>
224
+ <TextAreaFooter>
225
+ {maxLength !== undefined && (
226
+ <CharCount $isOver={isOver}>
227
+ {charCount}/{maxLength}
228
+ </CharCount>
229
+ )}
230
+ {showVariableButton && (
231
+ <Button
232
+ size="sm"
233
+ variant="ghost"
234
+ type="button"
235
+ disabled={disabled}
236
+ onClick={handleInsertVariable}
237
+ aria-label={variableButtonLabel}
238
+ >
239
+ {variableButtonLabel}
240
+ </Button>
241
+ )}
242
+ </TextAreaFooter>
243
+ {(helperText || error) && (
244
+ <HelperText id={`${label}-helper`}>
245
+ {error?.message || helperText}
246
+ </HelperText>
247
+ )}
248
+ </Wrapper>
249
+ )
250
+ }
134
251
 
135
252
  if (type === 'file') {
136
253
  return (
@@ -138,9 +255,11 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
138
255
  className={className}
139
256
  size={textFieldSize}
140
257
  $themefication={textFieldState}>
141
- <Label>
142
- {label} {requiredSymbol && <RequiredAsterisk />}
143
- </Label>
258
+ {label && (
259
+ <Label>
260
+ {label} {requiredSymbol && <RequiredAsterisk />}
261
+ </Label>
262
+ )}
144
263
  <InputWrapper
145
264
  as="label"
146
265
  onDragOver={handleDragOver}
@@ -181,10 +300,13 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
181
300
  <Wrapper
182
301
  className={className}
183
302
  size={textFieldSize}
184
- $themefication={textFieldState}>
185
- <Label>
186
- {label} {requiredSymbol && <RequiredAsterisk />}
187
- </Label>
303
+ $themefication={textFieldState}
304
+ >
305
+ {label && (
306
+ <Label>
307
+ {label} {requiredSymbol && <RequiredAsterisk />}
308
+ </Label>
309
+ )}
188
310
  <InputWrapper>
189
311
  {leftIcon && (
190
312
  <IconWrapper onClick={handleLeftIcon}>
@@ -20,6 +20,16 @@ export interface TextFieldProps<TFieldValues extends FieldValues = FieldValues>
20
20
  rightIcon?: React.ReactNode
21
21
  error?: TFieldValues
22
22
  requiredSymbol?: boolean
23
- type?: 'text' | 'password' | 'email' | 'number' | 'file'
23
+ type?: 'text' | 'password' | 'email' | 'number' | 'file' | 'textarea'
24
24
  register?: UseFormRegisterReturn<string>
25
+ /** Habilita o botão de inserir variável (apenas para type="textarea") */
26
+ showVariableButton?: boolean
27
+ /** Label do botão de variável */
28
+ variableButtonLabel?: string
29
+ /** Limite máximo de caracteres (exibido no rodapé quando type="textarea") */
30
+ maxLength?: number
31
+ /** Passa o maxLength nativo ao <textarea>, bloqueando digitação pelo browser. Default: false */
32
+ htmlMaxLength?: boolean
33
+ /** Callback chamado quando o valor do textarea muda */
34
+ onTextAreaChange?: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
25
35
  }
@@ -1,4 +1,4 @@
1
- import { toast, ToastOptions } from "react-toastify"
1
+ import { toast, ToastOptions } from 'react-toastify'
2
2
 
3
3
  const defaultOptions: ToastOptions = {
4
4
  position: 'top-right',
@@ -11,23 +11,6 @@ const defaultOptions: ToastOptions = {
11
11
 
12
12
  export type ToastType = 'success' | 'error' | 'warning' | 'info'
13
13
 
14
- export const triggerToast = (type: ToastType, message: string, options?: ToastOptions) => {
15
- const mergedOptions = { ...defaultOptions, ...options }
16
-
17
- switch (type) {
18
- case 'success':
19
- return toast.success(message, mergedOptions)
20
- case 'error':
21
- return toast.error(message, mergedOptions)
22
- case 'warning':
23
- return toast.warning(message, mergedOptions)
24
- case 'info':
25
- return toast.info(message, mergedOptions)
26
- default:
27
- return toast(message, mergedOptions)
28
- }
29
- }
30
-
31
14
  const Toaster = {
32
15
  success: (message: string, options?: ToastOptions) =>
33
16
  toast.success(message, { ...defaultOptions, ...options }),
@@ -42,4 +25,7 @@ const Toaster = {
42
25
  toast.info(message, { ...defaultOptions, ...options })
43
26
  }
44
27
 
45
- export default Toaster;
28
+ export const triggerToast = (type: ToastType, message: string, options?: ToastOptions) =>
29
+ Toaster[type](message, options)
30
+
31
+ export default Toaster
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liguelead/design-system",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "type": "module",
5
5
  "main": "components/index.ts",
6
6
  "publishConfig": {