@liguelead/design-system 0.0.30 → 0.0.32

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 (46) hide show
  1. package/components/Alert/Alert.stories.tsx +94 -18
  2. package/components/Badge/Badge.stories.tsx +114 -0
  3. package/components/Badge/Badge.styles.ts +36 -0
  4. package/components/Badge/Badge.tsx +23 -0
  5. package/components/Badge/Badge.types.ts +11 -0
  6. package/components/Badge/index.ts +2 -0
  7. package/components/Button/Button.appearance.ts +1 -1
  8. package/components/Button/Button.stories.tsx +99 -18
  9. package/components/Checkbox/Checkbox.stories.tsx +107 -7
  10. package/components/DatePicker/DatePicker.styles.ts +1 -0
  11. package/components/DatePicker/DatePicker.tsx +9 -10
  12. package/components/IconButton/IconButton.sizes.ts +7 -7
  13. package/components/IconButton/IconButton.tsx +0 -1
  14. package/components/InputOpt/InputOpt.stories.tsx +30 -44
  15. package/components/Select/Select.stories.tsx +80 -19
  16. package/components/Select/Select.tsx +7 -9
  17. package/components/Table/Datatable.stories.tsx +186 -0
  18. package/components/Table/Table.stories.tsx +127 -46
  19. package/components/Table/Table.styles.ts +83 -8
  20. package/components/Table/Table.tsx +292 -142
  21. package/components/Table/Table.types.ts +104 -12
  22. package/components/Table/components/ColumnVisibility/ColumnVisibility.style.ts +46 -0
  23. package/components/Table/components/ColumnVisibility/ColumnVisibility.tsx +55 -0
  24. package/components/Table/components/DatatableColumnFilterMenu/DatatableColumnFilterMenu.styles.ts +120 -0
  25. package/components/Table/components/DatatableColumnFilterMenu/DatatableColumnFilterMenu.tsx +228 -0
  26. package/components/Table/components/DatatableColumnFilterMenu/index.ts +1 -0
  27. package/components/Table/components/DatatableTopBar/DatatableTopBar.styles.ts +25 -0
  28. package/components/Table/components/DatatableTopBar/DatatableTopBar.tsx +89 -0
  29. package/components/Table/components/DatatableTopBar/index.ts +1 -0
  30. package/components/Table/components/SearchInput/SearchInput.tsx +30 -0
  31. package/components/Table/components/TableHeader/TableHeader.tsx +98 -0
  32. package/components/Table/components/TablePagination/TablePagination.tsx +78 -0
  33. package/components/Table/components/index.ts +6 -0
  34. package/components/Table/hooks/useDatatableFilters.ts +88 -0
  35. package/components/Table/stories.fixtures.ts +100 -0
  36. package/components/Table/tanstack-table.d.ts +10 -0
  37. package/components/Table/utils/dateRangeFilterFn.ts +33 -0
  38. package/components/Table/utils/index.ts +2 -1
  39. package/components/Tabs/Tabs.stories.tsx +152 -0
  40. package/components/Tabs/Tabs.styles.ts +12 -0
  41. package/components/Tabs/Tabs.tsx +34 -0
  42. package/components/Tabs/Tabs.types.ts +15 -0
  43. package/components/Tabs/index.ts +2 -0
  44. package/components/TextField/TextField.stories.tsx +135 -12
  45. package/components/index.ts +3 -0
  46. package/package.json +3 -2
