@reactberry/system 2.0.0-beta
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/README.md +48 -0
- package/package.json +74 -0
- package/src/blocks/Accordion/index.tsx +158 -0
- package/src/blocks/AnimatedCarousel/index.tsx +188 -0
- package/src/blocks/AppleGlow/index.tsx +144 -0
- package/src/blocks/Avatar/index.tsx +167 -0
- package/src/blocks/Await/index.tsx +45 -0
- package/src/blocks/Cards/AnimatedCard/index.tsx +175 -0
- package/src/blocks/Cards/FluorescentCard/index.tsx +180 -0
- package/src/blocks/Cards/InfoCard/index.tsx +206 -0
- package/src/blocks/Cards/TickerCard/index.tsx +125 -0
- package/src/blocks/Carousel/index.tsx +216 -0
- package/src/blocks/Checkbox/index.tsx +101 -0
- package/src/blocks/Collection/index.tsx +59 -0
- package/src/blocks/Container/index.tsx +55 -0
- package/src/blocks/Controls/Control.tsx +67 -0
- package/src/blocks/Controls/index.tsx +11 -0
- package/src/blocks/CyclingNumber/index.tsx +78 -0
- package/src/blocks/DisplaySet/index.tsx +42 -0
- package/src/blocks/Divider/index.tsx +14 -0
- package/src/blocks/Draggable/index.tsx +266 -0
- package/src/blocks/Drawer/index.tsx +136 -0
- package/src/blocks/DynamicIsland/DynamicIsland.tsx +89 -0
- package/src/blocks/DynamicIsland/index.tsx +2 -0
- package/src/blocks/Fader/index.tsx +145 -0
- package/src/blocks/FamilyDrawer/README.md +116 -0
- package/src/blocks/FamilyDrawer/example.tsx +108 -0
- package/src/blocks/FamilyDrawer/index.tsx +119 -0
- package/src/blocks/FamilyDrawer/views/DefaultView.tsx +93 -0
- package/src/blocks/FamilyDrawer/views/KeyView.tsx +129 -0
- package/src/blocks/FamilyDrawer/views/PhraseView.tsx +129 -0
- package/src/blocks/FamilyDrawer/views/RemoveView.tsx +81 -0
- package/src/blocks/FieldSet/index.tsx +173 -0
- package/src/blocks/Filesystem/index.tsx +198 -0
- package/src/blocks/Gallery/Carousel/index.tsx +257 -0
- package/src/blocks/Gallery/Modal/index.tsx +83 -0
- package/src/blocks/Gallery/index.tsx +57 -0
- package/src/blocks/Gallery/utils/animationVariants.ts +18 -0
- package/src/blocks/Gallery/utils/aspectRatio.ts +14 -0
- package/src/blocks/Gallery/utils/downloadPhoto.ts +24 -0
- package/src/blocks/Gallery/utils/range.ts +11 -0
- package/src/blocks/GradientMesh/index.tsx +106 -0
- package/src/blocks/Group/index.tsx +152 -0
- package/src/blocks/Heading/index.tsx +111 -0
- package/src/blocks/HorizontalScroller/index.tsx +135 -0
- package/src/blocks/Icon/index.tsx +45 -0
- package/src/blocks/Indicator/index.tsx +27 -0
- package/src/blocks/InlineEditor/index.tsx +216 -0
- package/src/blocks/List/index.tsx +657 -0
- package/src/blocks/Main/index.tsx +17 -0
- package/src/blocks/Marquee/index.tsx +116 -0
- package/src/blocks/MaskedField/index.tsx +199 -0
- package/src/blocks/Menu/MenuContent.tsx +246 -0
- package/src/blocks/Menu/MenuContext.tsx +34 -0
- package/src/blocks/Menu/MenuItem.tsx +104 -0
- package/src/blocks/Menu/index.tsx +60 -0
- package/src/blocks/Modal/index.tsx +268 -0
- package/src/blocks/MorphingPopover/index.tsx +294 -0
- package/src/blocks/Overlay/Backdrop.tsx +48 -0
- package/src/blocks/Overlay/OverscrollGuard.tsx +36 -0
- package/src/blocks/Overlay/index.ts +2 -0
- package/src/blocks/Parallax/index.tsx +117 -0
- package/src/blocks/ParallaxSection/index.tsx +61 -0
- package/src/blocks/Placeholder/index.tsx +48 -0
- package/src/blocks/Popover/index.tsx +402 -0
- package/src/blocks/Progress/getProgressColor.ts +61 -0
- package/src/blocks/Progress/index.tsx +179 -0
- package/src/blocks/ProgressiveBlur/index.tsx +75 -0
- package/src/blocks/README.md +15 -0
- package/src/blocks/RenderAsset/index.tsx +18 -0
- package/src/blocks/ScrollContainer/index.tsx +93 -0
- package/src/blocks/ShinyText/index.tsx +72 -0
- package/src/blocks/Skeleton/index.tsx +71 -0
- package/src/blocks/Slider/SliderControls.tsx +119 -0
- package/src/blocks/Slider/index.tsx +140 -0
- package/src/blocks/Slider/useSlider.ts +126 -0
- package/src/blocks/Slideshow/index.tsx +177 -0
- package/src/blocks/Spotlight/index.tsx +144 -0
- package/src/blocks/Steps/StepIndicator.tsx +149 -0
- package/src/blocks/Steps/StepProgress.tsx +164 -0
- package/src/blocks/Steps/Steps.tsx +197 -0
- package/src/blocks/Steps/StepsNav.tsx +30 -0
- package/src/blocks/Steps/StepsTracker.tsx +80 -0
- package/src/blocks/Steps/hooks.ts +71 -0
- package/src/blocks/Steps/index.tsx +16 -0
- package/src/blocks/Steps/types.ts +71 -0
- package/src/blocks/StickySectionStack/index.tsx +136 -0
- package/src/blocks/Switch/index.tsx +85 -0
- package/src/blocks/SystemNotice/index.tsx +81 -0
- package/src/blocks/Table/README.md +251 -0
- package/src/blocks/Table/Table.tsx +207 -0
- package/src/blocks/Table/TablePagination.tsx +189 -0
- package/src/blocks/Table/index.ts +33 -0
- package/src/blocks/Table/useTableControls.ts +331 -0
- package/src/blocks/Tag/index.tsx +27 -0
- package/src/blocks/TextBreak/index.tsx +96 -0
- package/src/blocks/TextReveal/index.tsx +104 -0
- package/src/blocks/Thumbnail/index.tsx +26 -0
- package/src/blocks/Ticker/index.tsx +112 -0
- package/src/blocks/Toast/index.tsx +77 -0
- package/src/blocks/Tooltip/index.tsx +174 -0
- package/src/blocks/Underlay/index.tsx +104 -0
- package/src/blocks/Upload/Dropzone.tsx +92 -0
- package/src/blocks/Upload/UploadBtn.tsx +38 -0
- package/src/blocks/Upload/index.tsx +61 -0
- package/src/blocks/Upload/types.ts +37 -0
- package/src/blocks/VideoMarquee/index.tsx +511 -0
- package/src/blocks/index.ts +119 -0
- package/src/blocks/pagination/Pagination.tsx +148 -0
- package/src/blocks/pagination/PaginationList.tsx +41 -0
- package/src/blocks/pagination/index.ts +2 -0
- package/src/charts/BarChart.tsx +63 -0
- package/src/charts/PieChart.tsx +39 -0
- package/src/charts/index.ts +3 -0
- package/src/charts/utils.ts +103 -0
- package/src/docs/README.md +373 -0
- package/src/docs/reference/README.md +299 -0
- package/src/elements/box.ts +163 -0
- package/src/elements/button.ts +49 -0
- package/src/elements/field.ts +129 -0
- package/src/elements/index.ts +8 -0
- package/src/elements/text.ts +47 -0
- package/src/elements/utils.js +97 -0
- package/src/hooks/use-copy-to-clipboard.tsx +33 -0
- package/src/hooks/use-enter-submit.tsx +23 -0
- package/src/hooks/use-local-storage.ts +42 -0
- package/src/hooks/use-sidebar.tsx +109 -0
- package/src/hooks/useAnimatedText.ts +32 -0
- package/src/hooks/useAutosizeTextArea.ts +45 -0
- package/src/hooks/useBreakpoint.tsx +123 -0
- package/src/hooks/useClickOutside.tsx +38 -0
- package/src/hooks/useHover.tsx +33 -0
- package/src/hooks/useHoverList.tsx +17 -0
- package/src/hooks/useKeyboardShortcuts.ts +91 -0
- package/src/hooks/useKeypress.ts +27 -0
- package/src/hooks/useOverlay.ts +32 -0
- package/src/hooks/useReducedMotion.ts +25 -0
- package/src/hooks/useStandaloneMode.ts +35 -0
- package/src/hooks/useTouchDevice.ts +34 -0
- package/src/icons/index.tsx +129 -0
- package/src/index.ts +12 -0
- package/src/providers/DesignSystemProvider.tsx +35 -0
- package/src/providers/StyledComponentsRegistry.tsx +30 -0
- package/src/providers/index.ts +2 -0
- package/src/themes/README.md +30 -0
- package/src/themes/default/assets/badge-avatar.tsx +45 -0
- package/src/themes/default/assets/logo.tsx +42 -0
- package/src/themes/default/global.ts +138 -0
- package/src/themes/default/modes/dark/config.js +49 -0
- package/src/themes/default/modes/dark/skins.js +631 -0
- package/src/themes/default/modes/dark/theme.js +87 -0
- package/src/themes/default/modes/light/config.js +48 -0
- package/src/themes/default/modes/light/skins.js +1026 -0
- package/src/themes/default/modes/light/theme.js +74 -0
- package/src/themes/default/tokens/controls.js +53 -0
- package/src/themes/default/tokens/shadows.js +63 -0
- package/src/themes/default/tokens/shapes.js +37 -0
- package/src/themes/default/tokens/space.js +143 -0
- package/src/themes/default/tokens/spectre.js +16 -0
- package/src/themes/default/utils.js +523 -0
- package/src/themes/index.ts +11 -0
- package/src/types.ts +394 -0
- package/src/utils/overlayTheme.ts +61 -0
- package/src/utils/pickColor.ts +15 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState, useMemo, useCallback } from "react";
|
|
3
|
+
|
|
4
|
+
export type SortDirection = "asc" | "desc" | null;
|
|
5
|
+
|
|
6
|
+
export interface UseTableControlsOptions<T> {
|
|
7
|
+
/** Initial data array */
|
|
8
|
+
data: T[];
|
|
9
|
+
/** Initial items per page */
|
|
10
|
+
initialItemsPerPage?: number;
|
|
11
|
+
/** Initial sort key */
|
|
12
|
+
initialSortKey?: string | null;
|
|
13
|
+
/** Initial sort direction */
|
|
14
|
+
initialSortDirection?: SortDirection;
|
|
15
|
+
/** Custom sort function */
|
|
16
|
+
customSort?: (data: T[], key: string, direction: "asc" | "desc") => T[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UseTableControlsReturn<T> {
|
|
20
|
+
// Sorted and paginated data
|
|
21
|
+
currentData: T[];
|
|
22
|
+
// Sorting
|
|
23
|
+
sortKey: string | null;
|
|
24
|
+
sortDirection: SortDirection;
|
|
25
|
+
handleSort: (key: string, direction: SortDirection) => void;
|
|
26
|
+
// Pagination
|
|
27
|
+
currentPage: number;
|
|
28
|
+
totalPages: number;
|
|
29
|
+
totalItems: number;
|
|
30
|
+
itemsPerPage: number;
|
|
31
|
+
handlePageChange: (page: number) => void;
|
|
32
|
+
handlePageSizeChange: (pageSize: number) => void;
|
|
33
|
+
// Utilities
|
|
34
|
+
resetPagination: () => void;
|
|
35
|
+
resetSorting: () => void;
|
|
36
|
+
resetAll: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Hook for managing table sorting and pagination
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* const tableControls = useTableControls({
|
|
45
|
+
* data: users,
|
|
46
|
+
* initialItemsPerPage: 10
|
|
47
|
+
* });
|
|
48
|
+
*
|
|
49
|
+
* return (
|
|
50
|
+
* <>
|
|
51
|
+
* <Table>
|
|
52
|
+
* <TableHeader>
|
|
53
|
+
* <TableRow>
|
|
54
|
+
* <SortableTableHeader
|
|
55
|
+
* sortKey="name"
|
|
56
|
+
* currentSortKey={tableControls.sortKey}
|
|
57
|
+
* currentSortDirection={tableControls.sortDirection}
|
|
58
|
+
* onSort={tableControls.handleSort}
|
|
59
|
+
* >
|
|
60
|
+
* Name
|
|
61
|
+
* </SortableTableHeader>
|
|
62
|
+
* </TableRow>
|
|
63
|
+
* </TableHeader>
|
|
64
|
+
* <TableBody>
|
|
65
|
+
* {tableControls.currentData.map(user => (
|
|
66
|
+
* <TableRow key={user.id}>
|
|
67
|
+
* <TableCell>{user.name}</TableCell>
|
|
68
|
+
* </TableRow>
|
|
69
|
+
* ))}
|
|
70
|
+
* </TableBody>
|
|
71
|
+
* </Table>
|
|
72
|
+
* <TablePagination
|
|
73
|
+
* currentPage={tableControls.currentPage}
|
|
74
|
+
* totalPages={tableControls.totalPages}
|
|
75
|
+
* totalItems={tableControls.totalItems}
|
|
76
|
+
* itemsPerPage={tableControls.itemsPerPage}
|
|
77
|
+
* onPageChange={tableControls.handlePageChange}
|
|
78
|
+
* onPageSizeChange={tableControls.handlePageSizeChange}
|
|
79
|
+
* />
|
|
80
|
+
* </>
|
|
81
|
+
* );
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export function useTableControls<T>({
|
|
85
|
+
data,
|
|
86
|
+
initialItemsPerPage = 10,
|
|
87
|
+
initialSortKey = null,
|
|
88
|
+
initialSortDirection = null,
|
|
89
|
+
customSort,
|
|
90
|
+
}: UseTableControlsOptions<T>): UseTableControlsReturn<T> {
|
|
91
|
+
// Sorting state
|
|
92
|
+
const [sortKey, setSortKey] = useState<string | null>(initialSortKey);
|
|
93
|
+
const [sortDirection, setSortDirection] =
|
|
94
|
+
useState<SortDirection>(initialSortDirection);
|
|
95
|
+
|
|
96
|
+
// Pagination state
|
|
97
|
+
const [currentPage, setCurrentPage] = useState(0);
|
|
98
|
+
const [itemsPerPage, setItemsPerPage] = useState(initialItemsPerPage);
|
|
99
|
+
|
|
100
|
+
// Default sort function
|
|
101
|
+
const defaultSort = useCallback(
|
|
102
|
+
(data: T[], key: string, direction: "asc" | "desc"): T[] => {
|
|
103
|
+
return [...data].sort((a, b) => {
|
|
104
|
+
const aValue = (a as any)[key];
|
|
105
|
+
const bValue = (b as any)[key];
|
|
106
|
+
|
|
107
|
+
// Handle null/undefined values
|
|
108
|
+
if (aValue === null || aValue === undefined) return 1;
|
|
109
|
+
if (bValue === null || bValue === undefined) return -1;
|
|
110
|
+
|
|
111
|
+
// Handle different types
|
|
112
|
+
if (typeof aValue === "string" && typeof bValue === "string") {
|
|
113
|
+
const comparison = aValue
|
|
114
|
+
.toLowerCase()
|
|
115
|
+
.localeCompare(bValue.toLowerCase());
|
|
116
|
+
return direction === "asc" ? comparison : -comparison;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (aValue < bValue) return direction === "asc" ? -1 : 1;
|
|
120
|
+
if (aValue > bValue) return direction === "asc" ? 1 : -1;
|
|
121
|
+
return 0;
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
[],
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Sort data
|
|
128
|
+
const sortedData = useMemo(() => {
|
|
129
|
+
if (!sortKey || !sortDirection) return data;
|
|
130
|
+
|
|
131
|
+
const sortFunction = customSort || defaultSort;
|
|
132
|
+
return sortFunction(data, sortKey, sortDirection);
|
|
133
|
+
}, [data, sortKey, sortDirection, customSort, defaultSort]);
|
|
134
|
+
|
|
135
|
+
// Calculate pagination
|
|
136
|
+
const totalItems = sortedData.length;
|
|
137
|
+
const totalPages = Math.ceil(totalItems / itemsPerPage);
|
|
138
|
+
|
|
139
|
+
// Ensure current page is valid
|
|
140
|
+
const validCurrentPage = Math.min(currentPage, Math.max(0, totalPages - 1));
|
|
141
|
+
|
|
142
|
+
// Paginate data
|
|
143
|
+
const currentData = useMemo(() => {
|
|
144
|
+
const start = validCurrentPage * itemsPerPage;
|
|
145
|
+
return sortedData.slice(start, start + itemsPerPage);
|
|
146
|
+
}, [sortedData, validCurrentPage, itemsPerPage]);
|
|
147
|
+
|
|
148
|
+
// Handlers
|
|
149
|
+
const handleSort = useCallback((key: string, direction: SortDirection) => {
|
|
150
|
+
setSortKey(direction ? key : null);
|
|
151
|
+
setSortDirection(direction);
|
|
152
|
+
// Reset to first page when sorting changes
|
|
153
|
+
setCurrentPage(0);
|
|
154
|
+
}, []);
|
|
155
|
+
|
|
156
|
+
const handlePageChange = useCallback((page: number) => {
|
|
157
|
+
setCurrentPage(page);
|
|
158
|
+
}, []);
|
|
159
|
+
|
|
160
|
+
const handlePageSizeChange = useCallback((pageSize: number) => {
|
|
161
|
+
setItemsPerPage(pageSize);
|
|
162
|
+
// Reset to first page when page size changes
|
|
163
|
+
setCurrentPage(0);
|
|
164
|
+
}, []);
|
|
165
|
+
|
|
166
|
+
// Utility functions
|
|
167
|
+
const resetPagination = useCallback(() => {
|
|
168
|
+
setCurrentPage(0);
|
|
169
|
+
setItemsPerPage(initialItemsPerPage);
|
|
170
|
+
}, [initialItemsPerPage]);
|
|
171
|
+
|
|
172
|
+
const resetSorting = useCallback(() => {
|
|
173
|
+
setSortKey(initialSortKey);
|
|
174
|
+
setSortDirection(initialSortDirection);
|
|
175
|
+
}, [initialSortKey, initialSortDirection]);
|
|
176
|
+
|
|
177
|
+
const resetAll = useCallback(() => {
|
|
178
|
+
resetPagination();
|
|
179
|
+
resetSorting();
|
|
180
|
+
}, [resetPagination, resetSorting]);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
// Data
|
|
184
|
+
currentData,
|
|
185
|
+
// Sorting
|
|
186
|
+
sortKey,
|
|
187
|
+
sortDirection,
|
|
188
|
+
handleSort,
|
|
189
|
+
// Pagination
|
|
190
|
+
currentPage: validCurrentPage,
|
|
191
|
+
totalPages,
|
|
192
|
+
totalItems,
|
|
193
|
+
itemsPerPage,
|
|
194
|
+
handlePageChange,
|
|
195
|
+
handlePageSizeChange,
|
|
196
|
+
// Utilities
|
|
197
|
+
resetPagination,
|
|
198
|
+
resetSorting,
|
|
199
|
+
resetAll,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Hook for filtering table data
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```tsx
|
|
208
|
+
* const { filteredData, filters, updateFilter, resetFilters } = useTableFilters({
|
|
209
|
+
* data: users,
|
|
210
|
+
* filterFunctions: {
|
|
211
|
+
* search: (item, value) =>
|
|
212
|
+
* item.name.toLowerCase().includes(value.toLowerCase()) ||
|
|
213
|
+
* item.email.toLowerCase().includes(value.toLowerCase()),
|
|
214
|
+
* status: (item, value) => value === 'all' || item.status === value,
|
|
215
|
+
* department: (item, value) => value === 'all' || item.department === value
|
|
216
|
+
* },
|
|
217
|
+
* initialFilters: {
|
|
218
|
+
* search: '',
|
|
219
|
+
* status: 'all',
|
|
220
|
+
* department: 'all'
|
|
221
|
+
* }
|
|
222
|
+
* });
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
export interface UseTableFiltersOptions<T, F extends Record<string, any>> {
|
|
226
|
+
/** Data to filter */
|
|
227
|
+
data: T[];
|
|
228
|
+
/** Filter functions for each filter key */
|
|
229
|
+
filterFunctions: {
|
|
230
|
+
[K in keyof F]: (item: T, value: F[K]) => boolean;
|
|
231
|
+
};
|
|
232
|
+
/** Initial filter values */
|
|
233
|
+
initialFilters: F;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface UseTableFiltersReturn<T, F> {
|
|
237
|
+
/** Filtered data */
|
|
238
|
+
filteredData: T[];
|
|
239
|
+
/** Current filter values */
|
|
240
|
+
filters: F;
|
|
241
|
+
/** Update a single filter */
|
|
242
|
+
updateFilter: <K extends keyof F>(key: K, value: F[K]) => void;
|
|
243
|
+
/** Update multiple filters at once */
|
|
244
|
+
updateFilters: (filters: Partial<F>) => void;
|
|
245
|
+
/** Reset all filters to initial values */
|
|
246
|
+
resetFilters: () => void;
|
|
247
|
+
/** Check if any filters are active */
|
|
248
|
+
hasActiveFilters: boolean;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function useTableFilters<T, F extends Record<string, any>>({
|
|
252
|
+
data,
|
|
253
|
+
filterFunctions,
|
|
254
|
+
initialFilters,
|
|
255
|
+
}: UseTableFiltersOptions<T, F>): UseTableFiltersReturn<T, F> {
|
|
256
|
+
const [filters, setFilters] = useState<F>(initialFilters);
|
|
257
|
+
|
|
258
|
+
// Apply filters
|
|
259
|
+
const filteredData = useMemo(() => {
|
|
260
|
+
return data.filter((item) => {
|
|
261
|
+
for (const [key, filterFn] of Object.entries(filterFunctions)) {
|
|
262
|
+
const filterValue = filters[key as keyof F];
|
|
263
|
+
if (!filterFn(item, filterValue)) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
});
|
|
269
|
+
}, [data, filters, filterFunctions]);
|
|
270
|
+
|
|
271
|
+
// Update single filter
|
|
272
|
+
const updateFilter = useCallback(<K extends keyof F>(key: K, value: F[K]) => {
|
|
273
|
+
setFilters((prev) => ({ ...prev, [key]: value }));
|
|
274
|
+
}, []);
|
|
275
|
+
|
|
276
|
+
// Update multiple filters
|
|
277
|
+
const updateFilters = useCallback((newFilters: Partial<F>) => {
|
|
278
|
+
setFilters((prev) => ({ ...prev, ...newFilters }));
|
|
279
|
+
}, []);
|
|
280
|
+
|
|
281
|
+
// Reset filters
|
|
282
|
+
const resetFilters = useCallback(() => {
|
|
283
|
+
setFilters(initialFilters);
|
|
284
|
+
}, [initialFilters]);
|
|
285
|
+
|
|
286
|
+
// Check if any filters are active
|
|
287
|
+
const hasActiveFilters = useMemo(() => {
|
|
288
|
+
return Object.entries(filters).some(([key, value]) => {
|
|
289
|
+
return value !== initialFilters[key as keyof F];
|
|
290
|
+
});
|
|
291
|
+
}, [filters, initialFilters]);
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
filteredData,
|
|
295
|
+
filters,
|
|
296
|
+
updateFilter,
|
|
297
|
+
updateFilters,
|
|
298
|
+
resetFilters,
|
|
299
|
+
hasActiveFilters,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Combine filtering with table controls
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```tsx
|
|
308
|
+
* const filters = useTableFilters({ ... });
|
|
309
|
+
* const controls = useTableControls({
|
|
310
|
+
* data: filters.filteredData,
|
|
311
|
+
* ...
|
|
312
|
+
* });
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
export function useFilteredTableControls<T, F extends Record<string, any>>(
|
|
316
|
+
filterOptions: UseTableFiltersOptions<T, F>,
|
|
317
|
+
controlOptions: Omit<UseTableControlsOptions<T>, "data">,
|
|
318
|
+
) {
|
|
319
|
+
const filters = useTableFilters(filterOptions);
|
|
320
|
+
const controls = useTableControls({
|
|
321
|
+
...controlOptions,
|
|
322
|
+
data: filters.filteredData,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
...filters,
|
|
327
|
+
...controls,
|
|
328
|
+
// Override data with the current page data
|
|
329
|
+
data: controls.currentData,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Text } from "@/design-system/elements";
|
|
2
|
+
|
|
3
|
+
type TagProps = {
|
|
4
|
+
[key: string]: any;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const Tag = ({ children, ...props }: TagProps) => {
|
|
8
|
+
return (
|
|
9
|
+
<Text
|
|
10
|
+
as="div"
|
|
11
|
+
display="flex"
|
|
12
|
+
alignItems="center"
|
|
13
|
+
gap="xxsmall"
|
|
14
|
+
p="xs"
|
|
15
|
+
py="xxxs"
|
|
16
|
+
color="secondary"
|
|
17
|
+
fontSize="small"
|
|
18
|
+
fontWeight="600"
|
|
19
|
+
shape="rounded"
|
|
20
|
+
width="fit-content"
|
|
21
|
+
flex="none"
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
{children}
|
|
25
|
+
</Text>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import { Box, Text } from "@/design-system/elements"
|
|
3
|
+
import { IconDotsAnim } from "@/design-system/icons"
|
|
4
|
+
|
|
5
|
+
type TextBreakProps = {
|
|
6
|
+
message?: string
|
|
7
|
+
colorSchema?: string
|
|
8
|
+
loading?: boolean
|
|
9
|
+
showBreaks?: boolean
|
|
10
|
+
position?: "left" | "center" | "right"
|
|
11
|
+
children?: React.ReactNode
|
|
12
|
+
contentProps?: {
|
|
13
|
+
[key: string]: any
|
|
14
|
+
}
|
|
15
|
+
breakProps?: {
|
|
16
|
+
[key: string]: any
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
[key: string]: any
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default function TextBreak({
|
|
23
|
+
message,
|
|
24
|
+
loading,
|
|
25
|
+
showBreaks = true,
|
|
26
|
+
colorSchema = "default",
|
|
27
|
+
position = "center",
|
|
28
|
+
children,
|
|
29
|
+
contentProps = {},
|
|
30
|
+
breakProps = {},
|
|
31
|
+
...props
|
|
32
|
+
}: TextBreakProps) {
|
|
33
|
+
const noticePalette: any = {
|
|
34
|
+
default: ["tertiary", "secondary"],
|
|
35
|
+
attention: ["currentColor", "red"],
|
|
36
|
+
accent: ["currentColor", "accent"],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const content = message || children
|
|
40
|
+
const leftBreakFlex =
|
|
41
|
+
position === "right" ? "auto" : position === "center" ? "auto" : "0"
|
|
42
|
+
const rightBreakFlex =
|
|
43
|
+
position === "left" ? "auto" : position === "center" ? "auto" : "0"
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Text
|
|
47
|
+
as="div"
|
|
48
|
+
color={noticePalette[colorSchema][1]}
|
|
49
|
+
display="flex"
|
|
50
|
+
alignItems={"center"}
|
|
51
|
+
width="100%"
|
|
52
|
+
fontSize="xsmall"
|
|
53
|
+
gap="xsmall"
|
|
54
|
+
{...props}
|
|
55
|
+
>
|
|
56
|
+
{showBreaks && position !== "left" && (
|
|
57
|
+
<Box
|
|
58
|
+
flex={leftBreakFlex}
|
|
59
|
+
width={"auto"}
|
|
60
|
+
height={"1px"}
|
|
61
|
+
bg={noticePalette[colorSchema][0]}
|
|
62
|
+
opacity="0.25"
|
|
63
|
+
{...breakProps}
|
|
64
|
+
/>
|
|
65
|
+
)}
|
|
66
|
+
{content && (
|
|
67
|
+
<Text
|
|
68
|
+
as="div"
|
|
69
|
+
display="flex"
|
|
70
|
+
alignItems={"center"}
|
|
71
|
+
flex="none"
|
|
72
|
+
gap="xsmall"
|
|
73
|
+
px={position === "left" ? 0 : "small"}
|
|
74
|
+
py="xxxsmall"
|
|
75
|
+
shape="pill"
|
|
76
|
+
{...contentProps}
|
|
77
|
+
>
|
|
78
|
+
{loading && (
|
|
79
|
+
<IconDotsAnim color="currentColor" width="20" height="20" />
|
|
80
|
+
)}
|
|
81
|
+
<Text flex="none">{content}</Text>
|
|
82
|
+
</Text>
|
|
83
|
+
)}
|
|
84
|
+
{showBreaks && position !== "right" && (
|
|
85
|
+
<Box
|
|
86
|
+
flex={rightBreakFlex}
|
|
87
|
+
width={"auto"}
|
|
88
|
+
height={"1px"}
|
|
89
|
+
bg={noticePalette[colorSchema][0]}
|
|
90
|
+
opacity="0.25"
|
|
91
|
+
{...breakProps}
|
|
92
|
+
/>
|
|
93
|
+
)}
|
|
94
|
+
</Text>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo, useRef } from "react";
|
|
4
|
+
import { motion, type MotionValue, useScroll, useTransform } from "motion/react";
|
|
5
|
+
import styled from "styled-components";
|
|
6
|
+
|
|
7
|
+
import { Text, type TextProps } from "@/design-system/elements";
|
|
8
|
+
import { useReducedMotion } from "@/design-system/hooks/useReducedMotion";
|
|
9
|
+
|
|
10
|
+
type ScrollOffset = NonNullable<Parameters<typeof useScroll>[0]>["offset"];
|
|
11
|
+
|
|
12
|
+
export interface TextRevealProps extends Omit<TextProps, "children" | "as"> {
|
|
13
|
+
children: string;
|
|
14
|
+
baseOpacity?: number;
|
|
15
|
+
offset?: ScrollOffset;
|
|
16
|
+
revealEnd?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const DEFAULT_OFFSET: ScrollOffset = ["start end", "end start"];
|
|
20
|
+
|
|
21
|
+
interface RevealedWordProps {
|
|
22
|
+
children: string;
|
|
23
|
+
progress: MotionValue<number>;
|
|
24
|
+
range: [number, number];
|
|
25
|
+
baseOpacity: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const Word = styled.span`
|
|
29
|
+
position: relative;
|
|
30
|
+
display: inline-grid;
|
|
31
|
+
margin-right: 0.25em;
|
|
32
|
+
margin-bottom: 0.1em;
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const WordBase = styled.span<{ $baseOpacity: number }>`
|
|
36
|
+
grid-area: 1 / 1;
|
|
37
|
+
opacity: ${(props) => props.$baseOpacity};
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const WordForeground = styled(motion.span)`
|
|
41
|
+
grid-area: 1 / 1;
|
|
42
|
+
color: currentColor;
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
function RevealedWord({ children, progress, range, baseOpacity }: RevealedWordProps) {
|
|
46
|
+
const opacity = useTransform(progress, range, [0, 1]);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Word aria-hidden="true">
|
|
50
|
+
<WordBase $baseOpacity={baseOpacity}>{children}</WordBase>
|
|
51
|
+
<WordForeground style={{ opacity }}>{children}</WordForeground>
|
|
52
|
+
</Word>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default function TextReveal({
|
|
57
|
+
children,
|
|
58
|
+
baseOpacity = 0.18,
|
|
59
|
+
offset = DEFAULT_OFFSET,
|
|
60
|
+
revealEnd = 0.88,
|
|
61
|
+
...props
|
|
62
|
+
}: TextRevealProps) {
|
|
63
|
+
const textRef = useRef<HTMLDivElement | null>(null);
|
|
64
|
+
const prefersReducedMotion = useReducedMotion();
|
|
65
|
+
const words = useMemo(
|
|
66
|
+
() => children.trim().split(/\s+/).filter(Boolean),
|
|
67
|
+
[children],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const { scrollYProgress } = useScroll({
|
|
71
|
+
target: textRef,
|
|
72
|
+
offset,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const clampedRevealEnd = Math.max(0.1, Math.min(revealEnd, 1));
|
|
76
|
+
|
|
77
|
+
if (prefersReducedMotion) {
|
|
78
|
+
return (
|
|
79
|
+
<Text as="div" ref={textRef} {...props}>
|
|
80
|
+
{children}
|
|
81
|
+
</Text>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Text as="div" ref={textRef} aria-label={children} {...props}>
|
|
87
|
+
{words.map((word, index) => {
|
|
88
|
+
const start = (index / words.length) * clampedRevealEnd;
|
|
89
|
+
const end = ((index + 1) / words.length) * clampedRevealEnd;
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<RevealedWord
|
|
93
|
+
key={`${word}-${index}`}
|
|
94
|
+
progress={scrollYProgress}
|
|
95
|
+
range={[start, end]}
|
|
96
|
+
baseOpacity={baseOpacity}
|
|
97
|
+
>
|
|
98
|
+
{word}
|
|
99
|
+
</RevealedWord>
|
|
100
|
+
);
|
|
101
|
+
})}
|
|
102
|
+
</Text>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import Box, { BoxProps } from "@/design-system/elements/box";
|
|
4
|
+
import Image from "next/image";
|
|
5
|
+
|
|
6
|
+
interface ThumbnailProps extends Omit<BoxProps, "as"> {
|
|
7
|
+
src: string;
|
|
8
|
+
alt: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Thumbnail: React.FC<ThumbnailProps> = ({ src, alt, ...rest }) => {
|
|
12
|
+
return (
|
|
13
|
+
<Box position="relative" overflow="hidden" {...rest}>
|
|
14
|
+
<Box
|
|
15
|
+
as={Image}
|
|
16
|
+
src={src}
|
|
17
|
+
alt={alt}
|
|
18
|
+
fill
|
|
19
|
+
sizes="100rem"
|
|
20
|
+
style={{ objectFit: "cover" }}
|
|
21
|
+
/>
|
|
22
|
+
</Box>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default Thumbnail;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { animate, motion, useMotionValue } from "motion/react";
|
|
4
|
+
import { Box } from "@/design-system/elements";
|
|
5
|
+
import useMeasure from "react-use-measure";
|
|
6
|
+
|
|
7
|
+
interface TickerProps {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
fastDuration?: number;
|
|
10
|
+
slowDuration?: number;
|
|
11
|
+
direction?: "left" | "right";
|
|
12
|
+
gap?: string | number;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function Ticker({
|
|
17
|
+
children,
|
|
18
|
+
fastDuration = 25,
|
|
19
|
+
slowDuration = 75,
|
|
20
|
+
direction = "left",
|
|
21
|
+
gap = "medium",
|
|
22
|
+
}: TickerProps) {
|
|
23
|
+
const [duration, setDuration] = React.useState(fastDuration);
|
|
24
|
+
const [ref, { width }] = useMeasure();
|
|
25
|
+
const xTranslation = useMotionValue(0);
|
|
26
|
+
const [mustFinish, setMustFinish] = React.useState(false);
|
|
27
|
+
const [rerender, setRerender] = React.useState(false);
|
|
28
|
+
|
|
29
|
+
// Convert direction string to multiplier
|
|
30
|
+
const directionMultiplier = direction === "left" ? -1 : 1;
|
|
31
|
+
|
|
32
|
+
// Convert children to array
|
|
33
|
+
const childrenArray = React.Children.toArray(children);
|
|
34
|
+
|
|
35
|
+
React.useEffect(() => {
|
|
36
|
+
if (!width) return;
|
|
37
|
+
|
|
38
|
+
let controls;
|
|
39
|
+
const finalPosition = (width / 2) * directionMultiplier;
|
|
40
|
+
|
|
41
|
+
if (mustFinish) {
|
|
42
|
+
const currentPosition = xTranslation.get();
|
|
43
|
+
const remainingDistance = finalPosition - currentPosition;
|
|
44
|
+
const progressRatio = Math.abs(remainingDistance / finalPosition);
|
|
45
|
+
|
|
46
|
+
controls = animate(xTranslation, finalPosition, {
|
|
47
|
+
ease: "linear",
|
|
48
|
+
duration: duration * progressRatio,
|
|
49
|
+
onComplete: () => {
|
|
50
|
+
setMustFinish(false);
|
|
51
|
+
setRerender(!rerender);
|
|
52
|
+
xTranslation.set(0);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
controls = animate(xTranslation, finalPosition, {
|
|
57
|
+
ease: "linear",
|
|
58
|
+
duration: duration,
|
|
59
|
+
repeat: Infinity,
|
|
60
|
+
repeatType: "loop",
|
|
61
|
+
onRepeat: () => {
|
|
62
|
+
xTranslation.set(0);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return () => controls.stop();
|
|
68
|
+
|
|
69
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
70
|
+
}, [rerender, duration, width, directionMultiplier, mustFinish]);
|
|
71
|
+
|
|
72
|
+
const renderItems = React.useCallback(() => {
|
|
73
|
+
// Create two sets of items to ensure smooth looping
|
|
74
|
+
const itemSet = (
|
|
75
|
+
<Box display="flex" gap={gap}>
|
|
76
|
+
{childrenArray.map((item, index) => (
|
|
77
|
+
<Box key={`set-${index}`}>{item}</Box>
|
|
78
|
+
))}
|
|
79
|
+
</Box>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<>
|
|
84
|
+
{itemSet}
|
|
85
|
+
{itemSet}
|
|
86
|
+
</>
|
|
87
|
+
);
|
|
88
|
+
}, [childrenArray, gap]);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Box width="100%" height="100%" overflow="hidden">
|
|
92
|
+
<Box
|
|
93
|
+
as={motion.div}
|
|
94
|
+
ref={ref}
|
|
95
|
+
style={{ x: xTranslation }}
|
|
96
|
+
display="flex"
|
|
97
|
+
gap={gap}
|
|
98
|
+
width="fit-content"
|
|
99
|
+
onHoverStart={() => {
|
|
100
|
+
setMustFinish(true);
|
|
101
|
+
setDuration(slowDuration);
|
|
102
|
+
}}
|
|
103
|
+
onHoverEnd={() => {
|
|
104
|
+
setMustFinish(true);
|
|
105
|
+
setDuration(fastDuration);
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
{renderItems()}
|
|
109
|
+
</Box>
|
|
110
|
+
</Box>
|
|
111
|
+
);
|
|
112
|
+
}
|