@studiocubics/components 0.0.1
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/CHANGELOG.md +11 -0
- package/README.md +71 -0
- package/eslint.config.js +21 -0
- package/package.json +66 -0
- package/rollup.config.js +34 -0
- package/src/Cards/Card/Card.module.css +27 -0
- package/src/Cards/Card/Card.tsx +105 -0
- package/src/Cards/CollectionItemCard/CollectionItemCard.module.css +84 -0
- package/src/Cards/CollectionItemCard/CollectionItemCard.tsx +170 -0
- package/src/Cards/CollectionItemCard/CollectionItemCardActions.tsx +85 -0
- package/src/Cards/CollectionItemCard/_index.ts +2 -0
- package/src/Cards/GlassCard/GlassCard.module.css +71 -0
- package/src/Cards/GlassCard/GlassCard.tsx +80 -0
- package/src/Cards/_index.ts +3 -0
- package/src/Display/Accordion/Accordion.module.css +69 -0
- package/src/Display/Accordion/Accordion.tsx +61 -0
- package/src/Display/Accordion/AccordionItem.tsx +135 -0
- package/src/Display/Accordion/_index.ts +2 -0
- package/src/Display/Chip/Chip.module.css +64 -0
- package/src/Display/Chip/Chip.tsx +105 -0
- package/src/Display/IdentityDisplay/IdentityDisplay.module.css +95 -0
- package/src/Display/IdentityDisplay/IdentityDisplay.tsx +119 -0
- package/src/Display/InputErrors/InputErrors.module.css +6 -0
- package/src/Display/InputErrors/InputErrors.tsx +52 -0
- package/src/Display/Kbd/Kbd.module.css +29 -0
- package/src/Display/Kbd/Kbd.tsx +39 -0
- package/src/Display/Kbd/_index.ts +2 -0
- package/src/Display/Kbd/buttonList.tsx +246 -0
- package/src/Display/LabeledValue/LabeledValue.module.css +32 -0
- package/src/Display/LabeledValue/LabeledValue.tsx +20 -0
- package/src/Display/List/List.module.css +143 -0
- package/src/Display/List/List.tsx +298 -0
- package/src/Display/PasswordStrength/PasswordStrength.module.css +45 -0
- package/src/Display/PasswordStrength/PasswordStrength.tsx +41 -0
- package/src/Display/PasswordStrength/usePasswordStrength.tsx +77 -0
- package/src/Display/Skeleton/Skeleton.module.css +54 -0
- package/src/Display/Skeleton/Skeleton.tsx +28 -0
- package/src/Display/Toast/Toaster.tsx +58 -0
- package/src/Display/Toast/_index.ts +2 -0
- package/src/Display/Toast/toast.ts +44 -0
- package/src/Display/Tooltip/Tooltip.module.css +128 -0
- package/src/Display/Tooltip/Tooltip.tsx +93 -0
- package/src/Display/Tooltip/getArrowDirection.ts +55 -0
- package/src/Display/Tooltip/useTooltip.tsx +63 -0
- package/src/Display/_index.ts +12 -0
- package/src/Forms/ConfirmationForm/ConfirmationForm.module.css +23 -0
- package/src/Forms/ConfirmationForm/ConfirmationForm.tsx +60 -0
- package/src/Forms/_index.ts +1 -0
- package/src/Inputs/Button/Button.module.css +131 -0
- package/src/Inputs/Button/Button.tsx +178 -0
- package/src/Inputs/Checkbox/Checkbox.module.css +77 -0
- package/src/Inputs/Checkbox/Checkbox.tsx +191 -0
- package/src/Inputs/Checkbox/CheckboxGroup/CheckboxGroup.module.css +10 -0
- package/src/Inputs/Checkbox/CheckboxGroup/CheckboxGroup.tsx +83 -0
- package/src/Inputs/Checkbox/CheckboxSelectAll.tsx +34 -0
- package/src/Inputs/Checkbox/_index.ts +3 -0
- package/src/Inputs/PasswordInput/PasswordInput.module.css +111 -0
- package/src/Inputs/PasswordInput/PasswordInput.tsx +229 -0
- package/src/Inputs/Select/Select.module.css +138 -0
- package/src/Inputs/Select/Select.tsx +136 -0
- package/src/Inputs/Switch/Switch.module.css +119 -0
- package/src/Inputs/Switch/Switch.tsx +195 -0
- package/src/Inputs/TextAreaInput/TextAreaInput.module.css +65 -0
- package/src/Inputs/TextAreaInput/TextAreaInput.tsx +97 -0
- package/src/Inputs/TextInput/TextInput.module.css +112 -0
- package/src/Inputs/TextInput/TextInput.tsx +142 -0
- package/src/Inputs/ThemeToggle/ThemeToggleListItem.tsx +80 -0
- package/src/Inputs/ThemeToggle/_index.ts +1 -0
- package/src/Inputs/_index.ts +8 -0
- package/src/Layout/Dialog/Dialog.module.css +15 -0
- package/src/Layout/Dialog/Dialog.tsx +115 -0
- package/src/Layout/PageLayout/PageLayout.module.css +20 -0
- package/src/Layout/PageLayout/PageLayout.tsx +79 -0
- package/src/Layout/PageLayoutPagination/PageLayoutPagination.module.css +5 -0
- package/src/Layout/PageLayoutPagination/PageLayoutPagination.tsx +40 -0
- package/src/Layout/PageLayoutTabs/PageLayoutTabs.module.css +3 -0
- package/src/Layout/PageLayoutTabs/PageLayoutTabs.tsx +62 -0
- package/src/Layout/Popover/Popover.module.css +9 -0
- package/src/Layout/Popover/Popover.tsx +145 -0
- package/src/Layout/SectionWrapper/SectionWrapper.module.css +31 -0
- package/src/Layout/SectionWrapper/SectionWrapper.tsx +62 -0
- package/src/Layout/Sidebar/Sidebar.module.css +17 -0
- package/src/Layout/Sidebar/Sidebar.tsx +39 -0
- package/src/Layout/Sidebar/SidebarBody/SidebarBody.module.css +31 -0
- package/src/Layout/Sidebar/SidebarBody/SidebarBody.tsx +18 -0
- package/src/Layout/Sidebar/SidebarDrawer/SidebarDrawer.module.css +20 -0
- package/src/Layout/Sidebar/SidebarDrawer/SidebarDrawer.tsx +19 -0
- package/src/Layout/Sidebar/SidebarFooter/SidebarFooter.module.css +35 -0
- package/src/Layout/Sidebar/SidebarFooter/SidebarFooter.tsx +19 -0
- package/src/Layout/Sidebar/SidebarHeader/SidebarHeader.tsx +14 -0
- package/src/Layout/Sidebar/SidebarViewport/SidebarViewport.module.css +12 -0
- package/src/Layout/Sidebar/SidebarViewport/SidebarViewport.tsx +11 -0
- package/src/Layout/Sidebar/_index.ts +6 -0
- package/src/Layout/Table/Table.module.css +46 -0
- package/src/Layout/Table/Table.tsx +222 -0
- package/src/Layout/Table/TableFooter.tsx +4 -0
- package/src/Layout/Table/TableHeader.tsx +4 -0
- package/src/Layout/Table/_index.ts +5 -0
- package/src/Layout/Table/tableUtils.ts +142 -0
- package/src/Layout/Table/types.ts +48 -0
- package/src/Layout/_index.ts +8 -0
- package/src/Misc/Cursor/Cursor.module.css +31 -0
- package/src/Misc/Cursor/Cursor.tsx +77 -0
- package/src/Misc/Logos.tsx +230 -0
- package/src/Misc/PoweredByBanner/PoweredByBanner.module.css +20 -0
- package/src/Misc/PoweredByBanner/PoweredByBanner.tsx +17 -0
- package/src/Misc/Ripple/Ripple.module.css +25 -0
- package/src/Misc/Ripple/Ripple.tsx +126 -0
- package/src/Misc/Spinner/Spinner.module.css +38 -0
- package/src/Misc/Spinner/Spinner.tsx +36 -0
- package/src/Misc/TransitionAnimation/TransitionAnimation.module.css +131 -0
- package/src/Misc/TransitionAnimation/TransitionAnimation.tsx +166 -0
- package/src/Misc/_index.ts +6 -0
- package/src/Navigation/Breadcrumbs/Breadcrumbs.module.css +22 -0
- package/src/Navigation/Breadcrumbs/Breadcrumbs.tsx +127 -0
- package/src/Navigation/Breadcrumbs/BreadcrumbsItem.tsx +31 -0
- package/src/Navigation/Breadcrumbs/_index.ts +3 -0
- package/src/Navigation/Breadcrumbs/useBreadcrumbs.tsx +74 -0
- package/src/Navigation/Pagination/Pagination.module.css +41 -0
- package/src/Navigation/Pagination/Pagination.tsx +187 -0
- package/src/Navigation/Pagination/PaginationItem.tsx +28 -0
- package/src/Navigation/Pagination/_index.ts +3 -0
- package/src/Navigation/Pagination/usePagination.tsx +65 -0
- package/src/Navigation/Tabs/Tab/Tab.module.css +43 -0
- package/src/Navigation/Tabs/Tab/Tab.tsx +155 -0
- package/src/Navigation/Tabs/Tabs.tsx +37 -0
- package/src/Navigation/Tabs/TabsBar/TabsBar.module.css +47 -0
- package/src/Navigation/Tabs/TabsBar/TabsBar.tsx +92 -0
- package/src/Navigation/Tabs/_index.ts +3 -0
- package/src/Navigation/_index.ts +3 -0
- package/src/Typography/ClampedText/ClampedText.module.css +5 -0
- package/src/Typography/ClampedText/ClampedText.tsx +77 -0
- package/src/Typography/CopyableText/CopyableText.module.css +21 -0
- package/src/Typography/CopyableText/CopyableText.tsx +120 -0
- package/src/Typography/PageTitle/PageTitle.module.css +47 -0
- package/src/Typography/PageTitle/PageTitle.tsx +35 -0
- package/src/Typography/_index.ts +3 -0
- package/src/declaration.d.ts +4 -0
- package/src/index.ts +8 -0
- package/tsconfig.json +32 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps } from "react";
|
|
4
|
+
import styles from "./SidebarBody.module.css";
|
|
5
|
+
import { cn } from "@studiocubics/utils";
|
|
6
|
+
import { useSidebar } from "../Sidebar";
|
|
7
|
+
export function SidebarBody(props: ComponentProps<"div">) {
|
|
8
|
+
const { children, className, ...rest } = props;
|
|
9
|
+
const { sidebarOpen } = useSidebar();
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
className={cn(styles.root, className, sidebarOpen ? styles.open : "")}
|
|
13
|
+
{...rest}
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
background: var(--color-surface);
|
|
3
|
+
transition: width var(--transition-time) var(--transition-tf);
|
|
4
|
+
width: 100%;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: row;
|
|
7
|
+
z-index: -1;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@media (min-width: 600px) {
|
|
11
|
+
.root {
|
|
12
|
+
background: var(--sidebar-color-surface);
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
width: var(--sidebar-closed-width);
|
|
15
|
+
z-index: 0;
|
|
16
|
+
}
|
|
17
|
+
.open {
|
|
18
|
+
width: var(--sidebar-open-width);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps } from "react";
|
|
4
|
+
import styles from "./SidebarDrawer.module.css";
|
|
5
|
+
import { cn } from "@studiocubics/utils";
|
|
6
|
+
import { useSidebar } from "../Sidebar";
|
|
7
|
+
|
|
8
|
+
export function SidebarDrawer(props: ComponentProps<"div">) {
|
|
9
|
+
const { children, className, ...rest } = props;
|
|
10
|
+
const { sidebarOpen } = useSidebar();
|
|
11
|
+
return (
|
|
12
|
+
<aside
|
|
13
|
+
className={cn(styles.root, className, sidebarOpen ? styles.open : "")}
|
|
14
|
+
{...rest}
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
</aside>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
/* display: none; */
|
|
3
|
+
|
|
4
|
+
position: fixed;
|
|
5
|
+
left: -100%;
|
|
6
|
+
top: calc(var(--sidebar-header-height) + var(--sidebar-body-height));
|
|
7
|
+
width: var(--sidebar-open-width);
|
|
8
|
+
padding-bottom: var(--spacing-gap-2);
|
|
9
|
+
padding-top: var(--spacing-gap-1);
|
|
10
|
+
|
|
11
|
+
background-color: var(--color-surface-alpha);
|
|
12
|
+
backdrop-filter: var(--backdrop-blur);
|
|
13
|
+
height: calc(
|
|
14
|
+
100dvh - var(--sidebar-body-height) - var(--sidebar-header-height)
|
|
15
|
+
);
|
|
16
|
+
overflow: auto;
|
|
17
|
+
transition: left var(--transition-time) var(--transition-tf);
|
|
18
|
+
}
|
|
19
|
+
.open {
|
|
20
|
+
left: 0;
|
|
21
|
+
}
|
|
22
|
+
@media (min-width: 600px) {
|
|
23
|
+
.root {
|
|
24
|
+
position: relative;
|
|
25
|
+
left: 0;
|
|
26
|
+
height: unset;
|
|
27
|
+
width: unset;
|
|
28
|
+
top: 0;
|
|
29
|
+
background-color: transparent;
|
|
30
|
+
backdrop-filter: none;
|
|
31
|
+
}
|
|
32
|
+
.open {
|
|
33
|
+
left: 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@studiocubics/utils";
|
|
4
|
+
import styles from "./SidebarFooter.module.css";
|
|
5
|
+
import type { ComponentProps } from "react";
|
|
6
|
+
import { useSidebar } from "../Sidebar";
|
|
7
|
+
|
|
8
|
+
export function SidebarFooter(props: ComponentProps<"div">) {
|
|
9
|
+
const { children, className, ...rest } = props;
|
|
10
|
+
const { sidebarOpen } = useSidebar();
|
|
11
|
+
return (
|
|
12
|
+
<div
|
|
13
|
+
className={cn(styles.root, className, sidebarOpen ? styles.open : "")}
|
|
14
|
+
{...rest}
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps } from "react";
|
|
4
|
+
import { useSidebar } from "../Sidebar";
|
|
5
|
+
|
|
6
|
+
export function SidebarHeader(props: ComponentProps<"div">) {
|
|
7
|
+
const { children, ...rest } = props;
|
|
8
|
+
const { sidebarOpen } = useSidebar();
|
|
9
|
+
return (
|
|
10
|
+
<div data-open={sidebarOpen} {...rest}>
|
|
11
|
+
{children}
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import styles from "./SidebarViewport.module.css";
|
|
3
|
+
import { cn } from "@studiocubics/utils";
|
|
4
|
+
export function SidebarViewport(props: ComponentProps<"div">) {
|
|
5
|
+
const { children, className, ...rest } = props;
|
|
6
|
+
return (
|
|
7
|
+
<div className={cn(styles.root, className)} {...rest}>
|
|
8
|
+
{children}
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from "./Sidebar";
|
|
2
|
+
export * from "./SidebarBody/SidebarBody";
|
|
3
|
+
export * from "./SidebarDrawer/SidebarDrawer";
|
|
4
|
+
export * from "./SidebarFooter/SidebarFooter";
|
|
5
|
+
export * from "./SidebarHeader/SidebarHeader";
|
|
6
|
+
export * from "./SidebarViewport/SidebarViewport";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
overflow: auto;
|
|
3
|
+
max-width: 100%;
|
|
4
|
+
max-height: 100%;
|
|
5
|
+
}
|
|
6
|
+
.table {
|
|
7
|
+
display: grid;
|
|
8
|
+
width: 100%;
|
|
9
|
+
/* border-collapse: collapse; */
|
|
10
|
+
font-family:
|
|
11
|
+
system-ui,
|
|
12
|
+
-apple-system,
|
|
13
|
+
BlinkMacSystemFont,
|
|
14
|
+
sans-serif;
|
|
15
|
+
font-size: var(--fs-body2);
|
|
16
|
+
line-height: 1.4;
|
|
17
|
+
border-radius: var(--shape-br-md);
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
}
|
|
20
|
+
.tableCell {
|
|
21
|
+
padding: 8px 12px;
|
|
22
|
+
border-right: 1px solid var(--color-outline);
|
|
23
|
+
border-bottom: 1px solid var(--color-outline);
|
|
24
|
+
background: var(--color-background);
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
text-overflow: ellipsis;
|
|
27
|
+
white-space: nowrap;
|
|
28
|
+
user-select: none;
|
|
29
|
+
&:hover {
|
|
30
|
+
background: var(--color-background-faint);
|
|
31
|
+
}
|
|
32
|
+
&:focus-visible {
|
|
33
|
+
outline: 2px solid var(--color-primary);
|
|
34
|
+
outline-offset: -2px;
|
|
35
|
+
}
|
|
36
|
+
/* Last column */
|
|
37
|
+
&:nth-child(n) {
|
|
38
|
+
border-right: none;
|
|
39
|
+
}
|
|
40
|
+
&[data-selected="true"] {
|
|
41
|
+
background: var(--color-surface);
|
|
42
|
+
outline: 2px solid var(--color-primary);
|
|
43
|
+
outline-offset: -2px;
|
|
44
|
+
z-index: 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
isValidElement,
|
|
6
|
+
useContext,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
type ElementType,
|
|
10
|
+
type MouseEvent,
|
|
11
|
+
} from "react";
|
|
12
|
+
import { getColumnCount, isAOA, isAOO, isOCR, isOOA } from "./tableUtils";
|
|
13
|
+
import styles from "./Table.module.css";
|
|
14
|
+
import type { CellData, TableContextProps, TableProps } from "./types";
|
|
15
|
+
import type {
|
|
16
|
+
PolymorphicComponentProps,
|
|
17
|
+
PolymorphicComponentType,
|
|
18
|
+
} from "@studiocubics/types";
|
|
19
|
+
import { cn, mergeRefs } from "@studiocubics/utils";
|
|
20
|
+
|
|
21
|
+
const TableContext = createContext<TableContextProps | null>(null);
|
|
22
|
+
|
|
23
|
+
export function useTable() {
|
|
24
|
+
const c = useContext(TableContext);
|
|
25
|
+
if (!c) throw new Error("Components must be wrapped in <Table/>");
|
|
26
|
+
return c;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Works for data of four types
|
|
31
|
+
*
|
|
32
|
+
* **Array of arrays:-**
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* const aoaData: TableData = [
|
|
36
|
+
["name", "email", "age"], // column headers are always 0th element
|
|
37
|
+
["pumpernickel", "pump@nick.com", 420],
|
|
38
|
+
["rumpelstiltskin", "rump@stilt.com", 96],
|
|
39
|
+
];
|
|
40
|
+
* ```
|
|
41
|
+
* **Array of objects:-**
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* const aooData: TableData = [
|
|
45
|
+
{ name: "pumpernickel", email: "pump@nick.com", age: 420 }, // keys are column headers
|
|
46
|
+
{ name: "rumpelstiltskin", email: "rump@stilt.com", age: 96 },
|
|
47
|
+
];
|
|
48
|
+
* ```
|
|
49
|
+
* **Object of arrays:-**
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* const ooaData: TableData = {
|
|
53
|
+
name: ["pumpernickel", "rumpelstiltskin"], // keys are column headers
|
|
54
|
+
email: ["pump@nick.com", "rump@stilt.com"],
|
|
55
|
+
age: [420, 96],
|
|
56
|
+
};
|
|
57
|
+
* ```
|
|
58
|
+
* **Explicit schema + rows:-**
|
|
59
|
+
*
|
|
60
|
+
* ```ts
|
|
61
|
+
* const ocrData: TableData = {
|
|
62
|
+
columns: ["name", "email", "age"], // column headers are defined in a seperate columns array
|
|
63
|
+
rows: [
|
|
64
|
+
["pumpernickel", "pump@nick.com", 420],
|
|
65
|
+
["rumpelstiltskin", "rump@stilt.com", 96],
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function Table({ data }: TableProps) {
|
|
71
|
+
const [selectedCell, setSelectedCell] = useState<HTMLElement | null>(null);
|
|
72
|
+
|
|
73
|
+
const columnCount = getColumnCount(data);
|
|
74
|
+
function renderRows() {
|
|
75
|
+
// AOA (array of arrays)
|
|
76
|
+
if (isAOA(data)) {
|
|
77
|
+
return data.map((row, r) =>
|
|
78
|
+
Array.isArray(row)
|
|
79
|
+
? row.map((cell, c) => (
|
|
80
|
+
<TableCell
|
|
81
|
+
id={`${r},${c}`}
|
|
82
|
+
key={`${r},${c}`}
|
|
83
|
+
// selected={selectedCell[0] === r && selectedCell[1] === c}
|
|
84
|
+
>
|
|
85
|
+
{renderCell(cell)}
|
|
86
|
+
</TableCell>
|
|
87
|
+
))
|
|
88
|
+
: null,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// AOO (array of objects)
|
|
93
|
+
if (isAOO(data)) {
|
|
94
|
+
const columns = Object.keys(data[0]);
|
|
95
|
+
|
|
96
|
+
return data.map((row, r) =>
|
|
97
|
+
columns.map((col, c) => (
|
|
98
|
+
<TableCell
|
|
99
|
+
id={`${r},${c}`}
|
|
100
|
+
key={`${r},${c}`}
|
|
101
|
+
// selected={selectedCell[0] === r && selectedCell[1] === c}
|
|
102
|
+
>
|
|
103
|
+
{renderCell(row[col])}
|
|
104
|
+
</TableCell>
|
|
105
|
+
)),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// OOA (object of arrays)
|
|
110
|
+
if (isOOA(data)) {
|
|
111
|
+
const columns = Object.keys(data);
|
|
112
|
+
const rowCount = data[columns[0]]?.length ?? 0;
|
|
113
|
+
|
|
114
|
+
return Array.from({ length: rowCount }, (_, r) =>
|
|
115
|
+
columns.map((col, c) => (
|
|
116
|
+
<TableCell
|
|
117
|
+
id={`${r},${c}`}
|
|
118
|
+
key={`${r},${c}`}
|
|
119
|
+
// selected={selectedCell[0] === r && selectedCell[1] === c}
|
|
120
|
+
>
|
|
121
|
+
{renderCell(data[col][r])}
|
|
122
|
+
</TableCell>
|
|
123
|
+
)),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// OCR (columns + rows)
|
|
128
|
+
if (isOCR(data)) {
|
|
129
|
+
return data.rows.map((row, r) =>
|
|
130
|
+
row.map((cell, c) => (
|
|
131
|
+
<TableCell
|
|
132
|
+
id={`${r},${c}`}
|
|
133
|
+
key={`${r},${c}`}
|
|
134
|
+
// selected={selectedCell[0] === r && selectedCell[1] === c}
|
|
135
|
+
>
|
|
136
|
+
{renderCell(cell)}
|
|
137
|
+
</TableCell>
|
|
138
|
+
)),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<TableContext.Provider
|
|
147
|
+
value={{ selectedCell, setSelectedCell, columnCount }}
|
|
148
|
+
>
|
|
149
|
+
<div className={styles.root}>
|
|
150
|
+
<div
|
|
151
|
+
className={styles.table}
|
|
152
|
+
style={{
|
|
153
|
+
gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))`,
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
{renderRows()}
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</TableContext.Provider>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
export interface TableCellBaseProps {}
|
|
163
|
+
|
|
164
|
+
const defaultElement = "div";
|
|
165
|
+
type DefaultElement = typeof defaultElement;
|
|
166
|
+
|
|
167
|
+
export type TableCellProps<C extends ElementType = DefaultElement> =
|
|
168
|
+
PolymorphicComponentProps<C, TableCellBaseProps>;
|
|
169
|
+
|
|
170
|
+
function TableCellBase<C extends ElementType = DefaultElement>(
|
|
171
|
+
props: TableCellProps<C>,
|
|
172
|
+
) {
|
|
173
|
+
const { as, children, ref, onClick, ...rest } =
|
|
174
|
+
props as TableCellProps<DefaultElement>;
|
|
175
|
+
const cellRef = useRef<HTMLDivElement>(null);
|
|
176
|
+
const { setSelectedCell, selectedCell } = useTable();
|
|
177
|
+
|
|
178
|
+
const Component = as || defaultElement;
|
|
179
|
+
const componentProps = {
|
|
180
|
+
className: cn(
|
|
181
|
+
styles.tableCell,
|
|
182
|
+
selectedCell?.id == cellRef.current?.id ? styles.selected : "",
|
|
183
|
+
),
|
|
184
|
+
ref: mergeRefs(cellRef, ref),
|
|
185
|
+
onClick: handleSelectCell,
|
|
186
|
+
...rest,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
function handleSelectCell(e: MouseEvent<HTMLDivElement>) {
|
|
190
|
+
setSelectedCell(cellRef.current);
|
|
191
|
+
onClick?.(e);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return <Component {...componentProps}>{children}</Component>;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
TableCellBase.displayName = "TableCell";
|
|
198
|
+
|
|
199
|
+
export const TableCell = TableCellBase as PolymorphicComponentType<
|
|
200
|
+
TableCellBaseProps,
|
|
201
|
+
DefaultElement
|
|
202
|
+
>;
|
|
203
|
+
|
|
204
|
+
export function renderCell(cell: CellData) {
|
|
205
|
+
if (typeof cell === "boolean") return cell ? "✔" : "❌";
|
|
206
|
+
if (!cell) return;
|
|
207
|
+
if (typeof cell === "string" || isValidElement(cell)) return cell;
|
|
208
|
+
if (typeof cell === "object" && "type" in cell) {
|
|
209
|
+
switch (cell.type) {
|
|
210
|
+
case "boolean":
|
|
211
|
+
return cell.value ? "✔" : "X";
|
|
212
|
+
case "timestamp":
|
|
213
|
+
return cell.value.toLocaleString();
|
|
214
|
+
case "array":
|
|
215
|
+
return JSON.stringify(cell.value);
|
|
216
|
+
case "image":
|
|
217
|
+
return <img src={cell.value} alt={cell.alt} />;
|
|
218
|
+
default:
|
|
219
|
+
return cell.value;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { TableData, RowData, CellData } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks if array of arrays:-
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const aoaData: TableData = [
|
|
8
|
+
* ["name", "email", "age"],
|
|
9
|
+
* ["pumpernickel", "pump@nick.com", 420],
|
|
10
|
+
* ["rumpelstiltskin", "rump@stilt.com", 96],
|
|
11
|
+
* ];
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function isAOA(data: TableData): data is RowData[] {
|
|
15
|
+
if (!Array.isArray(data) || data.length === 0) return false;
|
|
16
|
+
|
|
17
|
+
if (!Array.isArray(data[0])) return false;
|
|
18
|
+
|
|
19
|
+
for (let i = 1; i < data.length; i++) {
|
|
20
|
+
const row = data[i];
|
|
21
|
+
if (row === null || (typeof row !== "object" && !Array.isArray(row))) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* Checks if array of objects:-
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* const aooData: TableData = [
|
|
33
|
+
* { name: "pumpernickel", email: "pump@nick.com", age: 420 },
|
|
34
|
+
* { name: "rumpelstiltskin", email: "rump@stilt.com", age: 96 },
|
|
35
|
+
* ];
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function isAOO(
|
|
39
|
+
data: TableData,
|
|
40
|
+
): data is { [column: string]: CellData }[] {
|
|
41
|
+
if (!Array.isArray(data) || data.length === 0) return false;
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < data.length; i++) {
|
|
44
|
+
const row = data[i];
|
|
45
|
+
if (row === null || typeof row !== "object" || Array.isArray(row)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks if object of arrays:-
|
|
54
|
+
*
|
|
55
|
+
* ```ts
|
|
56
|
+
* const ooaData: TableData = {
|
|
57
|
+
* name: ["pumpernickel", "rumpelstiltskin"],
|
|
58
|
+
* email: ["pump@nick.com", "rump@stilt.com"],
|
|
59
|
+
* age: [420, 96],
|
|
60
|
+
* };
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export function isOOA(
|
|
64
|
+
data: TableData,
|
|
65
|
+
): data is { [column: string]: CellData[] } {
|
|
66
|
+
if (
|
|
67
|
+
data === null ||
|
|
68
|
+
typeof data !== "object" ||
|
|
69
|
+
Array.isArray(data) ||
|
|
70
|
+
"columns" in data
|
|
71
|
+
) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const value of Object.values(data)) {
|
|
76
|
+
if (!Array.isArray(value)) return false;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Checks if explicit schema + rows:-
|
|
83
|
+
*
|
|
84
|
+
* ```ts
|
|
85
|
+
* const ocrData: TableData = {
|
|
86
|
+
* columns: ["name", "email", "age"],
|
|
87
|
+
* rows: [
|
|
88
|
+
* ["pumpernickel", "pump@nick.com", 420],
|
|
89
|
+
* ["rumpelstiltskin", "rump@stilt.com", 96],
|
|
90
|
+
* ],
|
|
91
|
+
* };
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export function isOCR(
|
|
95
|
+
data: TableData,
|
|
96
|
+
): data is { columns: CellData[]; rows: CellData[][] } {
|
|
97
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const d = data as any;
|
|
102
|
+
|
|
103
|
+
if (!Array.isArray(d.columns) || !Array.isArray(d.rows)) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < d.rows.length; i++) {
|
|
108
|
+
if (!Array.isArray(d.rows[i])) return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Returns the column count for any supported TableData shape.
|
|
116
|
+
* Assumes the data has already been validated with the appropriate is* guard.
|
|
117
|
+
*/
|
|
118
|
+
export function getColumnCount(data: TableData): number {
|
|
119
|
+
// Array of arrays (AOA)
|
|
120
|
+
if (Array.isArray(data)) {
|
|
121
|
+
if (data.length === 0) return 0;
|
|
122
|
+
|
|
123
|
+
const firstRow = data[0];
|
|
124
|
+
|
|
125
|
+
// Header row (AOA)
|
|
126
|
+
if (Array.isArray(firstRow)) {
|
|
127
|
+
return firstRow.length;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Array of objects (AOO)
|
|
131
|
+
return Object.keys(firstRow).length;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Object of arrays (OOA)
|
|
135
|
+
if ("columns" in data && "rows" in data) {
|
|
136
|
+
// Explicit columns + rows (OCR)
|
|
137
|
+
return data.columns.length;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Object of arrays (OOA)
|
|
141
|
+
return Object.keys(data).length;
|
|
142
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SetState } from "@studiocubics/types";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
export type TableData =
|
|
5
|
+
| RowData[]
|
|
6
|
+
| { columns: CellData[]; rows: CellData[][] }
|
|
7
|
+
| { [column: string]: CellData[] };
|
|
8
|
+
|
|
9
|
+
export type RowData = { [column: string]: CellData } | CellData[];
|
|
10
|
+
export type ITableCell = {
|
|
11
|
+
defaultSelected?: boolean;
|
|
12
|
+
} & (
|
|
13
|
+
| {
|
|
14
|
+
value: Date;
|
|
15
|
+
type: "timestamp";
|
|
16
|
+
}
|
|
17
|
+
| {
|
|
18
|
+
value: (string | number)[];
|
|
19
|
+
type: "array";
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
value: string;
|
|
23
|
+
type: "string";
|
|
24
|
+
}
|
|
25
|
+
| {
|
|
26
|
+
value: number;
|
|
27
|
+
type: "number";
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
alt: string;
|
|
31
|
+
value: string;
|
|
32
|
+
type: "image";
|
|
33
|
+
}
|
|
34
|
+
| {
|
|
35
|
+
value: boolean;
|
|
36
|
+
type: "boolean";
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
export type CellData = ITableCell | ReactNode;
|
|
40
|
+
export interface TableContextProps {
|
|
41
|
+
selectedCell: HTMLElement | null;
|
|
42
|
+
setSelectedCell: SetState<HTMLElement | null>;
|
|
43
|
+
columnCount: number;
|
|
44
|
+
}
|
|
45
|
+
export interface TableProps {
|
|
46
|
+
data: TableData;
|
|
47
|
+
showMarker?: boolean;
|
|
48
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./Dialog/Dialog";
|
|
2
|
+
export * from "./PageLayout/PageLayout";
|
|
3
|
+
export * from "./PageLayoutPagination/PageLayoutPagination";
|
|
4
|
+
export * from "./PageLayoutTabs/PageLayoutTabs";
|
|
5
|
+
export * from "./Popover/Popover";
|
|
6
|
+
export * from "./SectionWrapper/SectionWrapper";
|
|
7
|
+
export * from "./Sidebar/_index";
|
|
8
|
+
export * from "./Table/_index";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
position: fixed;
|
|
3
|
+
width: 100%;
|
|
4
|
+
height: 100vh;
|
|
5
|
+
top: 0;
|
|
6
|
+
left: 0;
|
|
7
|
+
z-index: -1;
|
|
8
|
+
isolation: isolate;
|
|
9
|
+
pointer-events: none;
|
|
10
|
+
}
|
|
11
|
+
.main {
|
|
12
|
+
/* --cursor-mouse-down-size: 11em; */
|
|
13
|
+
background: radial-gradient(
|
|
14
|
+
41.94% 82.95% at 50% 50%,
|
|
15
|
+
color-mix(in srgb, var(--color-primary) 50%, transparent) 0%,
|
|
16
|
+
color-mix(in srgb, var(--color-secondary) 50%, transparent) 100%
|
|
17
|
+
);
|
|
18
|
+
filter: blur(90px);
|
|
19
|
+
|
|
20
|
+
position: absolute;
|
|
21
|
+
transition-property: width, height, filter;
|
|
22
|
+
transition-duration: var(--transition-time);
|
|
23
|
+
transition-timing-function: var(--transition-tf);
|
|
24
|
+
translate: -50% -50%;
|
|
25
|
+
border-radius: 50%;
|
|
26
|
+
}
|
|
27
|
+
/* .mouseDowned {
|
|
28
|
+
filter: blur(50px);
|
|
29
|
+
width: calc(1.618 * var(--cursor-mouse-down-size));
|
|
30
|
+
height: var(--cursor-mouse-down-size);
|
|
31
|
+
} */
|