@@ -0,0 +1,88 @@
1
+ import { useState, useCallback } from 'react'
2
+ import { ColumnFiltersState } from '@tanstack/react-table'
3
+ import {
4
+ ColumnFilterValue,
5
+ DatatableColumnFilters,
6
+ } from '../Table.types'
7
+
8
+ interface UseDatatableFiltersProps {
9
+ isServerMode: boolean
10
+ controlled?: DatatableColumnFilters
11
+ onChange?: (next: DatatableColumnFilters) => void
12
+ }
13
+
14
+ export function useDatatableFilters({
15
+ isServerMode,
16
+ controlled,
17
+ onChange,
18
+ }: UseDatatableFiltersProps) {
19
+ const isControlled = controlled !== undefined
20
+ const [internal, setInternal] = useState<DatatableColumnFilters>({})
21
+ const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
22
+ const [openFilterColumnId, setOpenFilterColumnId] = useState<string | null>(null)
23
+
24
+ const filters: DatatableColumnFilters = isControlled ? controlled! : internal
25
+
26
+ const updateFilters = useCallback((next: DatatableColumnFilters) => {
27
+ if (!isControlled) setInternal(next)
28
+ onChange?.(next)
29
+ }, [isControlled, onChange])
30
+
31
+ const handleFilterValueChange = useCallback((colId: string, value: ColumnFilterValue | undefined) => {
32
+ updateFilters({ ...filters, [colId]: value })
33
+
34
+ if (isServerMode) return
35
+
36
+ setColumnFilters((prev) => {
37
+ const rest = prev.filter((f) => f.id !== colId)
38
+ if (!value) return rest
39
+ if (value.type === 'text') return value.value ? [...rest, { id: colId, value: value.value }] : rest
40
+ if (value.type === 'select') return value.value != null ? [...rest, { id: colId, value: value.value }] : rest
41
+ if (value.type === 'dateRange') {
42
+ const { from, to } = value.value
43
+ return from || to ? [...rest, { id: colId, value: { from, to } }] : rest
44
+ }
45
+ return rest
46
+ })
47
+ }, [filters, isServerMode, updateFilters])
48
+
49
+ const handleClearColumnFilter = useCallback((colId: string) => {
50
+ const next = { ...filters }
51
+ delete next[colId]
52
+ updateFilters(next)
53
+ setColumnFilters((prev) => prev.filter((f) => f.id !== colId))
54
+ if (openFilterColumnId === colId) setOpenFilterColumnId(null)
55
+ }, [filters, openFilterColumnId, updateFilters])
56
+
57
+ const handleClearAllFilters = useCallback((onClearGlobal: () => void) => {
58
+ onClearGlobal()
59
+ setColumnFilters([])
60
+ setOpenFilterColumnId(null)
61
+ updateFilters({})
62
+ }, [updateFilters])
63
+
64
+ const handlePopoverOpenChange = useCallback((colId: string, isOpen: boolean) => {
65
+ setOpenFilterColumnId(isOpen ? colId : null)
66
+ }, [])
67
+
68
+ const hasActiveFilter = useCallback((colId: string): boolean => {
69
+ const v = filters[colId]
70
+ if (!v) return false
71
+ if (v.type === 'text') return v.value !== ''
72
+ if (v.type === 'select') return v.value != null
73
+ if (v.type === 'dateRange') return v.value.from != null || v.value.to != null
74
+ return false
75
+ }, [filters])
76
+
77
+ return {
78
+ filters,
79
+ columnFilters,
80
+ setColumnFilters,
81
+ openFilterColumnId,
82
+ handleFilterValueChange,
83
+ handleClearColumnFilter,
84
+ handleClearAllFilters,
85
+ handlePopoverOpenChange,
86
+ hasActiveFilter,
87
+ }
88
+ }
@@ -0,0 +1,100 @@
1
+ import { ColumnDef } from '@tanstack/react-table'
2
+ import { DatatableColumnMeta } from './Table.types'
3
+
4
+ export type OrderRow = {
5
+ id: string
6
+ customer: string
7
+ product: string
8
+ status: 'Aprovado' | 'Pendente' | 'Cancelado'
9
+ amount: string
10
+ createdAt: string
11
+ }
12
+
13
+ export const ORDER_DATA: OrderRow[] = [
14
+ { id: 'ORD-001', customer: 'João Silva', product: 'Plano Pro', status: 'Aprovado', amount: 'R$ 299,00', createdAt: '10/01/2026' },
15
+ { id: 'ORD-002', customer: 'Maria Oliveira', product: 'Plano Basic', status: 'Pendente', amount: 'R$ 99,00', createdAt: '12/01/2026' },
16
+ { id: 'ORD-003', customer: 'Carlos Souza', product: 'Plano Enterprise', status: 'Aprovado', amount: 'R$ 899,00', createdAt: '15/01/2026' },
17
+ { id: 'ORD-004', customer: 'Fernanda Lima', product: 'Plano Pro', status: 'Cancelado', amount: 'R$ 299,00', createdAt: '18/01/2026' },
18
+ { id: 'ORD-005', customer: 'Paulo Mendes', product: 'Plano Basic', status: 'Aprovado', amount: 'R$ 99,00', createdAt: '20/01/2026' },
19
+ { id: 'ORD-006', customer: 'Aline Costa', product: 'Plano Pro', status: 'Pendente', amount: 'R$ 299,00', createdAt: '22/01/2026' },
20
+ { id: 'ORD-007', customer: 'Rafael Santos', product: 'Plano Enterprise', status: 'Aprovado', amount: 'R$ 899,00', createdAt: '25/01/2026' },
21
+ { id: 'ORD-008', customer: 'Juliana Freitas', product: 'Plano Basic', status: 'Aprovado', amount: 'R$ 99,00', createdAt: '28/01/2026' },
22
+ { id: 'ORD-009', customer: 'Bruno Carvalho', product: 'Plano Pro', status: 'Cancelado', amount: 'R$ 299,00', createdAt: '01/02/2026' },
23
+ { id: 'ORD-010', customer: 'Camila Rocha', product: 'Plano Enterprise', status: 'Pendente', amount: 'R$ 899,00', createdAt: '03/02/2026' },
24
+ ]
25
+
26
+ export const TABLE_COLUMNS: ColumnDef<OrderRow, unknown>[] = [
27
+ { header: 'ID', accessorKey: 'id', enableSorting: true },
28
+ { header: 'Cliente', accessorKey: 'customer', enableSorting: true },
29
+ { header: 'Produto', accessorKey: 'product', enableSorting: true },
30
+ { header: 'Status', accessorKey: 'status', enableSorting: true },
31
+ { header: 'Valor', accessorKey: 'amount' },
32
+ { header: 'Data', accessorKey: 'createdAt', enableSorting: true },
33
+ ]
34
+
35
+ export const DATATABLE_COLUMNS: ColumnDef<OrderRow, unknown>[] = [
36
+ {
37
+ header: 'ID',
38
+ accessorKey: 'id',
39
+ meta: {
40
+ filterType: 'text',
41
+ filterLabel: 'ID',
42
+ filterPlaceholder: 'Buscar pelo ID',
43
+ } satisfies DatatableColumnMeta,
44
+ },
45
+ {
46
+ header: 'Cliente',
47
+ accessorKey: 'customer',
48
+ meta: {
49
+ filterType: 'text',
50
+ filterLabel: 'cliente',
51
+ filterPlaceholder: 'Buscar cliente',
52
+ } satisfies DatatableColumnMeta,
53
+ },
54
+ {
55
+ header: 'Produto',
56
+ accessorKey: 'product',
57
+ meta: {
58
+ filterType: 'select',
59
+ filterLabel: 'produto',
60
+ filterPlaceholder: 'Selecione um produto',
61
+ selectOptions: [
62
+ { label: 'Plano Basic', value: 'Plano Basic' },
63
+ { label: 'Plano Pro', value: 'Plano Pro' },
64
+ { label: 'Plano Enterprise', value: 'Plano Enterprise' },
65
+ ],
66
+ } satisfies DatatableColumnMeta,
67
+ },
68
+ {
69
+ header: 'Status',
70
+ accessorKey: 'status',
71
+ meta: {
72
+ filterType: 'select',
73
+ filterLabel: 'status',
74
+ filterPlaceholder: 'Selecione um status',
75
+ selectOptions: [
76
+ { label: 'Aprovado', value: 'Aprovado' },
77
+ { label: 'Pendente', value: 'Pendente' },
78
+ { label: 'Cancelado', value: 'Cancelado' },
79
+ ],
80
+ } satisfies DatatableColumnMeta,
81
+ },
82
+ {
83
+ header: 'Valor',
84
+ accessorKey: 'amount',
85
+ meta: {
86
+ filterType: 'text',
87
+ filterLabel: 'valor',
88
+ filterPlaceholder: 'Buscar pelo valor',
89
+ } satisfies DatatableColumnMeta,
90
+ },
91
+ {
92
+ header: 'Data',
93
+ accessorKey: 'createdAt',
94
+ filterFn: 'dateRange',
95
+ meta: {
96
+ filterType: 'dateRange',
97
+ filterLabel: 'Período',
98
+ } satisfies DatatableColumnMeta,
99
+ },
100
+ ]
@@ -0,0 +1,10 @@
1
+ import '@tanstack/react-table'
2
+ import { DatatableColumnMeta } from './Table.types'
3
+
4
+ declare module '@tanstack/react-table' {
5
+ interface FilterFns {
6
+ dateRange: import('@tanstack/react-table').FilterFn<unknown>;
7
+ }
8
+
9
+ interface ColumnMeta<TData extends RowData, TValue> extends DatatableColumnMeta {}
10
+ }
@@ -0,0 +1,33 @@
1
+ import { FilterFn, Row } from '@tanstack/react-table'
2
+
3
+ export const dateRangeFilterFn: FilterFn<unknown> = (
4
+ row: Row<unknown>,
5
+ columnId: string,
6
+ filterValue: { from?: Date; to?: Date }
7
+ ) => {
8
+ const raw = row.getValue(columnId)
9
+ if (!raw) return true
10
+
11
+ let cellDate: Date | null = null
12
+ if (raw instanceof Date) {
13
+ cellDate = raw
14
+ } else if (typeof raw === 'string') {
15
+ const parts = raw.includes('/') ? raw.split('/').reverse() : raw.split('-')
16
+ const parsed = new Date(parts.join('-'))
17
+ if (!isNaN(parsed.getTime())) cellDate = parsed
18
+ }
19
+
20
+ if (!cellDate) return true
21
+
22
+ const { from, to } = filterValue
23
+ if (from && cellDate < from) return false
24
+ if (to) {
25
+ const toEnd = new Date(to)
26
+ toEnd.setHours(23, 59, 59, 999)
27
+ if (cellDate > toEnd) return false
28
+ }
29
+ return true
30
+ }
31
+
32
+ dateRangeFilterFn.autoRemove = (val: { from?: Date; to?: Date }) =>
33
+ !val || (!val.from && !val.to)
@@ -1 +1,2 @@
1
- export {default as getPageNumbers} from './getPageNumbers'
1
+ export { default as getPageNumbers } from './getPageNumbers'
2
+ export { dateRangeFilterFn } from './dateRangeFilterFn'
@@ -0,0 +1,152 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { useState } from 'react'
3
+ import { HouseIcon, UserIcon, GearIcon, BellIcon } from '@phosphor-icons/react'
4
+ import Tabs from './Tabs'
5
+ import type { TabItem } from './Tabs.types'
6
+
7
+ const defaultItems: TabItem[] = [
8
+ { key: 'tab1', label: 'Início' },
9
+ { key: 'tab2', label: 'Perfil' },
10
+ { key: 'tab3', label: 'Configurações' },
11
+ ]
12
+
13
+ const meta: Meta<typeof Tabs> = {
14
+ title: 'Navigation/Tabs',
15
+ component: Tabs,
16
+ parameters: {
17
+ layout: 'centered',
18
+ docs: {
19
+ description: {
20
+ component: `
21
+ Componente de navegação por abas baseado no design system LigueLead.
22
+
23
+ **Quando usar:**
24
+ - Alternar entre seções de conteúdo relacionadas
25
+ - Navegação secundária dentro de uma página
26
+ - Filtros de visualização (ex: Ativo / Inativo)
27
+
28
+ **Comportamento:**
29
+ - A tab ativa recebe estilo \`Solid/Primary\` com sombra
30
+ - As tabs inativas são \`Ghost/Neutral\` com fundo transparente
31
+ - Suporte a ícones à esquerda e direita
32
+ - Suporte a estado \`disabled\` por tab individual
33
+ - Prop \`fullWidth\` distribui as tabs igualmente no container
34
+ `,
35
+ },
36
+ },
37
+ },
38
+ tags: ['autodocs'],
39
+ argTypes: {
40
+ activeKey: {
41
+ control: 'select',
42
+ options: ['tab1', 'tab2', 'tab3'],
43
+ description: 'Chave da tab ativa',
44
+ },
45
+ fullWidth: {
46
+ control: 'boolean',
47
+ description: 'Distribui as tabs igualmente na largura do container',
48
+ table: { defaultValue: { summary: 'false' } },
49
+ },
50
+ },
51
+ render: (args) => {
52
+ const [active, setActive] = useState(args.activeKey ?? 'tab1')
53
+ return <Tabs {...args} activeKey={active} onChange={setActive} />
54
+ },
55
+ }
56
+
57
+ export default meta
58
+ type Story = StoryObj<typeof meta>
59
+
60
+ export const Default: Story = {
61
+ args: {
62
+ items: defaultItems,
63
+ activeKey: 'tab1',
64
+ },
65
+ }
66
+
67
+ export const TabAtivaDireita: Story = {
68
+ name: 'Tab ativa à direita',
69
+ parameters: {
70
+ docs: {
71
+ description: { story: 'Variante com a tab ativa posicionada à direita (Variant=Right active do Figma).' },
72
+ },
73
+ },
74
+ args: {
75
+ items: defaultItems,
76
+ activeKey: 'tab3',
77
+ },
78
+ }
79
+
80
+ export const ComIcones: Story = {
81
+ name: 'Com ícones',
82
+ parameters: {
83
+ docs: {
84
+ description: { story: 'Tabs com ícones à esquerda para reforçar o contexto visual.' },
85
+ },
86
+ },
87
+ args: {
88
+ items: [
89
+ { key: 'home', label: 'Início', leftIcon: <HouseIcon size={16} /> },
90
+ { key: 'user', label: 'Perfil', leftIcon: <UserIcon size={16} /> },
91
+ { key: 'settings', label: 'Configurações', leftIcon: <GearIcon size={16} /> },
92
+ { key: 'notifications', label: 'Notificações', leftIcon: <BellIcon size={16} /> },
93
+ ],
94
+ activeKey: 'home',
95
+ },
96
+ }
97
+
98
+ export const ComDisabled: Story = {
99
+ name: 'Com tab desabilitada',
100
+ parameters: {
101
+ docs: {
102
+ description: { story: 'Tabs com uma ou mais opções desabilitadas.' },
103
+ },
104
+ },
105
+ args: {
106
+ items: [
107
+ { key: 'tab1', label: 'Ativo' },
108
+ { key: 'tab2', label: 'Pendente' },
109
+ { key: 'tab3', label: 'Bloqueado', disabled: true },
110
+ ],
111
+ activeKey: 'tab1',
112
+ },
113
+ }
114
+
115
+ export const LarguraTotal: Story = {
116
+ name: 'Largura total',
117
+ parameters: {
118
+ layout: 'padded',
119
+ docs: {
120
+ description: { story: 'Use `fullWidth` para distribuir as tabs igualmente no container.' },
121
+ },
122
+ },
123
+ render: () => {
124
+ const [active, setActive] = useState('tab1')
125
+ return (
126
+ <div style={{ width: 400 }}>
127
+ <Tabs
128
+ items={defaultItems}
129
+ activeKey={active}
130
+ onChange={setActive}
131
+ fullWidth
132
+ />
133
+ </div>
134
+ )
135
+ },
136
+ }
137
+
138
+ export const DuasAbas: Story = {
139
+ name: 'Duas abas',
140
+ parameters: {
141
+ docs: {
142
+ description: { story: 'Caso de uso comum: alternar entre dois estados (ex: Ativo / Inativo).' },
143
+ },
144
+ },
145
+ args: {
146
+ items: [
147
+ { key: 'active', label: 'Ativo' },
148
+ { key: 'inactive', label: 'Inativo' },
149
+ ],
150
+ activeKey: 'active',
151
+ },
152
+ }
@@ -0,0 +1,12 @@
1
+ import styled from 'styled-components'
2
+ import { spacing, radius } from '@liguelead/foundation'
3
+ import { parseColor } from '../../utils'
4
+
5
+ export const TabsContainer = styled.div<{ $fullWidth?: boolean }>`
6
+ display: inline-flex;
7
+ align-items: stretch;
8
+ padding: ${spacing.spacing4}px;
9
+ border-radius: ${radius.radius4}px;
10
+ background-color: ${({ theme }) => parseColor(theme.colors.neutral200)};
11
+ width: ${({ $fullWidth }) => ($fullWidth ? '100%' : 'auto')};
12
+ `
@@ -0,0 +1,34 @@
1
+ import React from 'react'
2
+ import Button from '../Button'
3
+ import { TabsContainer } from './Tabs.styles'
4
+ import { TabsProps } from './Tabs.types'
5
+
6
+ const Tabs: React.FC<TabsProps> = ({
7
+ items,
8
+ activeKey,
9
+ onChange,
10
+ className,
11
+ fullWidth = false,
12
+ }) => {
13
+ return (
14
+ <TabsContainer $fullWidth={fullWidth} className={className} role="tablist">
15
+ {items.map(item => (
16
+ <Button
17
+ key={item.key}
18
+ variant={activeKey === item.key ? 'solid' : 'neutralGhost'}
19
+ color={activeKey === item.key ? 'primary' : undefined}
20
+ size="sm"
21
+ fullWidth={fullWidth}
22
+ disabled={item.disabled}
23
+ onClick={() => onChange(item.key)}
24
+ type="button">
25
+ {item.leftIcon}
26
+ {item.label}
27
+ {item.rightIcon}
28
+ </Button>
29
+ ))}
30
+ </TabsContainer>
31
+ )
32
+ }
33
+
34
+ export default Tabs
@@ -0,0 +1,15 @@
1
+ export interface TabItem {
2
+ key: string
3
+ label: string
4
+ disabled?: boolean
5
+ leftIcon?: React.ReactNode
6
+ rightIcon?: React.ReactNode
7
+ }
8
+
9
+ export interface TabsProps {
10
+ items: TabItem[]
11
+ activeKey: string
12
+ onChange: (key: string) => void
13
+ className?: string
14
+ fullWidth?: boolean
15
+ }
@@ -0,0 +1,2 @@
1
+ export { default } from './Tabs'
2
+ export type { TabsProps, TabItem } from './Tabs.types'
@@ -1,4 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { EnvelopeIcon, LockIcon, EyeIcon, MagnifyingGlassIcon } from '@phosphor-icons/react'
2
3
  import TextField from './TextField'
