w-design-system 0.0.91 → 0.0.93

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "w-design-system",
3
- "version": "0.0.91",
3
+ "version": "0.0.93",
4
4
  "main": "dist/w-design-system.umd.js",
5
5
  "module": "dist/w-design-system.es.js",
6
6
  "types": "dist/index.d.ts",
@@ -5,28 +5,31 @@ import { theme } from '../Themes'
5
5
  import { ContainerLoading } from '../Loading'
6
6
  import { EmptyState } from '../EmptyState'
7
7
 
8
- type Column = {
8
+ type Column<T> = {
9
9
  header: string
10
- accessor: string
10
+ accessor?: keyof T
11
11
  width?: string
12
12
  sortable?: boolean
13
- onClick?: (row: any) => void
14
- Cell?: (row: any, handleExpandClick: (id: number) => void) => React.ReactNode
13
+ onClick?: (row: T) => void
14
+ Cell?: (row: T, handleExpandClick: (id: number) => void) => React.ReactNode
15
15
  align?: 'left' | 'center' | 'right'
16
16
  }
17
17
 
18
- type emptyStateMessage = {
18
+ type EmptyStateMessage = {
19
19
  title: string
20
20
  subTitle?: string
21
21
  description?: string
22
22
  }
23
- type TableProps = {
24
- columns: Column[] | any
25
- data: any[]
23
+
24
+ type TableProps<T extends { id: number }> = {
25
+ columns: Column<T>[]
26
+ data: T[]
26
27
  height?: string
27
28
  loading?: boolean
28
- emptyStateMessage?: emptyStateMessage
29
+ emptyStateMessage?: EmptyStateMessage
29
30
  striped?: boolean
31
+ rowClickable?: boolean
32
+ onRowClick?: (row: T) => void
30
33
  }
31
34
 
32
35
  const TableContainer = styled.div`
@@ -34,40 +37,33 @@ const TableContainer = styled.div`
34
37
  flex-direction: column;
35
38
  width: 100%;
36
39
  height: 100%;
37
- overflow-x: auto;
38
40
  `
39
41
 
40
42
  const StyledTable = styled.table`
41
43
  width: 100%;
42
44
  border-collapse: collapse;
43
- height: 100%;
45
+ table-layout: fixed;
44
46
  `
45
47
 
46
48
  const Thead = styled.thead`
47
- display: table-header-group;
48
49
  border-bottom: 1px solid #ddd;
49
50
  `
50
51
 
51
- const Tbody = styled.tbody<{ height?: string }>`
52
- display: block;
53
- width: 100%;
52
+ const TbodyContainer = styled.div<{ height?: string }>`
54
53
  overflow-y: auto;
55
54
  height: ${props => props.height || '100%'};
56
55
  `
57
56
 
58
- const Tr = styled.tr<{ striped?: boolean }>`
59
- display: table;
60
- width: 100%;
61
- table-layout: fixed;
62
- `
63
- const TrRow = styled.tr<{ striped?: boolean }>`
57
+ const Tbody = styled.tbody``
58
+
59
+ const Tr = styled.tr<{ striped?: boolean; clickable?: boolean }>`
64
60
  display: table;
65
61
  width: 100%;
66
62
  table-layout: fixed;
67
- &:nth-of-type(n + 2) {
68
- background-color: ${props => (props.striped ? '#f9f9f9' : 'transparent')};
69
- }
70
- &:nth-of-type(odd) {
63
+ background-color: ${props => (props.striped ? '#f9f9f9' : 'transparent')};
64
+ cursor: ${props => (props.clickable ? 'pointer' : 'default')};
65
+
66
+ &:nth-child(odd) {
71
67
  background-color: ${props => (props.striped ? '#fff' : 'transparent')};
72
68
  }
73
69
  `
@@ -76,9 +72,10 @@ const Th = styled.th<{ width?: string; align?: 'left' | 'center' | 'right' }>`
76
72
  padding: 10px;
77
73
  text-align: ${props => props.align || 'left'};
78
74
  width: ${props => props.width || 'auto'};
75
+ vertical-align: middle;
79
76
  `
80
77
 
81
- const Td = styled.td<{ width?: string; align: 'left' | 'center' | 'right' }>`
78
+ const Td = styled.td<{ width?: string; align?: 'left' | 'center' | 'right' }>`
82
79
  padding: 12px;
83
80
  width: ${props => props?.width || 'auto'};
84
81
  text-align: ${props => props?.align || 'left'};
@@ -106,7 +103,7 @@ const EmptyStateWrapper = styled.div`
106
103
  padding: 20px;
107
104
  `
108
105
 
109
- export const Table: React.FC<TableProps> = ({
106
+ export const Table = <T extends { id: number }>({
110
107
  columns,
111
108
  data,
112
109
  height = '100%',
@@ -117,101 +114,85 @@ export const Table: React.FC<TableProps> = ({
117
114
  description: 'Não há registros para exibir',
118
115
  },
119
116
  striped = false,
120
- }) => {
121
- const [expandedRow, setExpandedRow] = useState<number | null>(null)
117
+ rowClickable = false,
118
+ onRowClick,
119
+ }: TableProps<T>) => {
120
+ const [expandedRows, setExpandedRows] = useState<number[]>([])
121
+ const [selectedRowId, setSelectedRowId] = useState<number | null>(null)
122
122
 
123
+ const handleRowClick = (row: T) => {
124
+ setSelectedRowId(row.id)
125
+ onRowClick?.(row) // dispara a função recebida por props
126
+ }
123
127
  const handleExpandClick = (id: number) => {
124
- setExpandedRow(expandedRow === id ? null : id)
128
+ setExpandedRows(prev => (prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]))
125
129
  }
126
130
 
127
- // @ts-ignore
128
131
  return (
129
132
  <TableContainer>
130
133
  <StyledTable>
131
134
  <Thead>
132
135
  <Tr striped={striped}>
133
- {columns.map(
134
- (
135
- col: {
136
- width: string | undefined
137
- align: any
138
- header:
139
- | string
140
- | number
141
- | boolean
142
- | React.ReactElement<any, string | React.JSXElementConstructor<any>>
143
- | Iterable<React.ReactNode>
144
- | React.ReactPortal
145
- | null
146
- | undefined
147
- },
148
- index: React.Key | null | undefined,
149
- ) => (
150
- <Th key={index} width={col.width} align={col.align}>
151
- <Paragraph color={theme.colors.shade30} textTransform={'uppercase'} strongBod>
152
- {col.header}
153
- </Paragraph>
154
- </Th>
155
- ),
156
- )}
136
+ {columns.map((col, index) => (
137
+ <Th key={index} width={col.width} align={col.align}>
138
+ <Paragraph color={theme.colors.shade30} textTransform={'uppercase'} strongBod>
139
+ {col.header}
140
+ </Paragraph>
141
+ </Th>
142
+ ))}
157
143
  </Tr>
158
144
  </Thead>
159
- <Tbody height={height}>
160
- {loading ? (
161
- <LoadingWrapper>
162
- <ContainerLoading size={'small'} />
163
- </LoadingWrapper>
164
- ) : data.length === 0 ? (
165
- <EmptyStateWrapper>
166
- <EmptyState
167
- title={emptyStateMessage?.title}
168
- subTitle={emptyStateMessage?.subTitle}
169
- description={emptyStateMessage?.description}
170
- />
171
- </EmptyStateWrapper>
172
- ) : (
173
- data.map((row, rowIndex) => (
174
- <React.Fragment key={row.id}>
175
- <TrRow striped={striped}>
176
- {columns.map(
177
- (
178
- col: {
179
- width: string | undefined
180
- align: any
181
- Cell: (
182
- arg0: any,
183
- arg1: (id: number) => void,
184
- ) =>
185
- | string
186
- | number
187
- | boolean
188
- | React.ReactElement<any, string | React.JSXElementConstructor<any>>
189
- | Iterable<React.ReactNode>
190
- | React.ReactPortal
191
- | null
192
- | undefined
193
- accessor: string | number
194
- },
195
- colIndex: React.Key | null | undefined,
196
- ) => (
197
- <Td key={colIndex} width={col?.width} align={col?.align}>
145
+ </StyledTable>
146
+ <TbodyContainer height={height}>
147
+ <StyledTable>
148
+ <Tbody>
149
+ {loading ? (
150
+ <LoadingWrapper>
151
+ <ContainerLoading size={'small'} />
152
+ </LoadingWrapper>
153
+ ) : data.length === 0 ? (
154
+ <EmptyStateWrapper>
155
+ <EmptyState
156
+ title={emptyStateMessage?.title}
157
+ subTitle={emptyStateMessage?.subTitle}
158
+ description={emptyStateMessage?.description}
159
+ />
160
+ </EmptyStateWrapper>
161
+ ) : (
162
+ data.map(row => (
163
+ <React.Fragment key={row.id}>
164
+ <Tr
165
+ striped={striped}
166
+ clickable={rowClickable && !!onRowClick}
167
+ onClick={() => rowClickable && onRowClick && handleRowClick(row)}
168
+ style={{
169
+ border: `1px solid ${selectedRowId === row.id ? theme.colors.cyan30 : 'transparent'}`,
170
+ background: selectedRowId === row.id ? theme.colors.cyan10 : 'transparent',
171
+ }}
172
+ >
173
+ {columns.map((col, colIndex) => (
174
+ <Td key={colIndex} width={col.width} align={col.align || 'left'}>
198
175
  {col.Cell ? (
176
+ // Exemplo de uso de stopPropagation():
177
+ // Se o Cell renderiza um botão, no onClick do botão faça event.stopPropagation()
199
178
  col.Cell(row, handleExpandClick)
200
- ) : (
201
- <Paragraph color={theme.colors.shade40}>{row[col.accessor]}</Paragraph>
202
- )}
179
+ ) : col.accessor ? (
180
+ <Paragraph color={theme.colors.shade40}>{String(row[col.accessor])}</Paragraph>
181
+ ) : null}
203
182
  </Td>
204
- ),
205
- )}
206
- </TrRow>
207
- {expandedRow === row.id && row.containerColapsed && (
208
- <ExpandableRow>{row.containerColapsed(row)}</ExpandableRow>
209
- )}
210
- </React.Fragment>
211
- ))
212
- )}
213
- </Tbody>
214
- </StyledTable>
183
+ ))}
184
+ </Tr>
185
+ {expandedRows.includes(row.id) &&
186
+ 'containerColapsed' in row &&
187
+ typeof (row as any).containerColapsed === 'function' && (
188
+ <ExpandableRow>{(row as any).containerColapsed(row)}</ExpandableRow>
189
+ )}
190
+ </React.Fragment>
191
+ ))
192
+ )}
193
+ </Tbody>
194
+ </StyledTable>
195
+ </TbodyContainer>
215
196
  </TableContainer>
216
197
  )
217
198
  }
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'
2
2
  import React from 'react'
3
3
  import { ButtonRound } from '../ButtonRound'
4
4
  import { dumpSVG, editSVG } from '../../assets/icon'
5
- import { Table } from './index'
5
+ import { Table } from './index' // importe também o tipo Column
6
6
  import CardStories from '../CardStories'
7
7
  import { Paragraph } from '../Paragraph'
8
8
 
@@ -27,44 +27,51 @@ const meta: Meta<typeof Table> = {
27
27
  description: 'Loading state of the table',
28
28
  },
29
29
  emptyStateMessage: {
30
- control: 'text',
30
+ control: 'object',
31
31
  description: 'Message to display when there is no data',
32
32
  },
33
+ rowClickable: {
34
+ control: 'boolean',
35
+ description: 'Permite clicar em qualquer parte da linha',
36
+ },
37
+ onRowClick: {
38
+ control: false,
39
+ description: 'Função chamada ao clicar em uma linha',
40
+ },
33
41
  },
34
42
  } satisfies Meta<typeof Table>
35
43
 
36
44
  export default meta
37
45
  type Story = StoryObj<typeof meta>
38
46
 
39
- const columns = [
40
- {
41
- header: 'id',
42
- accessor: 'id',
43
- width: 'auto',
44
- },
45
- {
46
- header: 'item',
47
- accessor: 'item',
48
- width: 'auto',
49
- sortable: true,
50
- },
51
- {
52
- header: 'unidades',
53
- accessor: 'unidades',
54
- width: 'auto',
55
- sortable: true,
56
- },
47
+ type Column<T> = {
48
+ header: string
49
+ accessor?: keyof T
50
+ width?: string
51
+ sortable?: boolean
52
+ onClick?: (row: T) => void
53
+ Cell?: (row: T, handleExpandClick: (id: number) => void) => React.ReactNode
54
+ align?: 'left' | 'center' | 'right'
55
+ }
56
+ // Aqui anotamos explicitamente o tipo das colunas para evitar inferência incorreta de "string":
57
+ const columns: Column<any>[] = [
58
+ { header: 'id', accessor: 'id', width: 'auto' },
59
+ { header: 'item', accessor: 'item', width: 'auto', sortable: true },
60
+ { header: 'unidades', accessor: 'unidades', width: 'auto', sortable: true },
57
61
  {
58
62
  header: 'marca/ fabricante',
59
63
  accessor: 'marca_fabricante',
60
64
  width: 'auto',
61
65
  Cell: (row: any) => (
62
66
  <div
63
- onClick={() => alert('aqui')}
67
+ onClick={e => {
68
+ e.stopPropagation()
69
+ alert('Marca clicada')
70
+ }}
64
71
  style={{
65
72
  background: 'red',
66
73
  color: 'white',
67
- padding: '4px, 2px',
74
+ padding: '4px 2px',
68
75
  borderRadius: '8px',
69
76
  display: 'flex',
70
77
  alignItems: 'center',
@@ -75,26 +82,21 @@ const columns = [
75
82
  </div>
76
83
  ),
77
84
  },
85
+ { header: 'modelo', accessor: 'modelo', width: 'auto', sortable: true },
78
86
  {
79
- header: 'modelo',
80
- accessor: 'modelo',
81
- width: 'auto',
82
- sortable: true,
83
- },
84
- {
85
- header: 'quantidade solitada',
87
+ header: 'quantidade solicitada',
86
88
  accessor: 'quantidade_solitada',
87
89
  width: 'auto',
88
90
  sortable: true,
89
91
  },
90
92
  {
91
- header: 'valor estimado unitario',
93
+ header: 'valor unitário',
92
94
  accessor: 'valor_estimado_unitario',
93
95
  width: 'auto',
94
96
  sortable: true,
95
97
  },
96
98
  {
97
- header: 'valor estimado total',
99
+ header: 'valor total',
98
100
  accessor: 'valor_estimado_total',
99
101
  width: 'auto',
100
102
  sortable: true,
@@ -103,11 +105,24 @@ const columns = [
103
105
  header: 'Actions',
104
106
  accessor: 'valor_estimado_total',
105
107
  width: '100px',
106
- align: 'center',
108
+ align: 'center', // agora já é inferido corretamente como 'center'
107
109
  Cell: (row: any, handleExpandClick: (id: number) => void) => (
108
110
  <div style={{ display: 'flex', gap: '8px', justifyContent: 'center' }}>
109
- <ButtonRound icon={editSVG} onClick={() => handleExpandClick(row.id)} />
110
- <ButtonRound icon={dumpSVG} onClick={() => alert(`Delete ID: ${row.id}`)} variant={'danger'} />
111
+ <ButtonRound
112
+ icon={editSVG}
113
+ onClick={e => {
114
+ e.stopPropagation()
115
+ handleExpandClick(row.id)
116
+ }}
117
+ />
118
+ <ButtonRound
119
+ icon={dumpSVG}
120
+ onClick={e => {
121
+ e.stopPropagation()
122
+ alert(`Delete ID: ${row.id}`)
123
+ }}
124
+ variant={'danger'}
125
+ />
111
126
  </div>
112
127
  ),
113
128
  },
@@ -119,6 +134,9 @@ export const Default: Story = {
119
134
  columns: columns,
120
135
  striped: true,
121
136
  loading: false,
137
+ rowClickable: true,
138
+ onRowClick: (row: any) => alert(`Você clicou na linha ID: ${row.id}`),
139
+
122
140
  emptyStateMessage: {
123
141
  title: 'Nenhum registro encontrado',
124
142
  subTitle: 'Não há registros para exibir',
@@ -130,53 +148,52 @@ export const Default: Story = {
130
148
  item: 'Beetlejuice',
131
149
  unidades: '1988',
132
150
  marca_fabricante: 'adidas',
133
- modelo: 'modelo',
151
+ modelo: 'modelo X',
134
152
  quantidade_solitada: '10000',
135
153
  valor_estimado_unitario: '1000',
136
154
  valor_estimado_total: '10000000',
137
- colapsed: true,
138
155
  containerColapsed: ({ item }: any) => (
139
156
  <div>
140
157
  <Paragraph size={'large'} heavyBod>
141
158
  {item}
142
159
  </Paragraph>
143
- <Paragraph>Esta é uma informação adicional sobre .</Paragraph>
160
+ <Paragraph>Esta é uma informação adicional sobre {item}.</Paragraph>
144
161
  </div>
145
162
  ),
146
163
  },
147
164
  {
148
165
  id: 2,
149
- item: 'Beetlejuice',
150
- unidades: '1988',
151
- marca_fabricante: 'adidas',
152
- modelo: 'modelo',
153
- quantidade_solitada: '10000',
154
- valor_estimado_unitario: '1000',
155
- valor_estimado_total: '10000000',
166
+ item: 'Ghostbusters',
167
+ unidades: '1984',
168
+ marca_fabricante: 'nike',
169
+ modelo: 'modelo Y',
170
+ quantidade_solitada: '20000',
171
+ valor_estimado_unitario: '2000',
172
+ valor_estimado_total: '40000000',
156
173
  containerColapsed: ({ item }: any) => (
157
174
  <div>
158
175
  <Paragraph size={'large'} heavyBod>
159
176
  {item}
160
177
  </Paragraph>
161
- <Paragraph>Esta é uma informação adicional sobre .</Paragraph>
178
+ <Paragraph>Detalhes adicionais sobre {item}.</Paragraph>
162
179
  </div>
163
180
  ),
164
181
  },
165
182
  {
166
183
  id: 3,
167
- item: 'Beetlejuice',
168
- unidades: '1988',
169
- marca_fabricante: 'adidas',
170
- modelo: 'modelo',
171
- quantidade_solitada: '10000',
172
- valor_estimado_unitario: '1000',
173
- valor_estimado_total: '10000000',
184
+ item: 'Mad Max',
185
+ unidades: '1979',
186
+ marca_fabricante: 'puma',
187
+ modelo: 'modelo Z',
188
+ quantidade_solitada: '15000',
189
+ valor_estimado_unitario: '1500',
190
+ valor_estimado_total: '22500000',
174
191
  containerColapsed: ({ item }: any) => (
175
192
  <div>
176
193
  <Paragraph size={'large'} heavyBod>
177
194
  {item}
178
195
  </Paragraph>
179
- <Paragraph>Esta é uma informação adicional sobre .</Paragraph>
196
+ <Paragraph>Mais informações sobre {item} aqui.</Paragraph>
180
197
  </div>
181
198
  ),
182
199
  },
@@ -184,11 +201,21 @@ export const Default: Story = {
184
201
  },
185
202
  }
186
203
 
204
+ // @ts-ignore
187
205
  export const LoadingState: Story = {
188
206
  render: () => (
189
207
  <CardStories title={'Tabela'} subTitle={'tabela-loading'}>
190
208
  <div style={{ width: '900px', height: '800px' }}>
191
- <Table columns={columns} loading={true} height={'100%'} data={[]} />
209
+ <Table
210
+ columns={columns}
211
+ loading={true}
212
+ height={'100%'}
213
+ data={[]}
214
+ onRowClick={student => {
215
+ console.log('Linha selecionada:', student)
216
+ // aqui você pode abrir um modal, preencher formulário, etc.
217
+ }}
218
+ />
192
219
  </div>
193
220
  </CardStories>
194
221
  ),
@@ -47,10 +47,6 @@ const special = {
47
47
  cyan30opacity16: 'rgba(33, 126, 253, 0.16)',
48
48
  }
49
49
 
50
- const shadow = {
51
- shadow1: '0px 2px 5px 1px rgba(0, 0, 0, 0.05)',
52
- }
53
-
54
50
  const white = '#FFFFFF'
55
51
  const black = '#333333'
56
52
  const transparent = 'transparent'
@@ -95,7 +91,6 @@ export const theme = {
95
91
  transparent,
96
92
  ...special,
97
93
  },
98
- shadow,
99
94
  fonts: ['Nunito Sans', 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'],
100
95
  title: {
101
96
  medium: '24px',