Skip to content

Commit 5ac2e7d

Browse files
authored
Merge pull request #731 from code0-tech/feat/730
New dashboard design concept ans general fixes
2 parents 6969c10 + 944f643 commit 5ac2e7d

17 files changed

Lines changed: 1857 additions & 596 deletions

package-lock.json

Lines changed: 422 additions & 560 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"@radix-ui/react-dialog": "^1.1.15",
3535
"@radix-ui/react-dropdown-menu": "^2.1.16",
3636
"@radix-ui/react-hover-card": "^1.1.15",
37-
"@radix-ui/react-one-time-password-field": "^0.1.8",
37+
"@radix-ui/react-one-time-password-field": "^0.1.13",
3838
"@radix-ui/react-progress": "^1.1.8",
3939
"@radix-ui/react-radio-group": "^1.3.8",
4040
"@radix-ui/react-scroll-area": "^1.2.10",
@@ -61,9 +61,6 @@
6161
"@types/react-syntax-highlighter": "^15.5.13",
6262
"@uiw/codemirror-themes": "^4.25.10",
6363
"@uiw/react-codemirror": "^4.25.10",
64-
"slate": "^0.124.1",
65-
"slate-history": "^0.113.1",
66-
"slate-react": "^0.125.1",
6764
"@uiw/react-json-view": "^2.0.0-alpha.43",
6865
"@vitejs/plugin-react": "^5.2.0",
6966
"@vitest/browser-playwright": "^4.1.2",
@@ -89,6 +86,9 @@
8986
"rimraf": "^6.1.3",
9087
"sass": "^1.99.0",
9188
"sass-loader": "^16.0.8",
89+
"slate": "^0.124.1",
90+
"slate-history": "^0.113.1",
91+
"slate-react": "^0.125.1",
9292
"sonner": "^2.0.7",
9393
"storybook": "^10.4.2",
9494
"style-loader": "^4.0.0",
@@ -123,7 +123,7 @@
123123
"@radix-ui/react-dialog": "^1.1.15",
124124
"@radix-ui/react-dropdown-menu": "^2.1.16",
125125
"@radix-ui/react-hover-card": "^1.1.15",
126-
"@radix-ui/react-one-time-password-field": "^0.1.8",
126+
"@radix-ui/react-one-time-password-field": "^0.1.13",
127127
"@radix-ui/react-progress": "^1.1.8",
128128
"@radix-ui/react-radio-group": "^1.3.8",
129129
"@radix-ui/react-scroll-area": "^1.2.10",
@@ -144,10 +144,10 @@
144144
"react-dom": "^18.0.0 || ^19.0.0",
145145
"react-resizable-panels": "^4.3.1",
146146
"react-zoom-pan-pinch": "^3.7.0",
147-
"sonner": "^2.0.7",
148147
"slate": "^0.124.0",
149148
"slate-history": "^0.113.0",
150149
"slate-react": "^0.125.0",
150+
"sonner": "^2.0.7",
151151
"zustand": ">=4.3.0 <5.0.0"
152152
},
153153
"publishConfig": {

src/components/Dashboard.stories.tsx

Lines changed: 1054 additions & 0 deletions
Large diffs are not rendered by default.

src/components/avatar/Avatar.style.scss

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
align-items: center;
99
justify-content: center;
1010
overflow: hidden;
11-
border-radius: 50%;
1211

1312
& {
1413
@include box.box();
@@ -23,11 +22,19 @@
2322
&--identicon {
2423
> div > div {
2524
--color: inherit;
26-
background: helpers.backgroundColor(variables.$tertiary);
25+
background: transparent;
26+
width: calc(var(--size) * 1px);
27+
height: calc(var(--size) * 1px);
28+
29+
> span >svg {
30+
width: calc(var(--size) * 1px);
31+
height: calc(var(--size) * 1px);
32+
}
2733
}
2834

2935
> div > div > span, > div > div > p {
30-
color: var(--color)
36+
color: var(--color);
37+
font-size: calc(var(--size) * 1px);
3138
}
3239
}
3340
}

src/components/avatar/Avatar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const AvatarImage: React.FC<AvatarImageProps> = ({src, size}) => {
3030

3131
const AvatarIdenticon: React.FC<AvatarIdenticonProps> = ({identifier, size, color, type}) => {
3232

33-
return <div style={{"--color": color ?? hashToColor(identifier)} as React.CSSProperties}>
33+
return <div style={{"--color": color ?? hashToColor(identifier), "--size": size} as React.CSSProperties}>
3434
<Identicon style={type ?? "shape"} value={identifier} size={size}/>
3535
</div>
3636

src/components/card/Card.style.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@
105105
}
106106
}
107107

108+
&.card--clickable {
109+
@include box.boxHover($color);
110+
@include box.boxActive($color);
111+
cursor: pointer;
112+
}
113+
108114
}
109115

110116
.card--outline-#{$name} {

