@liguelead/design-system 0.0.33 → 0.0.35

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.
@@ -0,0 +1,221 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import {
3
+ ArchiveIcon,
4
+ ClockIcon,
5
+ PencilSimpleIcon,
6
+ TrashIcon
7
+ } from '@phosphor-icons/react'
8
+ import SplitButton from './SplitButton'
9
+
10
+ const meta: Meta<typeof SplitButton> = {
11
+ title: 'Form/SplitButton',
12
+ component: SplitButton,
13
+ parameters: {
14
+ layout: 'centered',
15
+ docs: {
16
+ description: {
17
+ component: `
18
+ Botão dividido com ação principal e menu de opções secundárias.
19
+
20
+ **Quando usar:**
21
+ - Quando há uma ação primária clara, mas também ações alternativas relacionadas
22
+ - Ex: "Publicar" com opções "Salvar rascunho" e "Agendar"
23
+
24
+ **Composição:**
25
+ - Lado esquerdo: botão de ação principal (\`onClick\`)
26
+ - Lado direito: trigger que abre um \`DropdownMenu\` com as \`options\`
27
+ `
28
+ }
29
+ }
30
+ },
31
+ tags: ['autodocs'],
32
+ argTypes: {
33
+ label: {
34
+ control: 'text',
35
+ description: 'Texto do botão principal'
36
+ },
37
+ variant: {
38
+ control: 'select',
39
+ options: ['solid', 'outline', 'ghost', 'neutralOutline', 'neutralGhost'],
40
+ description: 'Variante visual',
41
+ table: { defaultValue: { summary: 'neutralOutline' } }
42
+ },
43
+ size: {
44
+ control: 'select',
45
+ options: ['sm', 'md', 'lg'],
46
+ description: 'Tamanho do botão',
47
+ table: { defaultValue: { summary: 'sm' } }
48
+ },
49
+ disabled: {
50
+ control: 'boolean',
51
+ description: 'Desabilita ambos os botões',
52
+ table: { defaultValue: { summary: 'false' } }
53
+ }
54
+ }
55
+ }
56
+
57
+ export default meta
58
+ type Story = StoryObj<typeof meta>
59
+
60
+ const defaultOptions = [
61
+ {
62
+ label: 'Salvar rascunho',
63
+ icon: <PencilSimpleIcon size={16} />,
64
+ onClick: () => console.log('rascunho')
65
+ },
66
+ {
67
+ label: 'Agendar',
68
+ icon: <ClockIcon size={16} />,
69
+ onClick: () => console.log('agendar')
70
+ },
71
+ {
72
+ label: 'Arquivar',
73
+ icon: <ArchiveIcon size={16} />,
74
+ onClick: () => console.log('arquivar')
75
+ }
76
+ ]
77
+
78
+ export const Default: Story = {
79
+ args: {
80
+ label: 'Publicar',
81
+ onClick: () => console.log('publicar'),
82
+ options: defaultOptions
83
+ }
84
+ }
85
+
86
+ export const Tamanhos: Story = {
87
+ name: 'Tamanhos',
88
+ parameters: {
89
+ docs: {
90
+ description: { story: 'Três tamanhos disponíveis: `sm`, `md` e `lg`.' }
91
+ }
92
+ },
93
+ render: () => (
94
+ <div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
95
+ <SplitButton
96
+ label="Pequeno"
97
+ size="sm"
98
+ onClick={() => {}}
99
+ options={defaultOptions}
100
+ />
101
+ <SplitButton
102
+ label="Médio"
103
+ size="md"
104
+ onClick={() => {}}
105
+ options={defaultOptions}
106
+ />
107
+ <SplitButton
108
+ label="Grande"
109
+ size="lg"
110
+ onClick={() => {}}
111
+ options={defaultOptions}
112
+ />
113
+ </div>
114
+ )
115
+ }
116
+
117
+ export const Variantes: Story = {
118
+ name: 'Variantes',
119
+ parameters: {
120
+ docs: {
121
+ description: { story: 'Herda as mesmas variantes do Button.' }
122
+ }
123
+ },
124
+ render: () => (
125
+ <div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'center' }}>
126
+ <SplitButton
127
+ label="Neutral Outline"
128
+ variant="neutralOutline"
129
+ onClick={() => {}}
130
+ options={defaultOptions}
131
+ />
132
+ <SplitButton
133
+ label="Solid"
134
+ variant="solid"
135
+ onClick={() => {}}
136
+ options={defaultOptions}
137
+ />
138
+ <SplitButton
139
+ label="Outline"
140
+ variant="outline"
141
+ onClick={() => {}}
142
+ options={defaultOptions}
143
+ />
144
+ <SplitButton
145
+ label="Ghost"
146
+ variant="ghost"
147
+ onClick={() => {}}
148
+ options={defaultOptions}
149
+ />
150
+ </div>
151
+ )
152
+ }
153
+
154
+ export const Desabilitado: Story = {
155
+ name: 'Estado desabilitado',
156
+ args: {
157
+ label: 'Publicar',
158
+ disabled: true,
159
+ onClick: () => {},
160
+ options: defaultOptions
161
+ }
162
+ }
163
+
164
+ export const SemIcones: Story = {
165
+ name: 'Opções sem ícone',
166
+ args: {
167
+ label: 'Ação',
168
+ onClick: () => {},
169
+ options: [
170
+ { label: 'Opção A', onClick: () => {} },
171
+ { label: 'Opção B', onClick: () => {} },
172
+ { label: 'Opção C (desabilitada)', onClick: () => {}, disabled: true }
173
+ ]
174
+ }
175
+ }
176
+
177
+ export const ExemploReal: Story = {
178
+ name: 'Exemplo: Publicação de conteúdo',
179
+ parameters: {
180
+ docs: {
181
+ description: {
182
+ story: 'Padrão comum em sistemas de CMS ou gestão de tarefas.'
183
+ }
184
+ }
185
+ },
186
+ render: () => (
187
+ <div
188
+ style={{
189
+ display: 'flex',
190
+ justifyContent: 'flex-end',
191
+ padding: 16,
192
+ border: '1px solid #e5e7eb',
193
+ borderRadius: 8,
194
+ width: 400,
195
+ gap: 8
196
+ }}
197
+ >
198
+ <SplitButton
199
+ label="Publicar"
200
+ onClick={() => console.log('publicar')}
201
+ options={[
202
+ {
203
+ label: 'Salvar rascunho',
204
+ icon: <PencilSimpleIcon size={16} />,
205
+ onClick: () => console.log('rascunho')
206
+ },
207
+ {
208
+ label: 'Agendar publicação',
209
+ icon: <ClockIcon size={16} />,
210
+ onClick: () => console.log('agendar')
211
+ },
212
+ {
213
+ label: 'Mover para lixeira',
214
+ icon: <TrashIcon size={16} />,
215
+ onClick: () => console.log('lixeira')
216
+ }
217
+ ]}
218
+ />
219
+ </div>
220
+ )
221
+ }
@@ -0,0 +1,106 @@
1
+ import styled from 'styled-components'
2
+ import { fontSize, fontWeight, lineHeight, radius, shadow, spacing } from '@liguelead/foundation'
3
+ import { parseColor } from '../../utils'
4
+ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
5
+
6
+ export const Wrapper = styled.div`
7
+ display: inline-flex;
8
+ align-items: stretch;
9
+ `
10
+
11
+ export const MainButton = styled.button<{ $variant: string; $size: string }>`
12
+ position: relative;
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ overflow: hidden;
17
+ cursor: pointer;
18
+ outline: none;
19
+ transition: background-color 0.3s, box-shadow 0.3s;
20
+ ${({ $size }) => $size}
21
+ ${({ $variant }) => $variant}
22
+ border-radius: ${radius.radius4}px 0 0 ${radius.radius4}px;
23
+
24
+ &:focus-visible {
25
+ box-shadow: ${shadow.focusShadow};
26
+ }
27
+
28
+ &:disabled {
29
+ cursor: not-allowed;
30
+ }
31
+ `
32
+
33
+ export const TriggerButton = styled(DropdownMenu.Trigger)<{
34
+ $variant: string
35
+ $size: string
36
+ }>`
37
+ position: relative;
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ overflow: hidden;
42
+ cursor: pointer;
43
+ outline: none;
44
+ transition: background-color 0.3s, box-shadow 0.3s;
45
+ ${({ $size }) => $size}
46
+ ${({ $variant }) => $variant}
47
+ border-radius: 0 ${radius.radius4}px ${radius.radius4}px 0;
48
+
49
+ &:focus-visible {
50
+ box-shadow: ${shadow.focusShadow};
51
+ }
52
+
53
+ &:disabled {
54
+ cursor: not-allowed;
55
+ }
56
+
57
+ & svg {
58
+ width: 10px;
59
+ height: 10px;
60
+ pointer-events: none;
61
+ }
62
+ `
63
+
64
+ export const StyledContent = styled(DropdownMenu.Content)`
65
+ width: max-content;
66
+ background: ${({ theme }) => parseColor(theme.colors.white)};
67
+ border: 1px solid ${({ theme }) => parseColor(theme.colors.neutral400)};
68
+ border-radius: ${radius.radius4}px;
69
+ box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.08);
70
+ overflow: hidden;
71
+ z-index: 9999;
72
+ padding: 0;
73
+ `
74
+
75
+ export const StyledItem = styled(DropdownMenu.Item)`
76
+ height: ${spacing.spacing36}px;
77
+ padding: 0 ${spacing.spacing12}px;
78
+ display: flex;
79
+ align-items: center;
80
+ gap: ${spacing.spacing8}px;
81
+ cursor: pointer;
82
+ outline: none;
83
+ font-size: ${fontSize.fontSize14}px;
84
+ font-weight: ${fontWeight.fontWeight400};
85
+ line-height: ${lineHeight.lineHeight20}px;
86
+ color: ${({ theme }) => parseColor(theme.colors.textDark)};
87
+ transition: background-color 0.2s ease;
88
+ user-select: none;
89
+ white-space: nowrap;
90
+
91
+ &[data-highlighted] {
92
+ background-color: ${({ theme }) => parseColor(theme.colors.primaryLight)};
93
+ }
94
+
95
+ &[data-disabled] {
96
+ opacity: 0.6;
97
+ cursor: not-allowed;
98
+ }
99
+
100
+ & svg {
101
+ width: 16px;
102
+ height: 16px;
103
+ flex-shrink: 0;
104
+ color: ${({ theme }) => parseColor(theme.colors.textMedium)};
105
+ }
106
+ `
@@ -0,0 +1,83 @@
1
+ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
2
+ import { CaretDownIcon } from '@phosphor-icons/react'
3
+ import { useTheme } from 'styled-components'
4
+ import { ButtonVariant } from '../Button/Button.appearance'
5
+ import { ButtonSizes } from '../Button/Button.sizes'
6
+ import { parseColor } from '../../utils'
7
+ import {
8
+ MainButton,
9
+ StyledContent,
10
+ StyledItem,
11
+ TriggerButton,
12
+ Wrapper
13
+ } from './SplitButton.styles'
14
+ import { SplitButtonTriggerSizes } from './SplitButton.sizes'
15
+ import { SplitButtonProps } from './SplitButton.types'
16
+
17
+ const SplitButton: React.FC<SplitButtonProps> = ({
18
+ label,
19
+ onClick,
20
+ options,
21
+ variant = 'neutralOutline',
22
+ color = 'primary',
23
+ size = 'sm',
24
+ disabled = false,
25
+ className
26
+ }) => {
27
+ const theme = useTheme()
28
+ const buttonVariant = ButtonVariant(color, variant)
29
+ const buttonSize = ButtonSizes(size)
30
+ const triggerSize = SplitButtonTriggerSizes(size)
31
+
32
+ const mainVariantOverride = `
33
+ ${buttonVariant}
34
+ border-right: none;
35
+ `
36
+
37
+ const triggerVariantOverride = `
38
+ ${buttonVariant}
39
+ border-left: 1px solid ${parseColor(theme.colors.neutral400)};
40
+ `
41
+
42
+ return (
43
+ <DropdownMenu.Root>
44
+ <Wrapper className={className}>
45
+ <MainButton
46
+ type="button"
47
+ disabled={disabled}
48
+ onClick={onClick}
49
+ $variant={mainVariantOverride}
50
+ $size={buttonSize}
51
+ >
52
+ {label}
53
+ </MainButton>
54
+
55
+ <TriggerButton
56
+ disabled={disabled}
57
+ $variant={triggerVariantOverride}
58
+ $size={triggerSize}
59
+ aria-label="Mais opções"
60
+ >
61
+ <CaretDownIcon weight="fill" />
62
+ </TriggerButton>
63
+ </Wrapper>
64
+
65
+ <DropdownMenu.Portal>
66
+ <StyledContent align="end" sideOffset={0}>
67
+ {options.map((option, index) => (
68
+ <StyledItem
69
+ key={index}
70
+ disabled={option.disabled}
71
+ onSelect={option.onClick}
72
+ >
73
+ {option.icon}
74
+ {option.label}
75
+ </StyledItem>
76
+ ))}
77
+ </StyledContent>
78
+ </DropdownMenu.Portal>
79
+ </DropdownMenu.Root>
80
+ )
81
+ }
82
+
83
+ export default SplitButton
@@ -0,0 +1,20 @@
1
+ import { ButtonSizeTypes, ButtonVariantTypes } from '../Button/Button.types'
2
+ import { colorType } from '../../types'
3
+
4
+ export interface SplitButtonOption {
5
+ label: string
6
+ icon?: React.ReactNode
7
+ onClick: () => void
8
+ disabled?: boolean
9
+ }
10
+
11
+ export interface SplitButtonProps {
12
+ label: string
13
+ onClick: () => void
14
+ options: SplitButtonOption[]
15
+ variant?: ButtonVariantTypes
16
+ color?: colorType
17
+ size?: ButtonSizeTypes
18
+ disabled?: boolean
19
+ className?: string
20
+ }
@@ -0,0 +1,2 @@
1
+ export { default } from './SplitButton'
2
+ export type { SplitButtonProps, SplitButtonOption } from './SplitButton.types'
@@ -98,6 +98,7 @@ function Table<TData>({
98
98
  isServerMode,
99
99
  controlled: datatableColumnFiltersValue,
100
100
  onChange: onDatatableColumnFiltersChange,
101
+ onClearSorting: (colId) => setSorting((prev) => prev.filter((s) => s.id !== colId)),
101
102
  })
