w-design-system 0.0.91 → 0.0.92

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.92",
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,76 @@ 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[]>([])
122
121
 
123
122
  const handleExpandClick = (id: number) => {
124
- setExpandedRow(expandedRow === id ? null : id)
123
+ setExpandedRows(prev => (prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]))
125
124
  }
126
125
 
127
- // @ts-ignore
128
126
  return (
129
127
  <TableContainer>
130
128
  <StyledTable>
131
129
  <Thead>
132
130
  <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
- )}
131
+ {columns.map((col, index) => (
132
+ <Th key={index} width={col.width} align={col.align}>
133
+ <Paragraph color={theme.colors.shade30} textTransform={'uppercase'} strongBod>
134
+ {col.header}
135
+ </Paragraph>
136
+ </Th>
137
+ ))}
157
138
  </Tr>
158
139
  </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}>
140
+ </StyledTable>
141
+ <TbodyContainer height={height}>
142
+ <StyledTable>
143
+ <Tbody>
144
+ {loading ? (
145
+ <LoadingWrapper>
146
+ <ContainerLoading size={'small'} />
147
+ </LoadingWrapper>
148
+ ) : data.length === 0 ? (
149
+ <EmptyStateWrapper>
150
+ <EmptyState
151
+ title={emptyStateMessage?.title}
152
+ subTitle={emptyStateMessage?.subTitle}
153
+ description={emptyStateMessage?.description}
154
+ />
155
+ </EmptyStateWrapper>
156
+ ) : (
157
+ data.map(row => (
158
+ <React.Fragment key={row.id}>
159
+ <Tr
160
+ striped={striped}
161
+ clickable={rowClickable && !!onRowClick}
162
+ onClick={() => rowClickable && onRowClick && onRowClick(row)}
163
+ >
164
+ {columns.map((col, colIndex) => (
165
+ <Td key={colIndex} width={col.width} align={col.align || 'left'}>
198
166
  {col.Cell ? (
167
+ // Exemplo de uso de stopPropagation():
168
+ // Se o Cell renderiza um botão, no onClick do botão faça event.stopPropagation()
199
169
  col.Cell(row, handleExpandClick)
200
- ) : (
201
- <Paragraph color={theme.colors.shade40}>{row[col.accessor]}</Paragraph>
202
- )}
170
+ ) : col.accessor ? (
171
+ <Paragraph color={theme.colors.shade40}>{String(row[col.accessor])}</Paragraph>
172
+ ) : null}
203
173
  </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>
174
+ ))}
175
+ </Tr>
176
+ {expandedRows.includes(row.id) &&
177
+ 'containerColapsed' in row &&
178
+ typeof (row as any).containerColapsed === 'function' && (
179
+ <ExpandableRow>{(row as any).containerColapsed(row)}</ExpandableRow>
180
+ )}
181
+ </React.Fragment>
182
+ ))
183
+ )}
184
+ </Tbody>
185
+ </StyledTable>
186
+ </TbodyContainer>
215
187
  </TableContainer>
216
188
  )
217
189
  }
@@ -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,6 +201,7 @@ 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'}>
@@ -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',