src/components/card/Card.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export interface Card extends Component<HTMLDivElement> {
2121
outlineColor?: Color
2222
//defaults to false
2323
dashed?: boolean
24+
//defaults to false; adds hover + active (pressed) styling and a pointer cursor
25+
clickable?: boolean
2426
paddingSize?: Sizes
2527
}
2628

@@ -37,6 +39,7 @@ export const Card: React.FC<Card> = (props) => {
3739
outlineColor = "secondary",
3840
outline = false,
3941
dashed = false,
42+
clickable = false,
4043
paddingSize = "xl",
4144
...args
4245
} = props
@@ -50,9 +53,10 @@ export const Card: React.FC<Card> = (props) => {
5053
${outline ? `card--outline-${outlineColor}` : ""}
5154
${gradient ? "card--gradient" : ""}
5255
${borderColor ? `card--border-${borderColor}` : ""}
53-
${dashed ? `card--border--dashed` : ""}
54-
${gradient ? `card--gradient-${gradientColor}` : ""}
55-
56+
${dashed ? `card--border--dashed` : ""}
57+
${gradient ? `card--gradient-${gradientColor}` : ""}
58+
${clickable ? "card--clickable" : ""}
59+
5660
`
5761
, args)}>
5862
{children}

src/components/data-table/DataTable.stories.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {Flex} from "../flex/Flex";
1212
import {ButtonGroup} from "../button-group/ButtonGroup";
1313
import {Badge} from "../badge/Badge";
1414
import {DataTableColumn} from "./DataTableColumn";
15+
import {DataTableHeader, DataTableHeaderColumn} from "./DataTableHeader";
1516

1617
export const Default = () => {
1718

@@ -198,6 +199,60 @@ export const Default = () => {
198199
</>
199200
}
200201

202+
export const Pagination = () => {
203+
204+
const testData = Array.from({length: 23}, (_, i) => ({
205+
name: `Project ${i + 1}`,
206+
payload: {id: String(i + 1)},
207+
}))
208+
209+
return <DataTable data={testData} pagination limit={5}>
210+
{(item: any) => <>
211+
<DataTableColumn>
212+
<Avatar identifier={String(item.name)}/>
213+
</DataTableColumn>
214+
<DataTableColumn>
215+
<Text>{String(item.name)}</Text>
216+
</DataTableColumn>
217+
<DataTableColumn>
218+
<Text>{item.payload.id}</Text>
219+
</DataTableColumn>
220+
</>}
221+
</DataTable>
222+
}
223+
224+
225+
export const Header = () => {
226+
227+
const testData = Array.from({length: 12}, (_, i) => ({
228+
name: `Project ${i + 1}`,
229+
payload: {id: String(i + 1)},
230+
}))
231+
232+
return <DataTable data={testData} pagination limit={5}>
233+
<DataTableHeader>
234+
<DataTableHeaderColumn/>
235+
<DataTableHeaderColumn>
236+
<Text size={"sm"} hierarchy={"tertiary"}>Name</Text>
237+
</DataTableHeaderColumn>
238+
<DataTableHeaderColumn>
239+
<Text size={"sm"} hierarchy={"tertiary"}>Identifier</Text>
240+
</DataTableHeaderColumn>
241+
</DataTableHeader>
242+
{(item: any) => <>
243+
<DataTableColumn>
244+
<Avatar identifier={String(item.name)}/>
245+
</DataTableColumn>
246+
<DataTableColumn>
247+
<Text>{String(item.name)}</Text>
248+
</DataTableColumn>
249+
<DataTableColumn>
250+
<Text>{item.payload.id}</Text>
251+
</DataTableColumn>
252+
</>}
253+
</DataTable>
254+
}
255+
201256

202257
export default {
203258
title: "DataTable",

src/components/data-table/DataTable.style.scss

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,46 @@
88
-webkit-border-vertical-spacing: 0;
99
border-spacing: 0;
1010

11+
&__header {
12+
th {
13+
text-align: left;
14+
padding: variables.$xs 0;
15+
border-bottom: 0.5px solid helpers.borderColor();
16+
}
17+
18+
th:last-child {
19+
width: 1px;
20+
white-space: nowrap;
21+
}
22+
}
23+
1124
&__row {
1225
td {
1326
border-bottom: 0.5px solid helpers.borderColor();
1427
border-top: 0.5px solid helpers.borderColor();
15-
padding: variables.$xs variables.$xxs;
28+
padding: variables.$xs 0;
1629
}
1730

1831
&:hover {
19-
background: helpers.backgroundColor();
32+
background: rgba(255,255,255, .05);
2033
cursor: pointer;
2134
}
2235

36+
&:active {
37+
background: rgba(255,255,255, .1);
38+
}
39+
2340
td:last-child {
2441
width: 1px;
2542
white-space: nowrap;
2643
}
2744
}
2845

46+
&__pagination {
47+
display: flex;
48+
align-items: center;
49+
justify-content: space-between;
50+
gap: variables.$xs;
51+
}
52+
2953
}

src/components/data-table/DataTable.tsx

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import React from "react";
22
import "./DataTable.style.scss"
33
import {Component, mergeComponentProps} from "../../utils";
4+
import {Flex} from "../flex/Flex";
5+
import {DataTableHeader} from "./DataTableHeader";
6+
import {DataTablePaginationContext} from "./DataTablePagination";
47

58
export type DataTableFilterOperator = "isOneOf" | "isNotOneOf"
69

@@ -15,15 +18,21 @@ export interface DataTableSortProps {
1518
[key: string]: 'asc' | 'desc' | undefined
1619
}
1720

21+
export type DataTableRowRenderer<T> = (item: T, index: number) => React.ReactNode
22+
1823
export interface DataTableProps<T> extends Omit<Component<HTMLTableElement>, 'data' | 'children' | 'onSelect'> {
1924
data: Array<T>
2025
sort?: DataTableSortProps
2126
filter?: DataTableFilterProps
27+
limit?: number
28+
//when enabled the table splits its rows into pages of `limit` (default 10) and creates the pagination context
29+
pagination?: boolean
2230
loading?: boolean
2331
loadingComponent?: React.ReactNode
2432
emptyComponent?: React.ReactNode
2533
onSelect?: (item: T | undefined) => void
26-
children?: (item: T, index: number) => React.ReactNode
34+
//the row renderer (function child) plus an optional <DataTableHeader/> and any pagination controls, e.g. <DataTablePagination/>, to render below the table
35+
children?: DataTableRowRenderer<T> | React.ReactNode | Array<DataTableRowRenderer<T> | React.ReactNode>
2736
}
2837

2938
const getNestedValue = (obj: any, path: string): any => {
@@ -41,7 +50,27 @@ const getNestedValue = (obj: any, path: string): any => {
4150

4251
export const DataTable = <T, >(props: DataTableProps<T>) => {
4352

44-
const {data, sort, filter, loading, loadingComponent, emptyComponent, onSelect, children, ...rest} = props
53+
const {
54+
data,
55+
sort,
56+
filter,
57+
limit,
58+
pagination,
59+
loading,
60+
loadingComponent,
61+
emptyComponent,
62+
onSelect,
63+
children,
64+
...rest
65+
} = props
66+
67+
const [page, setPage] = React.useState(0)
68+
69+
// children may hold the row renderer (a function), an optional <DataTableHeader/> and pagination controls (elements)
70+
const childArray: Array<DataTableRowRenderer<T> | React.ReactNode> = Array.isArray(children) ? children : [children]
71+
const renderRow = childArray.find((child): child is DataTableRowRenderer<T> => typeof child === "function")
72+
const headerNode = childArray.find((child) => React.isValidElement(child) && child.type === DataTableHeader) as React.ReactNode
73+
const footerNodes = childArray.filter((child) => typeof child !== "function" && child !== headerNode) as React.ReactNode[]
4574

4675
const filteredData = data.filter(item => {
4776
return Object.entries(filter || {}).every(([key, {operator, value}]) => {
@@ -84,17 +113,44 @@ export const DataTable = <T, >(props: DataTableProps<T>) => {
84113
})
85114
}, [filteredData, sort])
86115

116+
const pageSize = typeof limit === "number" ? limit : 10
117+
const pageCount = pagination ? Math.max(1, Math.ceil(sortedData.length / pageSize)) : 1
118+
// clamp: filtering/sorting can shrink the data below the current page
119+
const currentPage = Math.min(page, pageCount - 1)
120+
121+
React.useEffect(() => {
122+
if (page !== currentPage) setPage(currentPage)
123+
}, [page, currentPage])
124+
125+
const visibleData = pagination
126+
? sortedData.slice(currentPage * pageSize, currentPage * pageSize + pageSize)
127+
: typeof limit === "number" ? sortedData.slice(0, limit) : sortedData
128+
87129
// @ts-ignore
88-
return <table {...mergeComponentProps("data-table", rest)}>
89-
{sortedData.map((item, i) => {
130+
const table = <table {...mergeComponentProps("data-table", rest)}>
131+
{headerNode}
132+
<tbody>
133+
{visibleData.map((item, i) => {
90134
return <tr className={"data-table__row"} onClick={() => onSelect?.(item)}>
91-
{children?.(item, i)}
135+
{renderRow?.(item, i)}
92136
</tr>
93137
})}
94138
{sortedData.length === 0 && !loading && emptyComponent ? (
95139
<tr className={"data-table__row"} onClick={() => onSelect?.(undefined)}>
96140
{emptyComponent}
97141
</tr>
98142
) : null}
143+
</tbody>
99144
</table>
145+
146+
if (!pagination) return table
147+
148+
// DataTable owns the pagination state and provides the context that the controls
149+
// (e.g. a <DataTablePagination/> placed as a child) read from.
150+
return <DataTablePaginationContext.Provider value={{page: currentPage, pageCount, setPage}}>
151+
<Flex style={{flexDirection: "column", gap: "0.5rem"}}>
152+
{table}
153+
{footerNodes.map((node, i) => <React.Fragment key={i}>{node}</React.Fragment>)}
154+
</Flex>
155+
</DataTablePaginationContext.Provider>
100156
}

0 commit comments

Comments
 (0)