@g4rcez/components 0.0.1 → 0.0.2
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 +11 -2
- package/.idea/bigweld.iml +0 -12
- package/.idea/codeStyles/Project.xml +0 -72
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/inspectionProfiles/Project_Default.xml +0 -30
- package/.idea/jsLibraryMappings.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/prettier.xml +0 -7
- package/.idea/reason.xml +0 -6
- package/.idea/vcs.xml +0 -6
- package/.prettierrc.json +0 -13
- package/app/client-table.tsx +0 -35
- package/app/favicon.ico +0 -0
- package/app/layout.tsx +0 -39
- package/app/page.tsx +0 -72
- package/docs/README.md +0 -36
- package/docs/next.config.mjs +0 -4
- package/docs/package.json +0 -28
- package/docs/pnpm-lock.yaml +0 -1030
- package/docs/postcss.config.mjs +0 -8
- package/docs/public/next.svg +0 -1
- package/docs/public/vercel.svg +0 -1
- package/docs/src/app/favicon.ico +0 -0
- package/docs/src/app/globals.css +0 -33
- package/docs/src/app/layout.tsx +0 -22
- package/docs/src/app/page.tsx +0 -10
- package/docs/tailwind.config.ts +0 -15
- package/docs/tsconfig.json +0 -26
- package/next-env.d.ts +0 -5
- package/next.config.mjs +0 -4
- package/postcss.config.mjs +0 -8
- package/public/next.svg +0 -1
- package/public/vercel.svg +0 -1
- package/src/components/core/button.tsx +0 -91
- package/src/components/core/polymorph.tsx +0 -17
- package/src/components/display/card.tsx +0 -8
- package/src/components/floating/dropdown.tsx +0 -93
- package/src/components/floating/tooltip.tsx +0 -67
- package/src/components/form/autocomplete.tsx +0 -222
- package/src/components/form/file-upload.tsx +0 -129
- package/src/components/form/form.tsx +0 -28
- package/src/components/form/input-field.tsx +0 -105
- package/src/components/form/input.tsx +0 -73
- package/src/components/form/select.tsx +0 -58
- package/src/components/form/switch.tsx +0 -40
- package/src/components/index.ts +0 -14
- package/src/components/table/filter.tsx +0 -186
- package/src/components/table/group.tsx +0 -123
- package/src/components/table/index.tsx +0 -207
- package/src/components/table/metadata.tsx +0 -55
- package/src/components/table/sort.tsx +0 -141
- package/src/components/table/table-lib.ts +0 -130
- package/src/components/table/thead.tsx +0 -108
- package/src/hooks/use-form.ts +0 -155
- package/src/hooks/use-previous.ts +0 -9
- package/src/hooks/use-reactive.ts +0 -10
- package/src/index.css +0 -37
- package/src/index.ts +0 -6
- package/src/lib/dom.ts +0 -27
- package/src/lib/fns.ts +0 -23
- package/src/styles/dark.json +0 -66
- package/src/styles/design-tokens.ts +0 -57
- package/src/styles/light.json +0 -49
- package/src/types.ts +0 -11
- package/styles.config.ts +0 -42
- package/tailwind.config.ts +0 -11
- package/tsconfig.json +0 -55
- package/tsconfig.lib.json +0 -50
- package/tsconfig.lib.tsbuildinfo +0 -1
- package/tsconfig.tailwind.json +0 -32
- package/tsconfig.tsbuildinfo +0 -1
- package/vite.config.mts +0 -39
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { LayoutGroup, Reorder, useDragControls, useMotionValue } from "framer-motion";
|
|
3
|
-
import Linq from "linq-arrays";
|
|
4
|
-
import { GripVerticalIcon, GroupIcon, Trash2Icon } from "lucide-react";
|
|
5
|
-
import React, { Fragment, useState } from "react";
|
|
6
|
-
import { keys } from "sidekicker";
|
|
7
|
-
import { Button } from "~/components/core/button";
|
|
8
|
-
import { Dropdown } from "~/components/floating/dropdown";
|
|
9
|
-
import { Select } from "~/components/form/select";
|
|
10
|
-
import { uuid } from "~/lib/fns";
|
|
11
|
-
import { Col, createOptionCols, TableConfiguration } from "./table-lib";
|
|
12
|
-
|
|
13
|
-
export type GroupItem<T extends {}> = Col<T> & {
|
|
14
|
-
rows: T[];
|
|
15
|
-
index: number;
|
|
16
|
-
groupId: string;
|
|
17
|
-
groupName: string;
|
|
18
|
-
groupKey: keyof T;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
type Props<T extends {}> = TableConfiguration<
|
|
22
|
-
T,
|
|
23
|
-
{
|
|
24
|
-
rows: T[];
|
|
25
|
-
groups: GroupItem<T>[];
|
|
26
|
-
setGroups: React.Dispatch<React.SetStateAction<GroupItem<T>[]>>;
|
|
27
|
-
}
|
|
28
|
-
>;
|
|
29
|
-
|
|
30
|
-
const Item = ({ item, onPointerDown }: { item: GroupItem<any>; onPointerDown: any }) => {
|
|
31
|
-
const y = useMotionValue(0);
|
|
32
|
-
return (
|
|
33
|
-
<Reorder.Item
|
|
34
|
-
onPointerDown={onPointerDown}
|
|
35
|
-
id={item.groupId}
|
|
36
|
-
className="flex flex-row items-center gap-2"
|
|
37
|
-
key={item.groupId}
|
|
38
|
-
value={item}
|
|
39
|
-
style={{ y }}
|
|
40
|
-
>
|
|
41
|
-
<button className="cursor-grab">
|
|
42
|
-
<GripVerticalIcon size={14} />
|
|
43
|
-
</button>
|
|
44
|
-
<span>{item.groupName}</span>
|
|
45
|
-
</Reorder.Item>
|
|
46
|
-
);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export const Group = <T extends {}>(props: Props<T>) => {
|
|
50
|
-
const options = createOptionCols(props.cols);
|
|
51
|
-
const controls = useDragControls();
|
|
52
|
-
const [group, setGroup] = useState((props.groups[0]?.thead as string) || "");
|
|
53
|
-
|
|
54
|
-
const onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
55
|
-
const select = e.target;
|
|
56
|
-
const key = select.value as keyof T;
|
|
57
|
-
const index = select.options.selectedIndex;
|
|
58
|
-
const label = select.options.item(index)?.label || "";
|
|
59
|
-
setGroup(label);
|
|
60
|
-
const groupBy = new Linq(props.rows).GroupBy(key);
|
|
61
|
-
const col = props.cols.find((x) => x.id === key)!;
|
|
62
|
-
props.setGroups(
|
|
63
|
-
keys(groupBy).map((groupName, index): GroupItem<T> => {
|
|
64
|
-
const rows = groupBy[groupName];
|
|
65
|
-
return { ...col, groupId: uuid(), groupKey: key, index, rows, groupName: groupName as string };
|
|
66
|
-
})
|
|
67
|
-
);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const onDelete = (e: React.MouseEvent<HTMLButtonElement>) => props.setGroups([]);
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<Fragment>
|
|
74
|
-
<Dropdown
|
|
75
|
-
arrow={false}
|
|
76
|
-
title="Groups"
|
|
77
|
-
trigger={
|
|
78
|
-
<span className="flex items-center gap-1 proportional-nums">
|
|
79
|
-
<GroupIcon size={14} />
|
|
80
|
-
Groups{props.groups.length > 0 ? ` - ${group}(${props.groups.length})` : ""}
|
|
81
|
-
</span>
|
|
82
|
-
}
|
|
83
|
-
>
|
|
84
|
-
<div className="flex flex-nowrap items-center">
|
|
85
|
-
<Select value={group} title="Tipo de agrupamento" onChange={onChange} options={options} placeholder="Agrupar por..." />
|
|
86
|
-
<Button className="mt-4" onClick={onDelete} theme="raw" data-id={group}>
|
|
87
|
-
<Trash2Icon size={16} className="text-danger" />
|
|
88
|
-
</Button>
|
|
89
|
-
</div>
|
|
90
|
-
{props.groups.length > 0 ? (
|
|
91
|
-
<section className="my-4">
|
|
92
|
-
<header>
|
|
93
|
-
<h2 className="text-xl font-medium">Ordenar grupos</h2>
|
|
94
|
-
</header>
|
|
95
|
-
<LayoutGroup>
|
|
96
|
-
<Reorder.Group
|
|
97
|
-
axis="y"
|
|
98
|
-
className="relative space-y-2"
|
|
99
|
-
dragListener={false}
|
|
100
|
-
dragControls={controls}
|
|
101
|
-
drag
|
|
102
|
-
layoutScroll
|
|
103
|
-
onReorder={props.setGroups}
|
|
104
|
-
values={props.groups}
|
|
105
|
-
>
|
|
106
|
-
{props.groups.map((item) => (
|
|
107
|
-
<Item
|
|
108
|
-
key={item.groupId}
|
|
109
|
-
item={item as any}
|
|
110
|
-
onPointerDown={(e: any) => {
|
|
111
|
-
controls.start(e);
|
|
112
|
-
props.setGroups([...props.groups]);
|
|
113
|
-
}}
|
|
114
|
-
/>
|
|
115
|
-
))}
|
|
116
|
-
</Reorder.Group>
|
|
117
|
-
</LayoutGroup>
|
|
118
|
-
</section>
|
|
119
|
-
) : null}
|
|
120
|
-
</Dropdown>
|
|
121
|
-
</Fragment>
|
|
122
|
-
);
|
|
123
|
-
};
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { AnimatePresence, motion } from "framer-motion";
|
|
2
|
-
import Linq from "linq-arrays";
|
|
3
|
-
import React, { Fragment, HTMLAttributes, useMemo } from "react";
|
|
4
|
-
import { TableBodyProps, TableVirtuoso } from "react-virtuoso";
|
|
5
|
-
import { Is } from "sidekicker";
|
|
6
|
-
import { useReducer } from "use-typed-reducer";
|
|
7
|
-
import { OptionProps } from "~/components/form/select";
|
|
8
|
-
import { path } from "~/lib/fns";
|
|
9
|
-
import { FilterConfig } from "./filter";
|
|
10
|
-
import { GroupItem } from "./group";
|
|
11
|
-
import { Metadata } from "./metadata";
|
|
12
|
-
import { multiSort, Sorter } from "./sort";
|
|
13
|
-
import { CellPropsElement, Col, ColMatrix, createOptionCols, TableOperationProps } from "./table-lib";
|
|
14
|
-
import { TableHeader } from "./thead";
|
|
15
|
-
|
|
16
|
-
type InnerTableProps<T extends {}> = HTMLAttributes<HTMLTableElement> &
|
|
17
|
-
TableOperationProps<T> & {
|
|
18
|
-
loading?: boolean;
|
|
19
|
-
group?: GroupItem<T>;
|
|
20
|
-
groups?: GroupItem<T>[];
|
|
21
|
-
optionCols: OptionProps[];
|
|
22
|
-
index: number;
|
|
23
|
-
rows: T[];
|
|
24
|
-
cols: Col<T>[];
|
|
25
|
-
sorters?: Sorter<T>[];
|
|
26
|
-
showMetadata?: boolean;
|
|
27
|
-
filters?: FilterConfig<T>[];
|
|
28
|
-
setGroups: React.Dispatch<React.SetStateAction<GroupItem<T>[]>>;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const TableBody = React.forwardRef((props: TableBodyProps, ref: any) => (
|
|
32
|
-
<tbody {...props} className={`divide-y divide-table-row ${props.className ?? ""}`} ref={ref}>
|
|
33
|
-
<AnimatePresence>{props.children}</AnimatePresence>
|
|
34
|
-
</tbody>
|
|
35
|
-
));
|
|
36
|
-
|
|
37
|
-
const VirtualTable = React.forwardRef((props: any, ref) => (
|
|
38
|
-
<table {...props} ref={ref as any} className={`table min-w-full divide-y divide-table-border table-auto text-left ${props.className ?? ""}`} />
|
|
39
|
-
));
|
|
40
|
-
|
|
41
|
-
const Thead = React.forwardRef((props: any, ref: any) => <thead {...props} className="bg-content-bg shadow-xs group:sticky top-0" ref={ref} />);
|
|
42
|
-
|
|
43
|
-
const TRow = React.forwardRef((props: any, ref: any) => <tr {...props} ref={ref} className={`table-row ${props.className ?? ""}`} />);
|
|
44
|
-
|
|
45
|
-
const components = {
|
|
46
|
-
TableHead: Thead as any,
|
|
47
|
-
Table: VirtualTable as any,
|
|
48
|
-
TableBody: TableBody as any,
|
|
49
|
-
TableRow: TRow as any,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const loadingArray = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
53
|
-
|
|
54
|
-
const InnerTable = <T extends {}>({ filters, setCols, setFilters, sorters, cols, setSorters, ...props }: InnerTableProps<T>) => {
|
|
55
|
-
const rows = useMemo(() => {
|
|
56
|
-
if (props.loading) return loadingArray as any as T[];
|
|
57
|
-
const linq = new Linq(props.rows);
|
|
58
|
-
if (filters.length > 0) {
|
|
59
|
-
filters.forEach((x) => (x.value === "" || Number.isNaN(x.value) ? undefined : linq.Where(x.name, x.operation.symbol, x.value)));
|
|
60
|
-
}
|
|
61
|
-
if (sorters.length === 0) return linq.Select();
|
|
62
|
-
return multiSort(linq.Select(), sorters);
|
|
63
|
-
}, [props.rows, filters, sorters, props.loading]);
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<div className="group border border-table-border rounded-lg px-1 min-w-full">
|
|
67
|
-
<TableVirtuoso
|
|
68
|
-
data={rows}
|
|
69
|
-
totalCount={rows.length}
|
|
70
|
-
useWindowScroll
|
|
71
|
-
components={components}
|
|
72
|
-
fixedHeaderContent={() => (
|
|
73
|
-
<TableHeader<T>
|
|
74
|
-
sorters={sorters}
|
|
75
|
-
setSorters={setSorters}
|
|
76
|
-
filters={filters}
|
|
77
|
-
setFilters={setFilters}
|
|
78
|
-
headers={cols}
|
|
79
|
-
setCols={setCols}
|
|
80
|
-
/>
|
|
81
|
-
)}
|
|
82
|
-
itemContent={(index, row) =>
|
|
83
|
-
cols.map((col, colIndex) => {
|
|
84
|
-
const matrix: ColMatrix = `${colIndex},${index}`;
|
|
85
|
-
const value: any = path(row, col.id as any);
|
|
86
|
-
const Component: React.FC<CellPropsElement<T, any>> = col.Element as any;
|
|
87
|
-
return (
|
|
88
|
-
<td
|
|
89
|
-
{...col.cellProps}
|
|
90
|
-
data-matrix={matrix}
|
|
91
|
-
key={`accessor-${index}-${colIndex}`}
|
|
92
|
-
className="px-2 h-14 border-none first:table-cell hidden md:table-cell"
|
|
93
|
-
>
|
|
94
|
-
{props.loading ? (
|
|
95
|
-
<div className="animate-pulse h-2 bg-table-border rounded" />
|
|
96
|
-
) : Component ? (
|
|
97
|
-
<Component row={row} matrix={matrix} col={col} rowIndex={index} value={value} />
|
|
98
|
-
) : (
|
|
99
|
-
<Fragment>{Is.nil(value) ? "" : value}</Fragment>
|
|
100
|
-
)}
|
|
101
|
-
</td>
|
|
102
|
-
);
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
/>
|
|
106
|
-
</div>
|
|
107
|
-
);
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
export type TableProps<T extends {}> = Pick<InnerTableProps<T>, "cols" | "rows"> & {
|
|
111
|
-
name: string;
|
|
112
|
-
} & Partial<TableOperationProps<T> & { reference: keyof T; loading: boolean }>;
|
|
113
|
-
|
|
114
|
-
const dispatcherFun = <Prev extends any, T extends Prev | ((prev: Prev) => Prev)>(prev: Prev, setter: T) =>
|
|
115
|
-
typeof setter === "function" ? setter(prev) : setter;
|
|
116
|
-
|
|
117
|
-
type DispatcherFun<T extends any> = T | ((prev: T) => T);
|
|
118
|
-
|
|
119
|
-
export const Table = <T extends {}>(props: TableProps<T>) => {
|
|
120
|
-
const optionCols = useMemo(() => createOptionCols(props.cols), [props.cols]);
|
|
121
|
-
const [state, dispatch] = useReducer(
|
|
122
|
-
{
|
|
123
|
-
cols: props.cols as Col<T>[],
|
|
124
|
-
filters: (props.filters ?? []) as FilterConfig<T>[],
|
|
125
|
-
groups: (props.groups ?? []) as GroupItem<T>[],
|
|
126
|
-
sorters: (props.sorters ?? []) as Sorter<T>[],
|
|
127
|
-
},
|
|
128
|
-
(get) => {
|
|
129
|
-
const create =
|
|
130
|
-
<T extends any>(key: string) =>
|
|
131
|
-
(arg: T) => {
|
|
132
|
-
const state = get.state();
|
|
133
|
-
return { ...state, [key]: dispatcherFun(state[key as keyof typeof state], arg as any) };
|
|
134
|
-
};
|
|
135
|
-
return {
|
|
136
|
-
cols: create<DispatcherFun<Col<T>[]>>("cols"),
|
|
137
|
-
filters: create<DispatcherFun<FilterConfig<T>[]>>("filters"),
|
|
138
|
-
groups: create<DispatcherFun<GroupItem<T>[]>>("groups"),
|
|
139
|
-
sorters: create<DispatcherFun<Sorter<T>[]>>("sorters"),
|
|
140
|
-
};
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
interceptor: [
|
|
144
|
-
(state) => {
|
|
145
|
-
props.set?.(state as any);
|
|
146
|
-
return state;
|
|
147
|
-
},
|
|
148
|
-
],
|
|
149
|
-
}
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
return (
|
|
153
|
-
<div className="relative min-w-full">
|
|
154
|
-
<Metadata
|
|
155
|
-
setCols={dispatch.cols}
|
|
156
|
-
rows={props.rows}
|
|
157
|
-
cols={state.cols}
|
|
158
|
-
filters={state.filters}
|
|
159
|
-
groups={state.groups}
|
|
160
|
-
options={optionCols}
|
|
161
|
-
setFilters={dispatch.filters}
|
|
162
|
-
setGroups={dispatch.groups}
|
|
163
|
-
setSorters={dispatch.sorters}
|
|
164
|
-
sorters={state.sorters}
|
|
165
|
-
/>
|
|
166
|
-
{state.groups.length === 0 ? (
|
|
167
|
-
<InnerTable
|
|
168
|
-
{...props}
|
|
169
|
-
cols={state.cols}
|
|
170
|
-
filters={state.filters}
|
|
171
|
-
groups={state.groups}
|
|
172
|
-
index={0}
|
|
173
|
-
optionCols={optionCols}
|
|
174
|
-
options={optionCols}
|
|
175
|
-
setCols={dispatch.cols}
|
|
176
|
-
setFilters={dispatch.filters}
|
|
177
|
-
setGroups={dispatch.groups}
|
|
178
|
-
setSorters={dispatch.sorters}
|
|
179
|
-
sorters={state.sorters}
|
|
180
|
-
/>
|
|
181
|
-
) : (
|
|
182
|
-
<div className="flex flex-wrap gap-4">
|
|
183
|
-
{state.groups.map((group, index) => (
|
|
184
|
-
<motion.div className="min-w-full" key={`group-${group.groupId}`}>
|
|
185
|
-
<InnerTable
|
|
186
|
-
{...props}
|
|
187
|
-
cols={state.cols}
|
|
188
|
-
filters={state.filters}
|
|
189
|
-
group={group}
|
|
190
|
-
groups={state.groups}
|
|
191
|
-
index={index}
|
|
192
|
-
optionCols={optionCols}
|
|
193
|
-
options={optionCols}
|
|
194
|
-
rows={group.rows}
|
|
195
|
-
setCols={dispatch.cols}
|
|
196
|
-
setFilters={dispatch.filters}
|
|
197
|
-
setGroups={dispatch.groups}
|
|
198
|
-
setSorters={dispatch.sorters}
|
|
199
|
-
sorters={state.sorters}
|
|
200
|
-
/>
|
|
201
|
-
</motion.div>
|
|
202
|
-
))}
|
|
203
|
-
</div>
|
|
204
|
-
)}
|
|
205
|
-
</div>
|
|
206
|
-
);
|
|
207
|
-
};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { Filter } from "./filter";
|
|
2
|
-
import { Group } from "./group";
|
|
3
|
-
import { Sort } from "./sort";
|
|
4
|
-
import { TableOperationProps, valueFromType } from "./table-lib";
|
|
5
|
-
|
|
6
|
-
export const Metadata = <T extends {}>(props: TableOperationProps<T>) => (
|
|
7
|
-
<header className="min-w-full mb-1">
|
|
8
|
-
<div className="flex flex-wrap min-w-full items-center justify-between gap-x-4 gap-y-1">
|
|
9
|
-
<div className="flex w-fit items-centeend gap-4 whitespace-nowrap py-2">
|
|
10
|
-
<p>
|
|
11
|
-
<Filter cols={props.cols} options={props.options} filters={props.filters} set={props.setFilters} />
|
|
12
|
-
</p>
|
|
13
|
-
<p>
|
|
14
|
-
<Sort options={props.options} cols={props.cols} sorters={props.sorters} set={props.setSorters} />
|
|
15
|
-
</p>
|
|
16
|
-
<p>
|
|
17
|
-
<Group rows={props.rows} groups={props.groups} setGroups={props.setGroups} options={props.options} cols={props.cols} />
|
|
18
|
-
</p>
|
|
19
|
-
</div>
|
|
20
|
-
<ul className="flex flex-wrap w-full flex-1 flex-grow flex-row items-center md:justify-end gap-4">
|
|
21
|
-
{props.filters.map((x) => (
|
|
22
|
-
<li key={`filter-table-${x.id}`} className="flex gap-1 items-center rounded-xl border border-card-border px-4 py-0.5">
|
|
23
|
-
<span>
|
|
24
|
-
<span className="size-3 mr-2 aspect-square bg-primary inline-block rounded-full" aria-hidden="true" />
|
|
25
|
-
{x.label} {x.operation.label.toLowerCase()}:
|
|
26
|
-
</span>
|
|
27
|
-
<div className="relative w-min min-w-[1ch]">
|
|
28
|
-
<span aria-hidden="true" className="invisible whitespace-pre p-0">
|
|
29
|
-
{x.value || " "}
|
|
30
|
-
</span>
|
|
31
|
-
<input
|
|
32
|
-
type={x.type}
|
|
33
|
-
value={x.value as string}
|
|
34
|
-
className="absolute left-0 top-0 m-0 inline-block w-full bg-transparent p-0 placeholder-primary/70 outline-none [appearance:textfield] after:empty:text-primary/70 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
|
35
|
-
onChange={(e) => {
|
|
36
|
-
const value = valueFromType(e.target);
|
|
37
|
-
props.setFilters((prev) =>
|
|
38
|
-
prev.map((item) =>
|
|
39
|
-
x.id === item.id
|
|
40
|
-
? {
|
|
41
|
-
...item,
|
|
42
|
-
value,
|
|
43
|
-
}
|
|
44
|
-
: item
|
|
45
|
-
)
|
|
46
|
-
);
|
|
47
|
-
}}
|
|
48
|
-
/>
|
|
49
|
-
</div>
|
|
50
|
-
</li>
|
|
51
|
-
))}
|
|
52
|
-
</ul>
|
|
53
|
-
</div>
|
|
54
|
-
</header>
|
|
55
|
-
);
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { ChevronDownIcon, ChevronUpIcon, PlusIcon, SortAscIcon, Trash2Icon } from "lucide-react";
|
|
3
|
-
import React, { Fragment, useEffect, useState } from "react";
|
|
4
|
-
import { Dropdown } from "~/components/floating/dropdown";
|
|
5
|
-
import { OptionProps, Select } from "~/components/form/select";
|
|
6
|
-
import { uuid } from "~/lib/fns";
|
|
7
|
-
import { Label } from "~/types";
|
|
8
|
-
import { Col, TableConfiguration, TableOperationProps } from "./table-lib";
|
|
9
|
-
|
|
10
|
-
type Keyof<T extends {}> = keyof T extends infer R extends string ? R : never;
|
|
11
|
-
|
|
12
|
-
enum Order {
|
|
13
|
-
Asc = "asc",
|
|
14
|
-
Desc = "desc",
|
|
15
|
-
Undefined = "undefined",
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type Sorter<T extends {}> = { value: Keyof<T>; type: Order; label: Label; id: string };
|
|
19
|
-
|
|
20
|
-
const createSorterFn =
|
|
21
|
-
<T extends {}>(fields: Sorter<T>[]) =>
|
|
22
|
-
(a: any, b: any) =>
|
|
23
|
-
fields.reduce<number>((acc, x) => {
|
|
24
|
-
const reverse = x.type === "desc" ? -1 : 1;
|
|
25
|
-
const property = x.value;
|
|
26
|
-
const p = a[property] > b[property] ? reverse : a[property] < b[property] ? -reverse : 0;
|
|
27
|
-
return acc !== 0 ? acc : p;
|
|
28
|
-
}, 0);
|
|
29
|
-
|
|
30
|
-
export const multiSort = <T extends {}>(array: T[], fields: Sorter<T>[]) => array.sort(createSorterFn(fields));
|
|
31
|
-
|
|
32
|
-
const orders = {
|
|
33
|
-
asc: { label: "Ascending", value: Order.Asc },
|
|
34
|
-
desc: { label: "Descending", value: Order.Desc },
|
|
35
|
-
} satisfies Omit<Record<Order, OptionProps>, Order.Undefined>;
|
|
36
|
-
|
|
37
|
-
const orderOptions: OptionProps[] = [orders.asc, orders.desc];
|
|
38
|
-
|
|
39
|
-
type Props<T extends {}> = TableConfiguration<
|
|
40
|
-
T,
|
|
41
|
-
{
|
|
42
|
-
cols: Col<T>[];
|
|
43
|
-
sorters: Sorter<T>[];
|
|
44
|
-
set: React.Dispatch<React.SetStateAction<Sorter<T>[]>>;
|
|
45
|
-
}
|
|
46
|
-
>;
|
|
47
|
-
|
|
48
|
-
const createSorter = <T extends {}>(col: Col<T>, order: Order = Order.Asc): Sorter<T> => ({
|
|
49
|
-
id: uuid(),
|
|
50
|
-
type: order,
|
|
51
|
-
value: col.id as any,
|
|
52
|
-
label: orders[Order.Asc].label,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
export const Sort = <T extends {}>(props: Props<T>) => {
|
|
56
|
-
const onAddSorter = () => {
|
|
57
|
-
const col = props.cols[0];
|
|
58
|
-
if (col) props.set((prev) => [...prev, createSorter(col)]);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const onSetSorter = (id: string) => (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
62
|
-
const value = e.target.value;
|
|
63
|
-
props.set((prev) => prev.map((x) => (x.id === id ? { ...x, value: value as Keyof<T> } : x)));
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const onSortOrderType = (id: string) => (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
67
|
-
const type = e.target.value;
|
|
68
|
-
props.set((prev) => prev.map((x) => (x.id === id ? { ...x, type: type as Order } : x)));
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const onDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
72
|
-
const id = e.currentTarget.dataset.id || "";
|
|
73
|
-
props.set((prev) => prev.filter((x) => x.id !== id));
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<Fragment>
|
|
78
|
-
<Dropdown
|
|
79
|
-
arrow={false}
|
|
80
|
-
title="Order By"
|
|
81
|
-
trigger={
|
|
82
|
-
<span className="flex items-center gap-1 proportional-nums text-foreground-description">
|
|
83
|
-
<SortAscIcon size={14} />
|
|
84
|
-
Order by {props.sorters.length === 0 ? "" : ` (${props.sorters.length})`}
|
|
85
|
-
</span>
|
|
86
|
-
}
|
|
87
|
-
>
|
|
88
|
-
<ul className="mt-4 space-y-2">
|
|
89
|
-
{props.sorters.map((sorter) => {
|
|
90
|
-
return (
|
|
91
|
-
<li key={`sorter-select-${sorter.id}`} className="flex flex-nowrap gap-3">
|
|
92
|
-
<Select
|
|
93
|
-
onChange={onSetSorter(sorter.id)}
|
|
94
|
-
options={props.options}
|
|
95
|
-
placeholder="Selecione um campo..."
|
|
96
|
-
value={sorter.value as string}
|
|
97
|
-
/>
|
|
98
|
-
<Select onChange={onSortOrderType(sorter.id)} value={sorter.type} options={orderOptions} placeholder="Operação..." />
|
|
99
|
-
<button className="mt-4" data-id={sorter.id} onClick={onDelete}>
|
|
100
|
-
<Trash2Icon className="text-danger" size={14} />
|
|
101
|
-
</button>
|
|
102
|
-
</li>
|
|
103
|
-
);
|
|
104
|
-
})}
|
|
105
|
-
<li>
|
|
106
|
-
<button onClick={onAddSorter} className="text-primary flex items-center gap-1">
|
|
107
|
-
<PlusIcon size={14} /> Adicionar ordenação
|
|
108
|
-
</button>
|
|
109
|
-
</li>
|
|
110
|
-
</ul>
|
|
111
|
-
</Dropdown>
|
|
112
|
-
</Fragment>
|
|
113
|
-
);
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
type SorterHeadProps<T extends {}> = Pick<TableOperationProps<T>, "sorters" | "setSorters"> & { col: Col<T> };
|
|
117
|
-
|
|
118
|
-
export const SorterHead = <T extends {}>(props: SorterHeadProps<T>) => {
|
|
119
|
-
const sorter = props.sorters.find((sort) => sort.id === props.col.id);
|
|
120
|
-
const [status, setStatus] = useState(sorter ? sorter.type : Order.Undefined);
|
|
121
|
-
|
|
122
|
-
const onClick = () => setStatus((prev) => (prev === Order.Undefined ? Order.Asc : prev === Order.Asc ? Order.Desc : Order.Undefined));
|
|
123
|
-
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
props.setSorters((prev) => {
|
|
126
|
-
if (status === Order.Undefined) return prev.filter((x) => (x.value as string) !== props.col.id);
|
|
127
|
-
const findIndex = prev.findIndex((p) => (p.value as string) === props.col.id);
|
|
128
|
-
if (findIndex === -1) return [...prev, createSorter(props.col, status)];
|
|
129
|
-
prev[findIndex] = createSorter(props.col, status);
|
|
130
|
-
return [...prev];
|
|
131
|
-
});
|
|
132
|
-
}, [status, props.col]);
|
|
133
|
-
|
|
134
|
-
return (
|
|
135
|
-
<button className="isolate flex items-center" onClick={onClick}>
|
|
136
|
-
{status === Order.Asc ? <ChevronDownIcon size={14} /> : null}
|
|
137
|
-
{status === Order.Desc ? <ChevronUpIcon size={14} /> : null}
|
|
138
|
-
{status === Order.Undefined ? <SortAscIcon size={14} /> : null}
|
|
139
|
-
</button>
|
|
140
|
-
);
|
|
141
|
-
};
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import React from "react";
|
|
3
|
-
import { LocalStorage } from "storage-manager-js";
|
|
4
|
-
import { useReducer } from "use-typed-reducer";
|
|
5
|
-
import { OptionProps } from "~/components/form/select";
|
|
6
|
-
import { isSsr } from "~/lib/fns";
|
|
7
|
-
import { POJO, SetState } from "~/types";
|
|
8
|
-
import { FilterConfig } from "./filter";
|
|
9
|
-
import { GroupItem } from "./group";
|
|
10
|
-
import { Sorter } from "./sort";
|
|
11
|
-
|
|
12
|
-
export const getLabel = <T extends {}>(col: Col<T>) => col.headerLabel ?? col.thead ?? (col.id as string);
|
|
13
|
-
|
|
14
|
-
export type TableConfiguration<T extends {}, M extends {} = {}> = M & {
|
|
15
|
-
cols: Col<T>[];
|
|
16
|
-
options: OptionProps[];
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export const createOptionCols = <T extends {}>(cols: Col<T>[]): OptionProps[] =>
|
|
20
|
-
cols.map((opt) => ({
|
|
21
|
-
value: opt.id as string,
|
|
22
|
-
label: (opt.thead ?? opt.headerLabel ?? (opt.id as string)) as string,
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
export enum ColType {
|
|
26
|
-
Boolean = "boolean",
|
|
27
|
-
Number = "number",
|
|
28
|
-
Select = "select",
|
|
29
|
-
Text = "text",
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const valueFromType = (input: HTMLInputElement) => (input.type === "number" ? input.valueAsNumber : input.value);
|
|
33
|
-
|
|
34
|
-
type THead = React.ReactElement | React.ReactNode;
|
|
35
|
-
|
|
36
|
-
export type ColMatrix = `${number},${number}`;
|
|
37
|
-
|
|
38
|
-
export type CellPropsElement<T extends {}, K extends keyof T> = {
|
|
39
|
-
row: T;
|
|
40
|
-
value: T[K];
|
|
41
|
-
rowIndex: number;
|
|
42
|
-
matrix: ColMatrix;
|
|
43
|
-
col: ColOptions<T, K> & { id: K; thead: THead };
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
type ColOptions<T extends {}, K extends keyof T> = Partial<{
|
|
47
|
-
cellProps: React.HTMLAttributes<HTMLTableCellElement>;
|
|
48
|
-
thProps: React.HTMLAttributes<HTMLTableCellElement>;
|
|
49
|
-
type: ColType;
|
|
50
|
-
headerLabel: string;
|
|
51
|
-
allowFilter: boolean;
|
|
52
|
-
Element: (props: CellPropsElement<T, K>) => React.ReactNode;
|
|
53
|
-
}>;
|
|
54
|
-
|
|
55
|
-
export type ColConstructor<T extends {}> = {
|
|
56
|
-
remove: <K extends keyof T>(id: K) => void;
|
|
57
|
-
add: <K extends keyof T>(id: K, thead: THead, props?: ColOptions<T, K>) => void;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const cols =
|
|
61
|
-
<T extends POJO>() =>
|
|
62
|
-
<K extends keyof T>(id: K, thead: THead, options: ColOptions<T, K>) => ({ ...options, id, thead });
|
|
63
|
-
|
|
64
|
-
export type Col<T extends {}> = ReturnType<ReturnType<typeof cols<T>>>;
|
|
65
|
-
|
|
66
|
-
type TableGetters<T extends POJO> = {
|
|
67
|
-
rows: T[];
|
|
68
|
-
cols: Col<T>[];
|
|
69
|
-
groups: GroupItem<T>[];
|
|
70
|
-
sorters: Sorter<T>[];
|
|
71
|
-
filters: FilterConfig<T>[];
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
type TableSetters<T extends POJO> = {
|
|
75
|
-
setCols: SetState<Col<T>[]>;
|
|
76
|
-
setSorters: SetState<Sorter<T>[]>;
|
|
77
|
-
setGroups: SetState<GroupItem<T>[]>;
|
|
78
|
-
setFilters: SetState<FilterConfig<T>[]>;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export type TableOperationProps<T extends {}> = TableConfiguration<
|
|
82
|
-
T,
|
|
83
|
-
TableSetters<T> &
|
|
84
|
-
TableGetters<T> & {
|
|
85
|
-
set?: (v: TableGetters<T>) => void;
|
|
86
|
-
}
|
|
87
|
-
>;
|
|
88
|
-
|
|
89
|
-
export const createColumns = <T extends {}>(callback: (o: ColConstructor<T>) => void) => {
|
|
90
|
-
let items: Col<T>[] = [];
|
|
91
|
-
const add: ColConstructor<T>["add"] = (id, thead, options) => items.push({ ...options, id, thead } as any);
|
|
92
|
-
const remove: ColConstructor<T>["remove"] = (id) => (items = items.filter((x) => x.id !== id));
|
|
93
|
-
callback({ add, remove });
|
|
94
|
-
return items;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
type TablePreferenceState<T extends POJO> = {
|
|
98
|
-
name: string;
|
|
99
|
-
cols: Col<T>[];
|
|
100
|
-
groups: GroupItem<T>[];
|
|
101
|
-
sorters: Sorter<T>[];
|
|
102
|
-
filters: FilterConfig<T>[];
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const noop = {};
|
|
106
|
-
|
|
107
|
-
export const useTablePreferences = <T extends POJO>(name: string, options: Partial<TableGetters<T>> = noop) => {
|
|
108
|
-
const init: TableGetters<T> | null = isSsr() ? null : (LocalStorage.get(`@unamed/table-${name}`) as TableGetters<T>) || null;
|
|
109
|
-
const [state, dispatch] = useReducer(
|
|
110
|
-
{
|
|
111
|
-
name,
|
|
112
|
-
groups: options.groups || init?.groups || [],
|
|
113
|
-
sorters: options.sorters || init?.sorters || [],
|
|
114
|
-
filters: options.filters || init?.filters || [],
|
|
115
|
-
cols: options.cols || init?.cols || [],
|
|
116
|
-
} as Omit<TableGetters<T>, "rows"> & { name: string },
|
|
117
|
-
(get) => {
|
|
118
|
-
const intercept = (partial: Partial<TablePreferenceState<T>>) => {
|
|
119
|
-
const prev = get.state();
|
|
120
|
-
const result = { ...prev, ...partial };
|
|
121
|
-
if (!isSsr()) LocalStorage.set(`@unamed/table-${prev.name}`, result);
|
|
122
|
-
return result;
|
|
123
|
-
};
|
|
124
|
-
return {
|
|
125
|
-
set: (getters: TableGetters<T>) => intercept(getters),
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
);
|
|
129
|
-
return { ...state, ...dispatch, name };
|
|
130
|
-
};
|