3
4
  import { iconMap, iconOptions } from '../../../stories/utils/icons'
4
5
 
@@ -7,48 +8,69 @@ const meta: Meta<typeof TextField> = {
7
8
  component: TextField,
8
9
  parameters: {
9
10
  layout: 'padded',
11
+ docs: {
12
+ description: {
13
+ component: `
14
+ Campo de texto para entrada de dados pelo usuário.
15
+
16
+ **Tamanhos:** \`sm\` · \`md\` · \`lg\`
17
+
18
+ **Tipos suportados:** \`text\` · \`password\` · \`email\` · \`number\` · \`file\`
19
+
20
+ **Boas práticas:**
21
+ - Sempre use \`label\` para identificar o campo
22
+ - Use \`helperText\` para instruções ou validações
23
+ - Use \`error\` para comunicar erros de validação
24
+ - Ícones devem reforçar o tipo de dado esperado
25
+ `,
26
+ },
27
+ },
10
28
  },
11
29
  tags: ['autodocs'],
12
30
  argTypes: {
13
31
  label: {
14
32
  control: 'text',
15
- description: 'Field label'
33
+ description: 'Label do campo',
16
34
  },
17
35
  placeholder: {
18
36
  control: 'text',
19
- description: 'Input placeholder'
37
+ description: 'Placeholder do input',
20
38
  },
21
39
  helperText: {
22
40
  control: 'text',
23
- description: 'Helper text below input'
41
+ description: 'Texto auxiliar abaixo do campo',
24
42
  },
25
43
  size: {
26
44
  control: 'select',
27
45
  options: ['sm', 'md', 'lg'],
28
- description: 'Input size'
46
+ description: 'Tamanho do campo',
47
+ table: { defaultValue: { summary: 'md' } },
29
48
  },
30
49
  type: {
31
50
  control: 'select',
32
51
  options: ['text', 'password', 'email', 'number', 'file'],
33
- description: 'Input type'
52
+ description: 'Tipo do input HTML',
53
+ table: { defaultValue: { summary: 'text' } },
34
54
  },
35
55
  disabled: {
36
56
  control: 'boolean',
37
- description: 'Whether input is disabled'
57
+ description: 'Desabilita o campo',
58
+ table: { defaultValue: { summary: 'false' } },
38
59
  },
39
60
  requiredSymbol: {
40
61
  control: 'boolean',
41
- description: 'Show required asterisk'
62
+ description: 'Exibe asterisco de campo obrigatório',
63
+ table: { defaultValue: { summary: 'false' } },
42
64
  },
43
65
  leftIcon: {
44
66
  control: 'select',
45
67
  options: iconOptions,
46
- description: 'Left icon',
68
+ description: 'Ícone à esquerda do campo',
47
69
  },
48
70
  rightIcon: {
49
71
  control: 'select',
50
72
  options: iconOptions,
51
- description: 'Right icon',
73
+ description: 'Ícone à direita do campo',
52
74
  },
53
75
  },
54
76
  }