102
103
 
103
104
  const queryState: TableQueryState = useMemo(() => ({
@@ -164,7 +165,7 @@ function Table<TData>({
164
165
  const currentPage = pagination.pageIndex + 1
165
166
 
166
167
  const hasActiveFilters = globalFilter !== '' || sorting.length > 0
167
- const hasDatatableActiveFilters = globalFilter !== '' || columnFilters.length > 0
168
+ const hasDatatableActiveFilters = globalFilter !== '' || columnFilters.length > 0 || sorting.length > 0
168
169
 
169
170
  const handleClearFilters = () => {
170
171
  setGlobalFilter('')
@@ -172,6 +173,10 @@ function Table<TData>({
172
173
  setPagination((prev) => ({ ...prev, pageIndex: 0 }))
173
174
  }
174
175
 
176
+ const handleNumberSort = (colId: string, direction: 'asc' | 'desc' | null) => {
177
+ setSorting(direction ? [{ id: colId, desc: direction === 'desc' }] : [])
178
+ }
179
+
175
180
  const handleExport = async () => {
176
181
  if (onExport) {
177
182
  await onExport(queryState)
@@ -204,7 +209,10 @@ function Table<TData>({
204
209
  enableColumnVisibility={datatableEnableColumnVisibility}
205
210
  enableClearFilters={datatableEnableClearFilters}
206
211
  hasActiveFilters={hasDatatableActiveFilters}
207
- onClearAll={() => handleClearAllFilters(() => setGlobalFilter(''))}
212
+ onClearAll={() => {
213
+ handleClearAllFilters(() => setGlobalFilter(''))
214
+ setSorting([])
215
+ }}
208
216
  searchPlaceholder={searchPlaceholder}
209
217
  searchWidth={searchWidth}
210
218
  clearFiltersLabel={clearFiltersLabel}
@@ -263,6 +271,7 @@ function Table<TData>({
263
271
  onFilterPopoverOpenChange={handlePopoverOpenChange}
264
272
  onFilterValueChange={handleFilterValueChange}
265
273
  onClearFilter={handleClearColumnFilter}
274
+ onNumberSort={handleNumberSort}
266
275
  labels={datatableFilterDropdownLabels}
267
276
  />
268
277
  <tbody>
@@ -8,6 +8,7 @@ export type ColumnFilterValue =
8
8
  | { type: 'text'; value: string }
9
9
  | { type: 'dateRange'; value: { from?: Date; to?: Date } }
10
10
  | { type: 'select'; value: string | number | null }
11
+ | { type: 'numberSort'; value: 'asc' | 'desc' | null }
11
12
 
12
13
  export type DatatableColumnFilters = Record<string, ColumnFilterValue | undefined>
13
14
 
@@ -33,7 +34,7 @@ export interface ExportButtonConfig {
33
34
  ariaLabel?: string
34
35
  }
35
36
 
36
- export type DatatableFilterType = 'text' | 'dateRange' | 'select'
37
+ export type DatatableFilterType = 'text' | 'dateRange' | 'select' | 'numberSort'
37
38
 
38
39
  export interface DatatableColumnMeta {
39
40
  filterType?: DatatableFilterType
@@ -118,3 +118,45 @@ export const ClearFilterButtonWrapper = styled.span`
118
118
  align-items: center;
119
119
  justify-content: end;
120
120
  `
121
+
122
+ export const NumberSortOptions = styled.div`
123
+ display: flex;
124
+ flex-direction: column;
125
+ gap: ${spacing.spacing4}px;
126
+ `
127
+
128
+ export const NumberSortButton = styled.button<{ $active?: boolean }>`
129
+ display: flex;
130
+ align-items: center;
131
+ gap: ${spacing.spacing8}px;
132
+ width: 100%;
133
+ padding: ${spacing.spacing8}px ${spacing.spacing12}px;
134
+ font-size: ${fontSize.fontSize14}px;
135
+ font-weight: ${fontWeight.fontWeight400};
136
+ border-radius: ${radius.radius4}px;
137
+ border: 1px solid ${({ theme, $active }) =>
138
+ $active ? parseColor(theme.colors.primary) : parseColor(theme.colors.neutral300)};
139
+ background: ${({ theme, $active }) =>
140
+ $active ? parseColor(theme.colors.primaryLighter) : parseColor(theme.colors.white)};
141
+ color: ${({ theme, $active }) =>
142
+ $active ? parseColor(theme.colors.primary) : parseColor(theme.colors.textDark)};
143
+ cursor: pointer;
144
+ transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
145
+ outline: none;
146
+
147
+ & svg {
148
+ flex-shrink: 0;
149
+ color: ${({ theme, $active }) =>
150
+ $active ? parseColor(theme.colors.primary) : parseColor(theme.colors.textMedium)};
151
+ }
152
+
153
+ &:hover:not([aria-pressed='true']) {
154
+ background: ${({ theme }) => parseColor(theme.colors.neutral100)};
155
+ border-color: ${({ theme }) => parseColor(theme.colors.neutral400)};
156
+ }
157
+
158
+ &:focus-visible {
159
+ outline: 2px solid ${({ theme }) => parseColor(theme.colors.primary)};
160
+ outline-offset: 1px;
161
+ }
162
+ `
@@ -1,5 +1,5 @@
1
1
  import * as PopoverPrimitive from '@radix-ui/react-popover'
2
- import { XIcon } from '@phosphor-icons/react'
2
+ import { ArrowDownIcon, ArrowUpIcon, XIcon } from '@phosphor-icons/react'
3
3
  import { DateRange } from 'react-day-picker'
4
4
  import DatePicker from '../../../DatePicker'
5
5
  import Select from '../../../Select'
@@ -19,6 +19,8 @@ import {
19
19
  FilterTriggerArea,
20
20
  FunnelActiveIcon,
21
21
  FunnelInactiveIcon,
22
+ NumberSortButton,
23
+ NumberSortOptions,
22
24
  StyledTextInput,
23
25
  } from './DatatableColumnFilterMenu.styles'
24
26
 
@@ -31,10 +33,10 @@ interface DatatableColumnFilterMenuProps {
31
33
  filterValue: ColumnFilterValue | undefined
32
34
  onFilterValueChange: (columnId: string, value: ColumnFilterValue | undefined) => void
33
35
  onClearFilter: (columnId: string) => void
36
+ onNumberSort: (colId: string, direction: 'asc' | 'desc' | null) => void
34
37
  labels?: DatatableFilterDropdownLabels
35
38
  }
36
39
 
37
- const SELECT_PLACEHOLDER_VALUE = '__placeholder__'
38
40
 
39
41
  function DatatableColumnFilterMenu({
40
42
  columnId,
@@ -45,25 +47,24 @@ function DatatableColumnFilterMenu({
45
47
  filterValue,
46
48
  onFilterValueChange,
47
49
  onClearFilter,
50
+ onNumberSort,
48
51
  labels = {},
49
52
  }: DatatableColumnFilterMenuProps) {
50
53
  const filterType = meta?.filterType
51
54
  const filterLabel = meta?.filterLabel ?? String(title)
52
55
  const filterPlaceholder = meta?.filterPlaceholder ?? 'Pesquisar...'
53
- const selectOptions = [
54
- { label: filterPlaceholder, value: SELECT_PLACEHOLDER_VALUE },
55
- ...(meta?.selectOptions ?? []).map((o) => ({
56
- label: o.label,
57
- value: String(o.value),
58
- })),
59
- ]
56
+ const selectOptions = (meta?.selectOptions ?? []).map((o) => ({
57
+ label: o.label,
58
+ value: String(o.value),
59
+ }))
60
60
 
61
61
  const hasActiveFilter =
62
62
  filterValue !== undefined &&
63
63
  ((filterValue.type === 'text' && filterValue.value !== '') ||
64
64
  (filterValue.type === 'select' && filterValue.value != null) ||
65
65
  (filterValue.type === 'dateRange' &&
66
- (filterValue.value.from != null || filterValue.value.to != null)))
66
+ (filterValue.value.from != null || filterValue.value.to != null)) ||
67
+ (filterValue.type === 'numberSort' && filterValue.value != null))
67
68
 
68
69
  const handleTriggerClick = (e: React.MouseEvent) => {
69
70
  e.stopPropagation()
@@ -92,14 +93,21 @@ function DatatableColumnFilterMenu({
92
93
  }
93
94
 
94
95
  const handleSelectChange = (value: string) => {
95
- if (!value || value === SELECT_PLACEHOLDER_VALUE) {
96
- onFilterValueChange(columnId, undefined)
97
- } else {
98
- onFilterValueChange(columnId, { type: 'select', value })
99
- }
96
+ onFilterValueChange(columnId, { type: 'select', value })
100
97
  onOpenChange(false)
101
98
  }
102
99
 
100
+ const handleNumberSort = (direction: 'asc' | 'desc') => {
101
+ const current = filterValue?.type === 'numberSort' ? filterValue.value : null
102
+ const next = current === direction ? null : direction
103
+ onFilterValueChange(columnId, { type: 'numberSort', value: next })
104
+ onNumberSort(columnId, next)
105
+ onOpenChange(false)
106
+ }
107
+
108
+ const currentSortDirection =
109
+ filterValue?.type === 'numberSort' ? filterValue.value : null
110
+
103
111
  const rangeValue: DateRange | undefined =
104
112
  filterValue?.type === 'dateRange'
105
113
  ? { from: filterValue.value.from, to: filterValue.value.to }
@@ -109,7 +117,7 @@ function DatatableColumnFilterMenu({
109
117
  const selectValue =
110
118
  filterValue?.type === 'select' && filterValue.value != null
111
119
  ? String(filterValue.value)
112
- : SELECT_PLACEHOLDER_VALUE
120
+ : undefined
113
121
 
114
122
  if (!filterType) {
115
123
  return (
@@ -203,21 +211,38 @@ function DatatableColumnFilterMenu({
203
211
 
204
212
  {filterType === 'select' && (
205
213
  <Select
206
- key={`select-${columnId}-${selectValue}`}
214
+ key={`select-${columnId}`}
207
215
  label=""
208
216
  options={selectOptions}
209
- defaultValue={selectValue}
217
+ value={selectValue ?? ''}
218
+ placeholder={filterPlaceholder}
210
219
  size="sm"
211
- register={{
212
- name: `datatable-select-${columnId}`,
213
- ref: () => { },
214
- onBlur: async () => { },
215
- onChange: async (e: { target: { value: string } }) => {
216
- handleSelectChange(e.target.value)
217
- },
218
- }}
220
+ onValueChange={handleSelectChange}
219
221
  />
220
222
  )}
223
+
224
+ {filterType === 'numberSort' && (
225
+ <NumberSortOptions>
226
+ <NumberSortButton
227
+ type="button"
228
+ $active={currentSortDirection === 'asc'}
229
+ onClick={() => handleNumberSort('asc')}
230
+ aria-pressed={currentSortDirection === 'asc'}
231
+ >
232
+ <ArrowUpIcon size={14} />
233
+ Crescente
234
+ </NumberSortButton>
235
+ <NumberSortButton
236
+ type="button"
237
+ $active={currentSortDirection === 'desc'}
238
+ onClick={() => handleNumberSort('desc')}
239
+ aria-pressed={currentSortDirection === 'desc'}
240
+ >
241
+ <ArrowDownIcon size={14} />
242
+ Decrescente
243
+ </NumberSortButton>
244
+ </NumberSortOptions>
245
+ )}
221
246
  </FilterPopoverContent>
222
247
  </PopoverPrimitive.Content>
223
248
  </PopoverPrimitive.Portal>