@spatika/react 1.1.0 → 1.2.0
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/AGENTS.md +71 -0
- package/README.md +4 -3
- package/dist/composites/Card.d.ts +7 -1
- package/dist/composites/Card.d.ts.map +1 -1
- package/dist/composites/Card.js +10 -0
- package/dist/composites/ChartDataGrid.d.ts +33 -0
- package/dist/composites/ChartDataGrid.d.ts.map +1 -0
- package/dist/composites/ChartDataGrid.js +77 -0
- package/dist/composites/CoverHero.d.ts +1 -1
- package/dist/composites/CoverHero.d.ts.map +1 -1
- package/dist/composites/CoverHero.js +2 -2
- package/dist/composites/EntityMediaCard.d.ts +1 -1
- package/dist/composites/EntityMediaCard.d.ts.map +1 -1
- package/dist/composites/EntityMediaCard.js +12 -4
- package/dist/composites/EventCalendar.d.ts +35 -0
- package/dist/composites/EventCalendar.d.ts.map +1 -0
- package/dist/composites/EventCalendar.js +339 -0
- package/dist/composites/EventEditor.d.ts +22 -0
- package/dist/composites/EventEditor.d.ts.map +1 -0
- package/dist/composites/EventEditor.js +86 -0
- package/dist/composites/EventTimeline.d.ts +24 -0
- package/dist/composites/EventTimeline.d.ts.map +1 -0
- package/dist/composites/EventTimeline.js +184 -0
- package/dist/composites/MapChart.d.ts +24 -0
- package/dist/composites/MapChart.d.ts.map +1 -0
- package/dist/composites/MapChart.js +58 -0
- package/dist/composites/ProfileHero.d.ts +6 -4
- package/dist/composites/ProfileHero.d.ts.map +1 -1
- package/dist/composites/ProfileHero.js +8 -8
- package/dist/composites/ScatterWebGL.d.ts +22 -0
- package/dist/composites/ScatterWebGL.d.ts.map +1 -0
- package/dist/composites/ScatterWebGL.js +45 -0
- package/dist/composites/SiteFooter.d.ts +1 -1
- package/dist/composites/SiteFooter.js +1 -1
- package/dist/composites/SiteNav.d.ts +7 -2
- package/dist/composites/SiteNav.d.ts.map +1 -1
- package/dist/composites/SiteNav.js +9 -5
- package/dist/composites/StatCard.d.ts +2 -1
- package/dist/composites/StatCard.d.ts.map +1 -1
- package/dist/composites/StatCard.js +3 -3
- package/dist/composites/StatusDot.d.ts +1 -1
- package/dist/composites/Wordmark.d.ts +2 -2
- package/dist/composites/Wordmark.d.ts.map +1 -1
- package/dist/composites/cartesian-charts.d.ts +138 -0
- package/dist/composites/cartesian-charts.d.ts.map +1 -0
- package/dist/composites/cartesian-charts.js +401 -0
- package/dist/composites/chart-composition.d.ts +139 -0
- package/dist/composites/chart-composition.d.ts.map +1 -0
- package/dist/composites/chart-composition.js +486 -0
- package/dist/composites/chart-ui.d.ts +83 -0
- package/dist/composites/chart-ui.d.ts.map +1 -0
- package/dist/composites/chart-ui.js +108 -0
- package/dist/composites/charts-3d.d.ts +37 -0
- package/dist/composites/charts-3d.d.ts.map +1 -0
- package/dist/composites/charts-3d.js +121 -0
- package/dist/composites/flow-charts.d.ts +89 -0
- package/dist/composites/flow-charts.d.ts.map +1 -0
- package/dist/composites/flow-charts.js +186 -0
- package/dist/composites/radial-charts.d.ts +109 -0
- package/dist/composites/radial-charts.d.ts.map +1 -0
- package/dist/composites/radial-charts.js +176 -0
- package/dist/composites/scheduler-ui.d.ts +43 -0
- package/dist/composites/scheduler-ui.d.ts.map +1 -0
- package/dist/composites/scheduler-ui.js +71 -0
- package/dist/index.d.ts +67 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +57 -7
- package/dist/lib/chart-export.d.ts +10 -0
- package/dist/lib/chart-export.d.ts.map +1 -0
- package/dist/lib/chart-export.js +102 -0
- package/dist/lib/charts.d.ts +235 -0
- package/dist/lib/charts.d.ts.map +1 -0
- package/dist/lib/charts.js +744 -0
- package/dist/lib/geo.d.ts +39 -0
- package/dist/lib/geo.d.ts.map +1 -0
- package/dist/lib/geo.js +101 -0
- package/dist/lib/iso.d.ts +21 -0
- package/dist/lib/iso.d.ts.map +1 -0
- package/dist/lib/iso.js +56 -0
- package/dist/lib/rrule.d.ts +20 -0
- package/dist/lib/rrule.d.ts.map +1 -0
- package/dist/lib/rrule.js +236 -0
- package/dist/lib/scheduler.d.ts +152 -0
- package/dist/lib/scheduler.d.ts.map +1 -0
- package/dist/lib/scheduler.js +501 -0
- package/dist/lib/webgl-scatter.d.ts +44 -0
- package/dist/lib/webgl-scatter.d.ts.map +1 -0
- package/dist/lib/webgl-scatter.js +185 -0
- package/dist/primitives/Accordion.d.ts +44 -20
- package/dist/primitives/Accordion.d.ts.map +1 -1
- package/dist/primitives/Accordion.js +82 -22
- package/dist/primitives/AppBar.d.ts +13 -0
- package/dist/primitives/AppBar.d.ts.map +1 -0
- package/dist/primitives/AppBar.js +27 -0
- package/dist/primitives/Autocomplete.d.ts +28 -0
- package/dist/primitives/Autocomplete.d.ts.map +1 -0
- package/dist/primitives/Autocomplete.js +118 -0
- package/dist/primitives/Avatar.d.ts +13 -1
- package/dist/primitives/Avatar.d.ts.map +1 -1
- package/dist/primitives/Avatar.js +17 -2
- package/dist/primitives/Backdrop.d.ts +17 -0
- package/dist/primitives/Backdrop.d.ts.map +1 -0
- package/dist/primitives/Backdrop.js +17 -0
- package/dist/primitives/BottomNavigation.d.ts +23 -0
- package/dist/primitives/BottomNavigation.d.ts.map +1 -0
- package/dist/primitives/BottomNavigation.js +30 -0
- package/dist/primitives/ButtonBase.d.ts +14 -0
- package/dist/primitives/ButtonBase.d.ts.map +1 -0
- package/dist/primitives/ButtonBase.js +13 -0
- package/dist/primitives/ButtonGroup.d.ts +25 -0
- package/dist/primitives/ButtonGroup.d.ts.map +1 -0
- package/dist/primitives/ButtonGroup.js +44 -0
- package/dist/primitives/CircularProgress.d.ts +16 -0
- package/dist/primitives/CircularProgress.d.ts.map +1 -0
- package/dist/primitives/CircularProgress.js +17 -0
- package/dist/primitives/ClickAwayListener.d.ts +16 -0
- package/dist/primitives/ClickAwayListener.d.ts.map +1 -0
- package/dist/primitives/ClickAwayListener.js +33 -0
- package/dist/primitives/Collapse.d.ts +19 -0
- package/dist/primitives/Collapse.d.ts.map +1 -0
- package/dist/primitives/Collapse.js +14 -0
- package/dist/primitives/Container.d.ts +13 -0
- package/dist/primitives/Container.d.ts.map +1 -0
- package/dist/primitives/Container.js +26 -0
- package/dist/primitives/Fab.d.ts +22 -0
- package/dist/primitives/Fab.d.ts.map +1 -0
- package/dist/primitives/Fab.js +42 -0
- package/dist/primitives/FormControl.d.ts +27 -0
- package/dist/primitives/FormControl.d.ts.map +1 -0
- package/dist/primitives/FormControl.js +24 -0
- package/dist/primitives/FormControlLabel.d.ts +18 -0
- package/dist/primitives/FormControlLabel.d.ts.map +1 -0
- package/dist/primitives/FormControlLabel.js +19 -0
- package/dist/primitives/Grid.d.ts +32 -0
- package/dist/primitives/Grid.d.ts.map +1 -0
- package/dist/primitives/Grid.js +50 -0
- package/dist/primitives/IconButton.d.ts +22 -0
- package/dist/primitives/IconButton.d.ts.map +1 -0
- package/dist/primitives/IconButton.js +39 -0
- package/dist/primitives/ImageList.d.ts +31 -0
- package/dist/primitives/ImageList.d.ts.map +1 -0
- package/dist/primitives/ImageList.js +33 -0
- package/dist/primitives/InputAdornment.d.ts +28 -0
- package/dist/primitives/InputAdornment.d.ts.map +1 -0
- package/dist/primitives/InputAdornment.js +13 -0
- package/dist/primitives/Link.d.ts +17 -0
- package/dist/primitives/Link.d.ts.map +1 -0
- package/dist/primitives/Link.js +29 -0
- package/dist/primitives/List.d.ts +41 -0
- package/dist/primitives/List.d.ts.map +1 -0
- package/dist/primitives/List.js +22 -0
- package/dist/primitives/Masonry.d.ts +14 -0
- package/dist/primitives/Masonry.d.ts.map +1 -0
- package/dist/primitives/Masonry.js +15 -0
- package/dist/primitives/Menu.d.ts +28 -0
- package/dist/primitives/Menu.d.ts.map +1 -0
- package/dist/primitives/Menu.js +54 -0
- package/dist/primitives/MobileStepper.d.ts +15 -0
- package/dist/primitives/MobileStepper.d.ts.map +1 -0
- package/dist/primitives/MobileStepper.js +8 -0
- package/dist/primitives/Modal.d.ts +18 -0
- package/dist/primitives/Modal.d.ts.map +1 -0
- package/dist/primitives/Modal.js +29 -0
- package/dist/primitives/NoSsr.d.ts +15 -0
- package/dist/primitives/NoSsr.d.ts.map +1 -0
- package/dist/primitives/NoSsr.js +19 -0
- package/dist/primitives/Paper.d.ts +22 -0
- package/dist/primitives/Paper.d.ts.map +1 -0
- package/dist/primitives/Paper.js +33 -0
- package/dist/primitives/Progress.d.ts +6 -2
- package/dist/primitives/Progress.d.ts.map +1 -1
- package/dist/primitives/Progress.js +8 -3
- package/dist/primitives/Snackbar.d.ts +23 -0
- package/dist/primitives/Snackbar.d.ts.map +1 -0
- package/dist/primitives/Snackbar.js +33 -0
- package/dist/primitives/SpeedDial.d.ts +26 -0
- package/dist/primitives/SpeedDial.d.ts.map +1 -0
- package/dist/primitives/SpeedDial.js +26 -0
- package/dist/primitives/Stack.d.ts +15 -0
- package/dist/primitives/Stack.d.ts.map +1 -0
- package/dist/primitives/Stack.js +45 -0
- package/dist/primitives/StarRating.d.ts +19 -1
- package/dist/primitives/StarRating.d.ts.map +1 -1
- package/dist/primitives/StarRating.js +36 -6
- package/dist/primitives/Stepper.d.ts +36 -0
- package/dist/primitives/Stepper.d.ts.map +1 -0
- package/dist/primitives/Stepper.js +53 -0
- package/dist/primitives/TablePagination.d.ts +16 -0
- package/dist/primitives/TablePagination.d.ts.map +1 -0
- package/dist/primitives/TablePagination.js +12 -0
- package/dist/primitives/TextField.d.ts +24 -0
- package/dist/primitives/TextField.d.ts.map +1 -0
- package/dist/primitives/TextField.js +19 -0
- package/dist/primitives/TextareaAutosize.d.ts +11 -0
- package/dist/primitives/TextareaAutosize.d.ts.map +1 -0
- package/dist/primitives/TextareaAutosize.js +26 -0
- package/dist/primitives/ToggleButton.d.ts +28 -0
- package/dist/primitives/ToggleButton.d.ts.map +1 -0
- package/dist/primitives/ToggleButton.js +56 -0
- package/dist/primitives/TransferList.d.ts +21 -0
- package/dist/primitives/TransferList.d.ts.map +1 -0
- package/dist/primitives/TransferList.js +40 -0
- package/dist/primitives/Typography.d.ts +23 -0
- package/dist/primitives/Typography.d.ts.map +1 -0
- package/dist/primitives/Typography.js +69 -0
- package/dist/primitives/transitions.d.ts +29 -0
- package/dist/primitives/transitions.d.ts.map +1 -0
- package/dist/primitives/transitions.js +52 -0
- package/package.json +28 -4
- package/skills/spatika-ui/SKILL.md +96 -0
- package/src/composites/Card.tsx +51 -1
- package/src/composites/ChartDataGrid.test.tsx +57 -0
- package/src/composites/ChartDataGrid.tsx +196 -0
- package/src/composites/CoverHero.test.tsx +38 -0
- package/src/composites/CoverHero.tsx +29 -22
- package/src/composites/EntityMediaCard.test.tsx +58 -0
- package/src/composites/EntityMediaCard.tsx +31 -13
- package/src/composites/EventCalendar.tsx +928 -0
- package/src/composites/EventEditor.tsx +268 -0
- package/src/composites/EventTimeline.tsx +357 -0
- package/src/composites/MapChart.test.tsx +39 -0
- package/src/composites/MapChart.tsx +130 -0
- package/src/composites/ProfileHero.test.tsx +33 -0
- package/src/composites/ProfileHero.tsx +96 -79
- package/src/composites/ScatterWebGL.test.tsx +25 -0
- package/src/composites/ScatterWebGL.tsx +79 -0
- package/src/composites/SiteFooter.test.tsx +37 -0
- package/src/composites/SiteFooter.tsx +1 -1
- package/src/composites/SiteNav.test.tsx +46 -0
- package/src/composites/SiteNav.tsx +14 -4
- package/src/composites/StatCard.test.tsx +30 -0
- package/src/composites/StatCard.tsx +4 -0
- package/src/composites/Wordmark.test.tsx +23 -0
- package/src/composites/Wordmark.tsx +2 -2
- package/src/composites/cartesian-charts.test.tsx +52 -0
- package/src/composites/cartesian-charts.tsx +1287 -0
- package/src/composites/chart-composition.test.tsx +53 -0
- package/src/composites/chart-composition.tsx +898 -0
- package/src/composites/chart-ui.tsx +328 -0
- package/src/composites/charts-3d.test.tsx +25 -0
- package/src/composites/charts-3d.tsx +318 -0
- package/src/composites/charts.test.tsx +254 -0
- package/src/composites/chrome.test.tsx +29 -0
- package/src/composites/flow-charts.test.tsx +79 -0
- package/src/composites/flow-charts.tsx +607 -0
- package/src/composites/marketing.test.tsx +38 -34
- package/src/composites/radial-charts.test.tsx +74 -0
- package/src/composites/radial-charts.tsx +617 -0
- package/src/composites/scheduler-ui.tsx +304 -0
- package/src/composites/scheduler.test.tsx +194 -0
- package/src/index.test.ts +22 -0
- package/src/index.ts +277 -5
- package/src/lib/chart-export.test.ts +20 -0
- package/src/lib/chart-export.ts +99 -0
- package/src/lib/charts-platform.test.ts +93 -0
- package/src/lib/charts.test.ts +132 -0
- package/src/lib/charts.ts +1021 -0
- package/src/lib/geo.test.ts +42 -0
- package/src/lib/geo.ts +138 -0
- package/src/lib/iso.test.ts +22 -0
- package/src/lib/iso.ts +102 -0
- package/src/lib/rrule.test.ts +53 -0
- package/src/lib/rrule.ts +281 -0
- package/src/lib/scheduler.test.ts +186 -0
- package/src/lib/scheduler.ts +688 -0
- package/src/lib/tokens.test.ts +2 -2
- package/src/lib/webgl-scatter.test.ts +36 -0
- package/src/lib/webgl-scatter.ts +230 -0
- package/src/primitives/Accordion.tsx +218 -63
- package/src/primitives/AppBar.tsx +39 -0
- package/src/primitives/Autocomplete.tsx +236 -0
- package/src/primitives/Avatar.tsx +52 -1
- package/src/primitives/Backdrop.tsx +48 -0
- package/src/primitives/BottomNavigation.tsx +102 -0
- package/src/primitives/ButtonBase.tsx +34 -0
- package/src/primitives/ButtonGroup.tsx +90 -0
- package/src/primitives/CircularProgress.tsx +104 -0
- package/src/primitives/ClickAwayListener.tsx +46 -0
- package/src/primitives/Collapse.tsx +39 -0
- package/src/primitives/Container.tsx +40 -0
- package/src/primitives/Fab.tsx +62 -0
- package/src/primitives/FormControl.tsx +90 -0
- package/src/primitives/FormControlLabel.tsx +51 -0
- package/src/primitives/Grid.tsx +101 -0
- package/src/primitives/IconButton.tsx +59 -0
- package/src/primitives/ImageList.test.tsx +48 -0
- package/src/primitives/ImageList.tsx +112 -0
- package/src/primitives/InputAdornment.tsx +86 -0
- package/src/primitives/Link.tsx +48 -0
- package/src/primitives/List.tsx +121 -0
- package/src/primitives/Masonry.tsx +32 -0
- package/src/primitives/Menu.tsx +112 -0
- package/src/primitives/MobileStepper.tsx +64 -0
- package/src/primitives/Modal.tsx +70 -0
- package/src/primitives/NoSsr.tsx +28 -0
- package/src/primitives/Paper.tsx +55 -0
- package/src/primitives/Progress.tsx +25 -7
- package/src/primitives/Snackbar.tsx +83 -0
- package/src/primitives/SpeedDial.tsx +110 -0
- package/src/primitives/Stack.tsx +88 -0
- package/src/primitives/StarRating.tsx +87 -28
- package/src/primitives/Stepper.tsx +184 -0
- package/src/primitives/TablePagination.tsx +58 -0
- package/src/primitives/TextField.tsx +105 -0
- package/src/primitives/TextareaAutosize.tsx +49 -0
- package/src/primitives/ToggleButton.tsx +148 -0
- package/src/primitives/TransferList.tsx +110 -0
- package/src/primitives/Typography.tsx +90 -0
- package/src/primitives/mui-more.test.tsx +76 -0
- package/src/primitives/mui.test.tsx +134 -0
- package/src/primitives/transitions.tsx +120 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { composeRefs } from "../lib/compose-refs";
|
|
3
|
+
|
|
4
|
+
export type ClickAwayListenerProps = {
|
|
5
|
+
onClickAway: (event: MouseEvent | TouchEvent) => void;
|
|
6
|
+
children: React.ReactElement;
|
|
7
|
+
mouseEvent?: "mousedown" | "mouseup" | "click" | false;
|
|
8
|
+
touchEvent?: "touchstart" | "touchend" | false;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Invokes `onClickAway` when a pointer event occurs outside the child.
|
|
13
|
+
*/
|
|
14
|
+
function ClickAwayListener({
|
|
15
|
+
onClickAway,
|
|
16
|
+
children,
|
|
17
|
+
mouseEvent = "mousedown",
|
|
18
|
+
touchEvent = "touchstart",
|
|
19
|
+
}: ClickAwayListenerProps) {
|
|
20
|
+
const ref = React.useRef<HTMLElement | null>(null);
|
|
21
|
+
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
const handler = (event: MouseEvent | TouchEvent) => {
|
|
24
|
+
const node = ref.current;
|
|
25
|
+
const target = event.target as Node | null;
|
|
26
|
+
if (!node || !target || node.contains(target)) return;
|
|
27
|
+
onClickAway(event);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (mouseEvent) document.addEventListener(mouseEvent, handler);
|
|
31
|
+
if (touchEvent) document.addEventListener(touchEvent, handler);
|
|
32
|
+
return () => {
|
|
33
|
+
if (mouseEvent) document.removeEventListener(mouseEvent, handler);
|
|
34
|
+
if (touchEvent) document.removeEventListener(touchEvent, handler);
|
|
35
|
+
};
|
|
36
|
+
}, [onClickAway, mouseEvent, touchEvent]);
|
|
37
|
+
|
|
38
|
+
const childRef = (children as { ref?: React.Ref<HTMLElement> }).ref;
|
|
39
|
+
|
|
40
|
+
return React.cloneElement(children, {
|
|
41
|
+
ref: composeRefs(ref, childRef),
|
|
42
|
+
} as Partial<unknown>);
|
|
43
|
+
}
|
|
44
|
+
ClickAwayListener.displayName = "ClickAwayListener";
|
|
45
|
+
|
|
46
|
+
export { ClickAwayListener };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../lib/cn";
|
|
3
|
+
|
|
4
|
+
export type CollapseProps = Omit<React.ComponentPropsWithoutRef<"div">, "in"> & {
|
|
5
|
+
/** When true, the child is expanded. */
|
|
6
|
+
in?: boolean;
|
|
7
|
+
/** Keep children mounted while collapsed. Defaults to true so height can animate. */
|
|
8
|
+
unmountOnExit?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Height collapse used by Accordion and other expandable regions.
|
|
13
|
+
* Animates `grid-template-rows` (transform-safe; no `transition-all`).
|
|
14
|
+
*/
|
|
15
|
+
const Collapse = React.forwardRef<HTMLDivElement, CollapseProps>(
|
|
16
|
+
({ in: open = false, unmountOnExit = false, className, children, ...props }, ref) => {
|
|
17
|
+
if (unmountOnExit && !open) return null;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
ref={ref}
|
|
22
|
+
data-slot="collapse"
|
|
23
|
+
data-state={open ? "open" : "closed"}
|
|
24
|
+
aria-hidden={!open}
|
|
25
|
+
className={cn(
|
|
26
|
+
"grid transition-[grid-template-rows] duration-300 ease-out",
|
|
27
|
+
open ? "grid-rows-[1fr]" : "grid-rows-[0fr]",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
<div className="min-h-0 overflow-hidden">{children}</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
Collapse.displayName = "Collapse";
|
|
38
|
+
|
|
39
|
+
export { Collapse };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "../lib/cn";
|
|
4
|
+
|
|
5
|
+
const containerVariants = cva("mx-auto w-full", {
|
|
6
|
+
variants: {
|
|
7
|
+
maxWidth: {
|
|
8
|
+
sm: "max-w-screen-sm",
|
|
9
|
+
md: "max-w-screen-md",
|
|
10
|
+
lg: "max-w-screen-lg",
|
|
11
|
+
xl: "max-w-screen-xl",
|
|
12
|
+
full: "max-w-none",
|
|
13
|
+
},
|
|
14
|
+
disableGutters: {
|
|
15
|
+
true: "px-0",
|
|
16
|
+
false: "px-4 sm:px-6",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
maxWidth: "lg",
|
|
21
|
+
disableGutters: false,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type ContainerProps = React.ComponentPropsWithoutRef<"div"> &
|
|
26
|
+
VariantProps<typeof containerVariants>;
|
|
27
|
+
|
|
28
|
+
const Container = React.forwardRef<HTMLDivElement, ContainerProps>(
|
|
29
|
+
({ className, maxWidth, disableGutters, ...props }, ref) => (
|
|
30
|
+
<div
|
|
31
|
+
ref={ref}
|
|
32
|
+
data-slot="container"
|
|
33
|
+
className={cn(containerVariants({ maxWidth, disableGutters }), className)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
),
|
|
37
|
+
);
|
|
38
|
+
Container.displayName = "Container";
|
|
39
|
+
|
|
40
|
+
export { Container, containerVariants };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { Slot } from "../lib/slot";
|
|
4
|
+
import { cn } from "../lib/cn";
|
|
5
|
+
|
|
6
|
+
const fabVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center gap-2 font-bold shadow-[0_10px_28px_color-mix(in_srgb,var(--foreground)_16%,transparent)] outline-none transition-[color,background-color,box-shadow,transform] duration-150 focus-visible:ring-ring/50 focus-visible:ring-[3px] active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50 [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
circular: "rounded-full",
|
|
12
|
+
extended: "rounded-full px-5",
|
|
13
|
+
},
|
|
14
|
+
color: {
|
|
15
|
+
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
16
|
+
glass: "glass border-border/50 text-foreground hover:border-primary/30 hover:text-primary",
|
|
17
|
+
default: "bg-card text-foreground border border-border/50 hover:bg-muted/60",
|
|
18
|
+
},
|
|
19
|
+
size: {
|
|
20
|
+
sm: "size-10 text-sm",
|
|
21
|
+
md: "size-14 text-base",
|
|
22
|
+
lg: "size-16 text-lg",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
compoundVariants: [
|
|
26
|
+
{ variant: "extended", size: "sm", className: "h-10 w-auto min-w-10" },
|
|
27
|
+
{ variant: "extended", size: "md", className: "h-12 w-auto min-w-12 size-auto" },
|
|
28
|
+
{ variant: "extended", size: "lg", className: "h-14 w-auto min-w-14 size-auto" },
|
|
29
|
+
],
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
variant: "circular",
|
|
32
|
+
color: "primary",
|
|
33
|
+
size: "md",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export type FabProps = Omit<React.ComponentPropsWithoutRef<"button">, "color"> &
|
|
39
|
+
VariantProps<typeof fabVariants> & {
|
|
40
|
+
asChild?: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Floating action button. Use `variant="extended"` for a labeled FAB.
|
|
45
|
+
*/
|
|
46
|
+
const Fab = React.forwardRef<HTMLButtonElement, FabProps>(
|
|
47
|
+
({ className, variant, color, size, asChild = false, ...props }, ref) => {
|
|
48
|
+
const Comp = asChild ? Slot : "button";
|
|
49
|
+
return (
|
|
50
|
+
<Comp
|
|
51
|
+
ref={ref}
|
|
52
|
+
type={asChild ? undefined : "button"}
|
|
53
|
+
data-slot="fab"
|
|
54
|
+
className={cn(fabVariants({ variant, color, size }), className)}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
Fab.displayName = "Fab";
|
|
61
|
+
|
|
62
|
+
export { Fab, fabVariants };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../lib/cn";
|
|
3
|
+
import { Label } from "./Label";
|
|
4
|
+
|
|
5
|
+
type FormControlContextValue = {
|
|
6
|
+
error?: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
required?: boolean;
|
|
9
|
+
fullWidth?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const FormControlContext = React.createContext<FormControlContextValue | null>(null);
|
|
13
|
+
|
|
14
|
+
export type FormControlProps = React.ComponentPropsWithoutRef<"div"> & {
|
|
15
|
+
error?: boolean;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
required?: boolean;
|
|
18
|
+
fullWidth?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function FormControl({
|
|
22
|
+
error,
|
|
23
|
+
disabled,
|
|
24
|
+
required,
|
|
25
|
+
fullWidth,
|
|
26
|
+
className,
|
|
27
|
+
...props
|
|
28
|
+
}: FormControlProps) {
|
|
29
|
+
return (
|
|
30
|
+
<FormControlContext.Provider value={{ error, disabled, required, fullWidth }}>
|
|
31
|
+
<div
|
|
32
|
+
data-slot="form-control"
|
|
33
|
+
data-error={error ? "" : undefined}
|
|
34
|
+
data-disabled={disabled ? "" : undefined}
|
|
35
|
+
className={cn("flex flex-col gap-1.5", fullWidth && "w-full", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
</FormControlContext.Provider>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
FormControl.displayName = "FormControl";
|
|
42
|
+
|
|
43
|
+
function FormLabel({ className, children, ...props }: React.ComponentPropsWithoutRef<"label">) {
|
|
44
|
+
const ctx = React.useContext(FormControlContext);
|
|
45
|
+
return (
|
|
46
|
+
<Label
|
|
47
|
+
data-slot="form-label"
|
|
48
|
+
className={cn(ctx?.error && "text-destructive", className)}
|
|
49
|
+
{...props}
|
|
50
|
+
>
|
|
51
|
+
{children}
|
|
52
|
+
{ctx?.required ? <span className="text-destructive">*</span> : null}
|
|
53
|
+
</Label>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
FormLabel.displayName = "FormLabel";
|
|
57
|
+
|
|
58
|
+
function FormHelperText({ className, ...props }: React.ComponentPropsWithoutRef<"p">) {
|
|
59
|
+
const ctx = React.useContext(FormControlContext);
|
|
60
|
+
return (
|
|
61
|
+
<p
|
|
62
|
+
data-slot="form-helper-text"
|
|
63
|
+
className={cn(
|
|
64
|
+
"text-xs font-medium",
|
|
65
|
+
ctx?.error ? "text-destructive" : "text-muted-foreground",
|
|
66
|
+
className,
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
FormHelperText.displayName = "FormHelperText";
|
|
73
|
+
|
|
74
|
+
function FormGroup({
|
|
75
|
+
row,
|
|
76
|
+
className,
|
|
77
|
+
...props
|
|
78
|
+
}: React.ComponentPropsWithoutRef<"div"> & { row?: boolean }) {
|
|
79
|
+
return (
|
|
80
|
+
<div
|
|
81
|
+
data-slot="form-group"
|
|
82
|
+
role="group"
|
|
83
|
+
className={cn("flex", row ? "flex-row flex-wrap gap-4" : "flex-col gap-2", className)}
|
|
84
|
+
{...props}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
FormGroup.displayName = "FormGroup";
|
|
89
|
+
|
|
90
|
+
export { FormControl, FormLabel, FormHelperText, FormGroup };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../lib/cn";
|
|
3
|
+
import { Label } from "./Label";
|
|
4
|
+
|
|
5
|
+
export type FormControlLabelProps = Omit<React.ComponentPropsWithoutRef<"label">, "control"> & {
|
|
6
|
+
control: React.ReactElement<{ disabled?: boolean }>;
|
|
7
|
+
label: React.ReactNode;
|
|
8
|
+
labelPlacement?: "end" | "start" | "top" | "bottom";
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const placementClass = {
|
|
13
|
+
end: "flex-row",
|
|
14
|
+
start: "flex-row-reverse",
|
|
15
|
+
top: "flex-col-reverse items-center",
|
|
16
|
+
bottom: "flex-col items-center",
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Pairs a checkbox, radio, or switch with its visible label.
|
|
21
|
+
*/
|
|
22
|
+
function FormControlLabel({
|
|
23
|
+
control,
|
|
24
|
+
label,
|
|
25
|
+
labelPlacement = "end",
|
|
26
|
+
disabled,
|
|
27
|
+
className,
|
|
28
|
+
...props
|
|
29
|
+
}: FormControlLabelProps) {
|
|
30
|
+
const controlDisabled = disabled || control.props.disabled;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Label
|
|
34
|
+
data-slot="form-control-label"
|
|
35
|
+
data-disabled={controlDisabled ? "" : undefined}
|
|
36
|
+
className={cn(
|
|
37
|
+
"inline-flex w-fit cursor-pointer items-center gap-2",
|
|
38
|
+
placementClass[labelPlacement],
|
|
39
|
+
controlDisabled && "cursor-not-allowed opacity-50",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
{React.cloneElement(control, { disabled: controlDisabled })}
|
|
45
|
+
<span className="text-sm font-medium">{label}</span>
|
|
46
|
+
</Label>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
FormControlLabel.displayName = "FormControlLabel";
|
|
50
|
+
|
|
51
|
+
export { FormControlLabel };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Slot } from "../lib/slot";
|
|
3
|
+
import { cn } from "../lib/cn";
|
|
4
|
+
|
|
5
|
+
export type BoxProps = React.ComponentPropsWithoutRef<"div"> & {
|
|
6
|
+
component?: React.ElementType;
|
|
7
|
+
asChild?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generic layout primitive. Prefer Stack / Grid / Paper for structured layouts.
|
|
12
|
+
*/
|
|
13
|
+
const Box = React.forwardRef<HTMLDivElement, BoxProps>(
|
|
14
|
+
({ className, component = "div", asChild = false, ...props }, ref) => {
|
|
15
|
+
const Comp = asChild ? Slot : component;
|
|
16
|
+
return <Comp ref={ref} data-slot="box" className={cn(className)} {...props} />;
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
Box.displayName = "Box";
|
|
20
|
+
|
|
21
|
+
const gapClass: Record<number, string> = {
|
|
22
|
+
0: "gap-0",
|
|
23
|
+
1: "gap-1",
|
|
24
|
+
2: "gap-2",
|
|
25
|
+
3: "gap-3",
|
|
26
|
+
4: "gap-4",
|
|
27
|
+
5: "gap-5",
|
|
28
|
+
6: "gap-6",
|
|
29
|
+
8: "gap-8",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const spanClass: Record<number, string> = {
|
|
33
|
+
1: "col-span-1",
|
|
34
|
+
2: "col-span-2",
|
|
35
|
+
3: "col-span-3",
|
|
36
|
+
4: "col-span-4",
|
|
37
|
+
5: "col-span-5",
|
|
38
|
+
6: "col-span-6",
|
|
39
|
+
7: "col-span-7",
|
|
40
|
+
8: "col-span-8",
|
|
41
|
+
9: "col-span-9",
|
|
42
|
+
10: "col-span-10",
|
|
43
|
+
11: "col-span-11",
|
|
44
|
+
12: "col-span-12",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type GridProps = React.ComponentPropsWithoutRef<"div"> & {
|
|
48
|
+
container?: boolean;
|
|
49
|
+
item?: boolean;
|
|
50
|
+
spacing?: number;
|
|
51
|
+
columns?: 12;
|
|
52
|
+
xs?: number | "auto";
|
|
53
|
+
sm?: number | "auto";
|
|
54
|
+
md?: number | "auto";
|
|
55
|
+
lg?: number | "auto";
|
|
56
|
+
size?: number | "grow";
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function spanFor(value?: number | "auto" | "grow") {
|
|
60
|
+
if (value == null || value === "auto" || value === "grow") return undefined;
|
|
61
|
+
return spanClass[value] ?? "col-span-12";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 12-column CSS grid. Use `container` on the parent and breakpoint spans on items.
|
|
66
|
+
*/
|
|
67
|
+
function Grid({
|
|
68
|
+
container,
|
|
69
|
+
item: _item,
|
|
70
|
+
spacing = 2,
|
|
71
|
+
columns: _columns = 12,
|
|
72
|
+
xs,
|
|
73
|
+
sm,
|
|
74
|
+
md,
|
|
75
|
+
lg,
|
|
76
|
+
size,
|
|
77
|
+
className,
|
|
78
|
+
...props
|
|
79
|
+
}: GridProps) {
|
|
80
|
+
const span = spanFor(typeof size === "number" ? size : xs);
|
|
81
|
+
return (
|
|
82
|
+
<div
|
|
83
|
+
data-slot="grid"
|
|
84
|
+
data-container={container ? "" : undefined}
|
|
85
|
+
data-item={_item || !container ? "" : undefined}
|
|
86
|
+
className={cn(
|
|
87
|
+
container && "grid grid-cols-12",
|
|
88
|
+
container && (gapClass[spacing] ?? "gap-2"),
|
|
89
|
+
!container && (span ?? (size === "grow" ? "col-span-12" : "col-span-12")),
|
|
90
|
+
sm && typeof sm === "number" && spanClass[sm]?.replace("col-span", "sm:col-span"),
|
|
91
|
+
md && typeof md === "number" && spanClass[md]?.replace("col-span", "md:col-span"),
|
|
92
|
+
lg && typeof lg === "number" && spanClass[lg]?.replace("col-span", "lg:col-span"),
|
|
93
|
+
className,
|
|
94
|
+
)}
|
|
95
|
+
{...props}
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
Grid.displayName = "Grid";
|
|
100
|
+
|
|
101
|
+
export { Box, Grid };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { Slot } from "../lib/slot";
|
|
4
|
+
import { cn } from "../lib/cn";
|
|
5
|
+
|
|
6
|
+
const iconButtonVariants = cva(
|
|
7
|
+
"inline-flex shrink-0 items-center justify-center outline-none transition-[color,background-color,border-color,transform] duration-150 focus-visible:ring-ring/50 focus-visible:ring-[3px] active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50 [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
ghost: "text-muted-foreground hover:bg-muted/40 hover:text-foreground",
|
|
12
|
+
outline: "border border-border/60 bg-background/80 text-foreground hover:bg-muted/40",
|
|
13
|
+
glass: "glass border-border/50 text-foreground hover:border-primary/30 hover:text-primary",
|
|
14
|
+
filled: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
15
|
+
},
|
|
16
|
+
size: {
|
|
17
|
+
sm: "size-8 rounded-lg",
|
|
18
|
+
md: "size-10 rounded-xl",
|
|
19
|
+
lg: "size-12 rounded-xl",
|
|
20
|
+
touch: "size-11 rounded-xl",
|
|
21
|
+
},
|
|
22
|
+
shape: {
|
|
23
|
+
rounded: "",
|
|
24
|
+
circular: "rounded-full",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
variant: "ghost",
|
|
29
|
+
size: "md",
|
|
30
|
+
shape: "rounded",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export type IconButtonProps = React.ComponentPropsWithoutRef<"button"> &
|
|
36
|
+
VariantProps<typeof iconButtonVariants> & {
|
|
37
|
+
asChild?: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Compact icon-only control. Prefer {@link HeaderIconButton} inside app chrome.
|
|
42
|
+
*/
|
|
43
|
+
const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(
|
|
44
|
+
({ className, variant, size, shape, asChild = false, ...props }, ref) => {
|
|
45
|
+
const Comp = asChild ? Slot : "button";
|
|
46
|
+
return (
|
|
47
|
+
<Comp
|
|
48
|
+
ref={ref}
|
|
49
|
+
type={asChild ? undefined : "button"}
|
|
50
|
+
data-slot="icon-button"
|
|
51
|
+
className={cn(iconButtonVariants({ variant, size, shape }), className)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
IconButton.displayName = "IconButton";
|
|
58
|
+
|
|
59
|
+
export { IconButton, iconButtonVariants };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { ImageList, ImageListItem, ImageListItemBar } from "./ImageList";
|
|
4
|
+
|
|
5
|
+
describe("ImageList", () => {
|
|
6
|
+
it("lays out items in a full-width grid with overlay titles", () => {
|
|
7
|
+
const { container } = render(
|
|
8
|
+
<ImageList cols={3} rowHeight={148} gap={10}>
|
|
9
|
+
<ImageListItem>
|
|
10
|
+
<img src="/coast.jpg" alt="Coast" />
|
|
11
|
+
<ImageListItemBar title="Coast" subtitle="Pacific" />
|
|
12
|
+
</ImageListItem>
|
|
13
|
+
<ImageListItem>
|
|
14
|
+
<img src="/dawn.jpg" alt="Dawn" />
|
|
15
|
+
<ImageListItemBar title="Dawn" />
|
|
16
|
+
</ImageListItem>
|
|
17
|
+
</ImageList>,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const list = container.querySelector("[data-slot='image-list']");
|
|
21
|
+
expect(list).toHaveAttribute("data-variant", "standard");
|
|
22
|
+
expect(list).toHaveClass("w-full");
|
|
23
|
+
expect(list).toHaveStyle({
|
|
24
|
+
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
|
|
25
|
+
gridAutoRows: "148px",
|
|
26
|
+
});
|
|
27
|
+
expect(screen.getByText("Coast")).toBeInTheDocument();
|
|
28
|
+
expect(screen.getByText("Pacific")).toBeInTheDocument();
|
|
29
|
+
expect(screen.getByRole("img", { name: "Coast" })).toBeInTheDocument();
|
|
30
|
+
const item = container.querySelector("[data-slot='image-list-item']");
|
|
31
|
+
expect(item).not.toHaveStyle({ marginBottom: "8px" });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("switches to masonry columns without a css grid", () => {
|
|
35
|
+
const { container } = render(
|
|
36
|
+
<ImageList variant="masonry" cols={2} gap={12}>
|
|
37
|
+
<ImageListItem>
|
|
38
|
+
<img src="/dusk.jpg" alt="Dusk" />
|
|
39
|
+
</ImageListItem>
|
|
40
|
+
</ImageList>,
|
|
41
|
+
);
|
|
42
|
+
const list = container.querySelector("[data-slot='image-list']");
|
|
43
|
+
expect(list).toHaveAttribute("data-variant", "masonry");
|
|
44
|
+
expect(list).toHaveClass("block");
|
|
45
|
+
expect(list).not.toHaveClass("grid");
|
|
46
|
+
expect(list).toHaveStyle({ columnCount: "2" });
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../lib/cn";
|
|
3
|
+
|
|
4
|
+
export type ImageListProps = React.ComponentPropsWithoutRef<"ul"> & {
|
|
5
|
+
cols?: number;
|
|
6
|
+
gap?: number;
|
|
7
|
+
variant?: "standard" | "quilted" | "masonry";
|
|
8
|
+
rowHeight?: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function ImageList({
|
|
12
|
+
cols = 2,
|
|
13
|
+
gap = 8,
|
|
14
|
+
variant = "standard",
|
|
15
|
+
rowHeight = 180,
|
|
16
|
+
className,
|
|
17
|
+
style,
|
|
18
|
+
children,
|
|
19
|
+
...props
|
|
20
|
+
}: ImageListProps) {
|
|
21
|
+
return (
|
|
22
|
+
<ul
|
|
23
|
+
data-slot="image-list"
|
|
24
|
+
data-variant={variant}
|
|
25
|
+
className={cn(
|
|
26
|
+
"m-0 w-full list-none p-0",
|
|
27
|
+
variant === "masonry"
|
|
28
|
+
? "block [&>[data-slot=image-list-item]]:mb-2 [&>[data-slot=image-list-item]]:h-auto"
|
|
29
|
+
: "grid",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
style={{
|
|
33
|
+
gap,
|
|
34
|
+
...(variant === "masonry"
|
|
35
|
+
? { columnCount: cols, columnGap: gap }
|
|
36
|
+
: {
|
|
37
|
+
gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
|
|
38
|
+
gridAutoRows: rowHeight,
|
|
39
|
+
}),
|
|
40
|
+
...style,
|
|
41
|
+
}}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
{children}
|
|
45
|
+
</ul>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
ImageList.displayName = "ImageList";
|
|
49
|
+
|
|
50
|
+
export type ImageListItemProps = React.ComponentPropsWithoutRef<"li"> & {
|
|
51
|
+
cols?: number;
|
|
52
|
+
rows?: number;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function ImageListItem({ cols = 1, rows = 1, className, style, ...props }: ImageListItemProps) {
|
|
56
|
+
return (
|
|
57
|
+
<li
|
|
58
|
+
data-slot="image-list-item"
|
|
59
|
+
className={cn(
|
|
60
|
+
"relative h-full min-h-0 overflow-hidden rounded-xl [&_img]:absolute [&_img]:inset-0 [&_img]:size-full [&_img]:object-cover",
|
|
61
|
+
className,
|
|
62
|
+
)}
|
|
63
|
+
style={{
|
|
64
|
+
gridColumn: `span ${cols}`,
|
|
65
|
+
gridRow: `span ${rows}`,
|
|
66
|
+
breakInside: "avoid",
|
|
67
|
+
...style,
|
|
68
|
+
}}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
ImageListItem.displayName = "ImageListItem";
|
|
74
|
+
|
|
75
|
+
export type ImageListItemBarProps = React.ComponentPropsWithoutRef<"div"> & {
|
|
76
|
+
title?: React.ReactNode;
|
|
77
|
+
subtitle?: React.ReactNode;
|
|
78
|
+
actionIcon?: React.ReactNode;
|
|
79
|
+
position?: "bottom" | "top";
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
function ImageListItemBar({
|
|
83
|
+
title,
|
|
84
|
+
subtitle,
|
|
85
|
+
actionIcon,
|
|
86
|
+
position = "bottom",
|
|
87
|
+
className,
|
|
88
|
+
...props
|
|
89
|
+
}: ImageListItemBarProps) {
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
data-slot="image-list-item-bar"
|
|
93
|
+
className={cn(
|
|
94
|
+
"absolute inset-x-0 z-10 flex items-end justify-between gap-2 p-3 text-white",
|
|
95
|
+
position === "top"
|
|
96
|
+
? "top-0 bg-gradient-to-b from-black/70 to-transparent"
|
|
97
|
+
: "bottom-0 bg-gradient-to-t from-black/70 to-transparent",
|
|
98
|
+
className,
|
|
99
|
+
)}
|
|
100
|
+
{...props}
|
|
101
|
+
>
|
|
102
|
+
<div className="min-w-0">
|
|
103
|
+
<div className="truncate text-sm font-bold">{title}</div>
|
|
104
|
+
{subtitle ? <div className="truncate text-xs opacity-80">{subtitle}</div> : null}
|
|
105
|
+
</div>
|
|
106
|
+
{actionIcon}
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
ImageListItemBar.displayName = "ImageListItemBar";
|
|
111
|
+
|
|
112
|
+
export { ImageList, ImageListItem, ImageListItemBar };
|