@@ -58,15 +80,14 @@ type Story = StoryObj<typeof meta>
58
80
 
59
81
  export const Default: Story = {
60
82
  args: {
61
- label: 'Label',
62
- placeholder: 'Enter text...',
83
+ label: 'Nome completo',
84
+ placeholder: 'Digite seu nome...',
63
85
  leftIcon: 'none',
64
86
  rightIcon: 'none',
65
87
  },
66
88
  render: (args) => {
67
89
  const leftIcon = args.leftIcon === 'none' ? undefined : iconMap[args.leftIcon as keyof typeof iconMap]
68
90
  const rightIcon = args.rightIcon === 'none' ? undefined : iconMap[args.rightIcon as keyof typeof iconMap]
69
-
70
91
  return (
71
92
  <TextField
72
93
  label={args.label}
@@ -82,3 +103,105 @@ export const Default: Story = {
82
103
  )
83
104
  },
84
105
  }
106
+
107
+ export const Tamanhos: Story = {
108
+ name: 'Tamanhos',
109
+ parameters: {
110
+ docs: { description: { story: 'Três tamanhos disponíveis para diferentes densidades de layout.' } },
111
+ },
112
+ render: () => (
113
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 16, width: 360 }}>
114
+ <TextField label="Pequeno" size="sm" placeholder="sm" />
115
+ <TextField label="Médio" size="md" placeholder="md" />
116
+ <TextField label="Grande" size="lg" placeholder="lg" />
117
+ </div>
118
+ ),
119
+ }
120
+
121
+ export const ComHelperText: Story = {
122
+ name: 'Com helper text',
123
+ parameters: {
124
+ docs: { description: { story: 'Use `helperText` para instruções de preenchimento.' } },
125
+ },
126
+ args: {
127
+ label: 'Senha',
128
+ placeholder: '••••••••',
129
+ helperText: 'Mínimo de 8 caracteres, com letras e números.',
130
+ type: 'password',
131
+ },
132
+ }
133
+
134
+ export const ComErro: Story = {
135
+ name: 'Estado de erro',
136
+ parameters: {
137
+ docs: { description: { story: 'Use `error` para comunicar falhas de validação.' } },
138
+ },
139
+ args: {
140
+ label: 'E-mail',
141
+ placeholder: 'seu@email.com',
142
+ error: { message: 'E-mail inválido.' },
143
+ value: 'email-invalido',
144
+ },
145
+ }
146
+
147
+ export const Desabilitado: Story = {
148
+ name: 'Estado desabilitado',
149
+ args: {
150
+ label: 'Campo desabilitado',
151
+ placeholder: 'Não editável',
152
+ disabled: true,
153
+ },
154
+ }
155
+
156
+ export const ComIcones: Story = {
157
+ name: 'Com ícones',
158
+ parameters: {
159
+ docs: { description: { story: 'Ícones reforçam o tipo de dado esperado.' } },
160
+ },
161
+ render: () => (
162
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 16, width: 360 }}>
163
+ <TextField label="E-mail" placeholder="seu@email.com" leftIcon={<EnvelopeIcon />} />
164
+ <TextField label="Senha" placeholder="••••••••" leftIcon={<LockIcon />} type="password" />
165
+ <TextField label="Buscar" placeholder="Pesquisar..." leftIcon={<MagnifyingGlassIcon />} />
166
+ <TextField label="Confirmar senha" placeholder="••••••••" rightIcon={<EyeIcon />} type="password" />
167
+ </div>
168
+ ),
169
+ }
170
+
171
+ export const Obrigatorio: Story = {
172
+ name: 'Campo obrigatório',
173
+ parameters: {
174
+ docs: { description: { story: 'Use `requiredSymbol` para indicar campos obrigatórios.' } },
175
+ },
176
+ args: {
177
+ label: 'Nome completo',
178
+ placeholder: 'Digite seu nome...',
179
+ requiredSymbol: true,
180
+ },
181
+ }
182
+
183
+ export const ExemploLogin: Story = {
184
+ name: 'Exemplo: Formulário de login',
185
+ parameters: {
186
+ docs: { description: { story: 'Padrão de uso em formulários de autenticação.' } },
187
+ },
188
+ render: () => (
189
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 16, width: 360 }}>
190
+ <TextField
191
+ label="E-mail"
192
+ placeholder="seu@email.com"
193
+ leftIcon={<EnvelopeIcon />}
194
+ requiredSymbol
195
+ type="email"
196
+ />
197
+ <TextField
198
+ label="Senha"
199
+ placeholder="••••••••"
200
+ leftIcon={<LockIcon />}
201
+ requiredSymbol
202
+ type="password"
203
+ helperText="Mínimo de 8 caracteres."
204
+ />
205
+ </div>
206
+ ),
207
+ }
@@ -1,4 +1,6 @@
1
1
  export { default as Alert } from './Alert'
2
+ export { Badge } from './Badge'
3
+ export type { TBadgeProps } from './Badge'
2
4
  export { default as Button } from './Button'
3
5
  export { default as Checkbox } from './Checkbox'
4
6
  export { default as DatePicker } from './DatePicker'
@@ -17,3 +19,4 @@ export { ToastProvider, Toaster } from './Toaster'
17
19
  export { default as Dialog } from './Dialog'
18
20
  export { Combobox } from './Combobox'
19
21
  export { default as Table } from './Table'
22
+ export { default as Tabs } from './Tabs'