@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.
Files changed (140) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +71 -0
  3. package/eslint.config.js +21 -0
  4. package/package.json +66 -0
  5. package/rollup.config.js +34 -0
  6. package/src/Cards/Card/Card.module.css +27 -0
  7. package/src/Cards/Card/Card.tsx +105 -0
  8. package/src/Cards/CollectionItemCard/CollectionItemCard.module.css +84 -0
  9. package/src/Cards/CollectionItemCard/CollectionItemCard.tsx +170 -0
  10. package/src/Cards/CollectionItemCard/CollectionItemCardActions.tsx +85 -0
  11. package/src/Cards/CollectionItemCard/_index.ts +2 -0
  12. package/src/Cards/GlassCard/GlassCard.module.css +71 -0
  13. package/src/Cards/GlassCard/GlassCard.tsx +80 -0
  14. package/src/Cards/_index.ts +3 -0
  15. package/src/Display/Accordion/Accordion.module.css +69 -0
  16. package/src/Display/Accordion/Accordion.tsx +61 -0
  17. package/src/Display/Accordion/AccordionItem.tsx +135 -0
  18. package/src/Display/Accordion/_index.ts +2 -0
  19. package/src/Display/Chip/Chip.module.css +64 -0
  20. package/src/Display/Chip/Chip.tsx +105 -0
  21. package/src/Display/IdentityDisplay/IdentityDisplay.module.css +95 -0
  22. package/src/Display/IdentityDisplay/IdentityDisplay.tsx +119 -0
  23. package/src/Display/InputErrors/InputErrors.module.css +6 -0
  24. package/src/Display/InputErrors/InputErrors.tsx +52 -0
  25. package/src/Display/Kbd/Kbd.module.css +29 -0
  26. package/src/Display/Kbd/Kbd.tsx +39 -0
  27. package/src/Display/Kbd/_index.ts +2 -0
  28. package/src/Display/Kbd/buttonList.tsx +246 -0
  29. package/src/Display/LabeledValue/LabeledValue.module.css +32 -0
  30. package/src/Display/LabeledValue/LabeledValue.tsx +20 -0
  31. package/src/Display/List/List.module.css +143 -0
  32. package/src/Display/List/List.tsx +298 -0
  33. package/src/Display/PasswordStrength/PasswordStrength.module.css +45 -0
  34. package/src/Display/PasswordStrength/PasswordStrength.tsx +41 -0
  35. package/src/Display/PasswordStrength/usePasswordStrength.tsx +77 -0
  36. package/src/Display/Skeleton/Skeleton.module.css +54 -0
  37. package/src/Display/Skeleton/Skeleton.tsx +28 -0
  38. package/src/Display/Toast/Toaster.tsx +58 -0
  39. package/src/Display/Toast/_index.ts +2 -0
  40. package/src/Display/Toast/toast.ts +44 -0
  41. package/src/Display/Tooltip/Tooltip.module.css +128 -0
  42. package/src/Display/Tooltip/Tooltip.tsx +93 -0
  43. package/src/Display/Tooltip/getArrowDirection.ts +55 -0
  44. package/src/Display/Tooltip/useTooltip.tsx +63 -0
  45. package/src/Display/_index.ts +12 -0
  46. package/src/Forms/ConfirmationForm/ConfirmationForm.module.css +23 -0
  47. package/src/Forms/ConfirmationForm/ConfirmationForm.tsx +60 -0
  48. package/src/Forms/_index.ts +1 -0
  49. package/src/Inputs/Button/Button.module.css +131 -0
  50. package/src/Inputs/Button/Button.tsx +178 -0
  51. package/src/Inputs/Checkbox/Checkbox.module.css +77 -0
  52. package/src/Inputs/Checkbox/Checkbox.tsx +191 -0
  53. package/src/Inputs/Checkbox/CheckboxGroup/CheckboxGroup.module.css +10 -0
  54. package/src/Inputs/Checkbox/CheckboxGroup/CheckboxGroup.tsx +83 -0
  55. package/src/Inputs/Checkbox/CheckboxSelectAll.tsx +34 -0
  56. package/src/Inputs/Checkbox/_index.ts +3 -0
  57. package/src/Inputs/PasswordInput/PasswordInput.module.css +111 -0
  58. package/src/Inputs/PasswordInput/PasswordInput.tsx +229 -0
  59. package/src/Inputs/Select/Select.module.css +138 -0
  60. package/src/Inputs/Select/Select.tsx +136 -0
  61. package/src/Inputs/Switch/Switch.module.css +119 -0
  62. package/src/Inputs/Switch/Switch.tsx +195 -0
  63. package/src/Inputs/TextAreaInput/TextAreaInput.module.css +65 -0
  64. package/src/Inputs/TextAreaInput/TextAreaInput.tsx +97 -0
  65. package/src/Inputs/TextInput/TextInput.module.css +112 -0
  66. package/src/Inputs/TextInput/TextInput.tsx +142 -0
  67. package/src/Inputs/ThemeToggle/ThemeToggleListItem.tsx +80 -0
  68. package/src/Inputs/ThemeToggle/_index.ts +1 -0
  69. package/src/Inputs/_index.ts +8 -0
  70. package/src/Layout/Dialog/Dialog.module.css +15 -0
  71. package/src/Layout/Dialog/Dialog.tsx +115 -0
  72. package/src/Layout/PageLayout/PageLayout.module.css +20 -0
  73. package/src/Layout/PageLayout/PageLayout.tsx +79 -0
  74. package/src/Layout/PageLayoutPagination/PageLayoutPagination.module.css +5 -0
  75. package/src/Layout/PageLayoutPagination/PageLayoutPagination.tsx +40 -0
  76. package/src/Layout/PageLayoutTabs/PageLayoutTabs.module.css +3 -0
  77. package/src/Layout/PageLayoutTabs/PageLayoutTabs.tsx +62 -0
  78. package/src/Layout/Popover/Popover.module.css +9 -0
  79. package/src/Layout/Popover/Popover.tsx +145 -0
  80. package/src/Layout/SectionWrapper/SectionWrapper.module.css +31 -0
  81. package/src/Layout/SectionWrapper/SectionWrapper.tsx +62 -0
  82. package/src/Layout/Sidebar/Sidebar.module.css +17 -0
  83. package/src/Layout/Sidebar/Sidebar.tsx +39 -0
  84. package/src/Layout/Sidebar/SidebarBody/SidebarBody.module.css +31 -0
  85. package/src/Layout/Sidebar/SidebarBody/SidebarBody.tsx +18 -0
  86. package/src/Layout/Sidebar/SidebarDrawer/SidebarDrawer.module.css +20 -0
  87. package/src/Layout/Sidebar/SidebarDrawer/SidebarDrawer.tsx +19 -0
  88. package/src/Layout/Sidebar/SidebarFooter/SidebarFooter.module.css +35 -0
  89. package/src/Layout/Sidebar/SidebarFooter/SidebarFooter.tsx +19 -0
  90. package/src/Layout/Sidebar/SidebarHeader/SidebarHeader.tsx +14 -0
  91. package/src/Layout/Sidebar/SidebarViewport/SidebarViewport.module.css +12 -0
  92. package/src/Layout/Sidebar/SidebarViewport/SidebarViewport.tsx +11 -0
  93. package/src/Layout/Sidebar/_index.ts +6 -0
  94. package/src/Layout/Table/Table.module.css +46 -0
  95. package/src/Layout/Table/Table.tsx +222 -0
  96. package/src/Layout/Table/TableFooter.tsx +4 -0
  97. package/src/Layout/Table/TableHeader.tsx +4 -0
  98. package/src/Layout/Table/_index.ts +5 -0
  99. package/src/Layout/Table/tableUtils.ts +142 -0
  100. package/src/Layout/Table/types.ts +48 -0
  101. package/src/Layout/_index.ts +8 -0
  102. package/src/Misc/Cursor/Cursor.module.css +31 -0
  103. package/src/Misc/Cursor/Cursor.tsx +77 -0
  104. package/src/Misc/Logos.tsx +230 -0
  105. package/src/Misc/PoweredByBanner/PoweredByBanner.module.css +20 -0
  106. package/src/Misc/PoweredByBanner/PoweredByBanner.tsx +17 -0
  107. package/src/Misc/Ripple/Ripple.module.css +25 -0
  108. package/src/Misc/Ripple/Ripple.tsx +126 -0
  109. package/src/Misc/Spinner/Spinner.module.css +38 -0
  110. package/src/Misc/Spinner/Spinner.tsx +36 -0
  111. package/src/Misc/TransitionAnimation/TransitionAnimation.module.css +131 -0
  112. package/src/Misc/TransitionAnimation/TransitionAnimation.tsx +166 -0
  113. package/src/Misc/_index.ts +6 -0
  114. package/src/Navigation/Breadcrumbs/Breadcrumbs.module.css +22 -0
  115. package/src/Navigation/Breadcrumbs/Breadcrumbs.tsx +127 -0
  116. package/src/Navigation/Breadcrumbs/BreadcrumbsItem.tsx +31 -0
  117. package/src/Navigation/Breadcrumbs/_index.ts +3 -0
  118. package/src/Navigation/Breadcrumbs/useBreadcrumbs.tsx +74 -0
  119. package/src/Navigation/Pagination/Pagination.module.css +41 -0
  120. package/src/Navigation/Pagination/Pagination.tsx +187 -0
  121. package/src/Navigation/Pagination/PaginationItem.tsx +28 -0
  122. package/src/Navigation/Pagination/_index.ts +3 -0
  123. package/src/Navigation/Pagination/usePagination.tsx +65 -0
  124. package/src/Navigation/Tabs/Tab/Tab.module.css +43 -0
  125. package/src/Navigation/Tabs/Tab/Tab.tsx +155 -0
  126. package/src/Navigation/Tabs/Tabs.tsx +37 -0
  127. package/src/Navigation/Tabs/TabsBar/TabsBar.module.css +47 -0
  128. package/src/Navigation/Tabs/TabsBar/TabsBar.tsx +92 -0
  129. package/src/Navigation/Tabs/_index.ts +3 -0
  130. package/src/Navigation/_index.ts +3 -0
  131. package/src/Typography/ClampedText/ClampedText.module.css +5 -0
  132. package/src/Typography/ClampedText/ClampedText.tsx +77 -0
  133. package/src/Typography/CopyableText/CopyableText.module.css +21 -0
  134. package/src/Typography/CopyableText/CopyableText.tsx +120 -0
  135. package/src/Typography/PageTitle/PageTitle.module.css +47 -0
  136. package/src/Typography/PageTitle/PageTitle.tsx +35 -0
  137. package/src/Typography/_index.ts +3 -0
  138. package/src/declaration.d.ts +4 -0
  139. package/src/index.ts +8 -0
  140. package/tsconfig.json +32 -0
@@ -0,0 +1,71 @@
1
+ .root {
2
+ --glass-tint: var(--color-primary);
3
+ --glass-border-width: 2px;
4
+ --glass-border-radius: var(--shape-br-md);
5
+ --glass-border-color: var(--color-outline);
6
+ --glass-mouse-pointer-color: color-mix(
7
+ in srgb,
8
+ var(--color-primary) 50%,
9
+ transparent
10
+ );
11
+ --glass-shadow: color-mix(in srgb, var(--color-primary) 15%, transparent);
12
+ --glass-backdrop-blur: 5px;
13
+ --glass-position: relative;
14
+
15
+ isolation: isolate;
16
+ border-radius: var(--glass-border-radius);
17
+ position: var(--glass-position);
18
+ box-shadow: inset 0px 0px 4px var(--glass-shadow);
19
+ backdrop-filter: blur(var(--glass-backdrop-blur));
20
+
21
+ /* Border */
22
+ &::after {
23
+ --glass-reflection-i: color-mix(
24
+ in srgb,
25
+ var(--glass-tint) 20%,
26
+ transparent
27
+ );
28
+ --glass-reflection: color-mix(in srgb, var(--glass-tint) 40%, transparent);
29
+ --glass-reflection-w: #ffffff79;
30
+ content: "";
31
+ position: absolute;
32
+ inset: 0;
33
+ border-radius: inherit;
34
+ padding: var(--glass-border-width);
35
+ background-image: linear-gradient(
36
+ var(--gradient-angle, 255deg),
37
+ var(--glass-reflection-w) 0%,
38
+ var(--glass-reflection) 0.5%,
39
+ transparent 30%,
40
+ transparent 70%,
41
+ var(--glass-reflection-i) 100%
42
+ );
43
+ opacity: var(--glass-reflection-intensity, 1);
44
+ mask:
45
+ linear-gradient(#000 0 0) exclude,
46
+ linear-gradient(#000 0 0) content-box;
47
+
48
+ z-index: -2;
49
+ }
50
+
51
+ /* Mouse Pointer Light */
52
+ &::before {
53
+ content: "";
54
+ inset: 0;
55
+ position: absolute;
56
+ transition: opacity var(--transition-time);
57
+ pointer-events: none;
58
+ opacity: 1;
59
+ padding: var(--glass-border-width);
60
+ mask:
61
+ linear-gradient(#000 0 0) exclude,
62
+ linear-gradient(#000 0 0) content-box;
63
+ border-radius: inherit;
64
+ background: radial-gradient(
65
+ 250px circle at var(--mouse-x) var(--mouse-y),
66
+ var(--glass-mouse-pointer-color),
67
+ transparent 40%
68
+ );
69
+ z-index: -1;
70
+ }
71
+ }
@@ -0,0 +1,80 @@
1
+ "use client";
2
+
3
+ import { useMousePosition } from "@studiocubics/hooks";
4
+ import type {
5
+ PolymorphicComponentProps,
6
+ PolymorphicComponentType,
7
+ } from "@studiocubics/types";
8
+ import { mergeRefs, cn } from "@studiocubics/utils";
9
+ import { type ElementType, useRef, useEffect } from "react";
10
+ import styles from "./GlassCard.module.css";
11
+
12
+ const defaultElement = "div";
13
+ type DefaultElement = typeof defaultElement;
14
+
15
+ export type GlassCardProps<C extends ElementType = DefaultElement> = Omit<
16
+ PolymorphicComponentProps<C, object>,
17
+ "ref"
18
+ >;
19
+
20
+ // Create the base component with forwardRef
21
+ function GlassCardBase<C extends ElementType = DefaultElement>({
22
+ as,
23
+ children,
24
+ className,
25
+ ref,
26
+ ...restProps
27
+ }: GlassCardProps<C>) {
28
+ const Component = (as || defaultElement) as ElementType;
29
+ const { mousePosition } = useMousePosition({ includeTouch: false });
30
+ const cardRef = useRef<HTMLDivElement>(null);
31
+
32
+ useEffect(() => {
33
+ if (!cardRef.current) return;
34
+ if (!mousePosition.x || !mousePosition.y) return;
35
+
36
+ const rect = cardRef.current.getBoundingClientRect();
37
+ const x = mousePosition.x - rect.left;
38
+ const y = mousePosition.y - rect.top;
39
+ const xCenter = rect.left + rect.width / 2;
40
+ const yCenter = rect.top + rect.height / 2;
41
+
42
+ const dx = (mousePosition.x - xCenter) / rect.width;
43
+ const dy = (mousePosition.y - yCenter) / rect.height;
44
+
45
+ // Angle from card center to mouse
46
+ const angle = Math.atan2(dy, dx) * (180 / Math.PI) + 270;
47
+
48
+ // Distance normalized against window width/height
49
+ const dxGlobal = mousePosition.x - xCenter;
50
+ const dyGlobal = mousePosition.y - yCenter;
51
+ const tX = Math.min(Math.abs(dxGlobal) / window.innerWidth, 1);
52
+ const tY = Math.min(Math.abs(dyGlobal) / window.innerHeight, 1);
53
+ const t = Math.min(tX + tY, 1); // Manhattan distance capped at 1
54
+ const intensity = 1 - 0.8 * t; // maps to 0.2..1
55
+
56
+ cardRef.current.style.setProperty("--mouse-x", `${x}px`);
57
+ cardRef.current.style.setProperty("--mouse-y", `${y}px`);
58
+ cardRef.current.style.setProperty("--gradient-angle", `${angle}deg`);
59
+ cardRef.current.style.setProperty(
60
+ "--glass-reflection-intensity",
61
+ `${intensity}`
62
+ );
63
+ }, [mousePosition]);
64
+
65
+ const componentProps = {
66
+ ref: mergeRefs(ref, cardRef),
67
+ className: cn(className, styles.root),
68
+ ...restProps,
69
+ };
70
+
71
+ return <Component {...componentProps}>{children}</Component>;
72
+ }
73
+
74
+ GlassCardBase.displayName = "GlassCard";
75
+
76
+ // Type assertion to make it polymorphic
77
+ export const GlassCard = GlassCardBase as PolymorphicComponentType<
78
+ object,
79
+ DefaultElement
80
+ >;
@@ -0,0 +1,3 @@
1
+ export * from "./Card/Card";
2
+ export * from "./CollectionItemCard/_index";
3
+ export * from "./GlassCard/GlassCard";
@@ -0,0 +1,69 @@
1
+ .summary {
2
+ cursor: pointer;
3
+ display: flex;
4
+ align-items: center;
5
+ gap: var(--spacing-gap);
6
+ padding: var(--spacing-gap);
7
+ border-radius: var(--shape-br-md);
8
+ position: relative;
9
+ isolation: isolate;
10
+ transition: all var(--transition-time) var(--transition-tf);
11
+ /* background: var(--color-background); */
12
+ &:hover {
13
+ background: var(--color-background-faint);
14
+ }
15
+ }
16
+ .highlight {
17
+ position: absolute;
18
+ inset: 0;
19
+ z-index: -1;
20
+ }
21
+ .summaryContent {
22
+ flex: 1;
23
+ border-radius: calc(var(--shape-br-md) - var(--spacing-gap));
24
+ container-type: inline-size;
25
+ overflow: hidden;
26
+ }
27
+ .marker {
28
+ flex: 0 0 var(--spacing-gap-4);
29
+ width: var(--spacing-gap-4);
30
+ height: var(--spacing-gap-4);
31
+ position: relative;
32
+ & > svg {
33
+ position: absolute;
34
+ inset: 0;
35
+ }
36
+ }
37
+ .root {
38
+ background: var(--color-background);
39
+ border-radius: var(--shape-br-md);
40
+ overflow: hidden;
41
+ transition: margin-block var(--transition-time) var(--transition-tf);
42
+ margin-block: 0;
43
+
44
+ @media (prefers-reduced-motion: no-preference) {
45
+ interpolate-size: allow-keywords;
46
+ }
47
+ &::details-content {
48
+ overflow: hidden;
49
+ max-height: 0;
50
+ transition:
51
+ content-visibility var(--transition-time) allow-discrete,
52
+ max-height var(--transition-time) var(--transition-tf);
53
+ }
54
+ &[open] {
55
+ margin-block: var(--spacing-gap-2);
56
+ & > .summary {
57
+ background: var(--color-surface);
58
+ }
59
+ &::details-content {
60
+ max-height: 999px;
61
+ @supports (interpolate-size: allow-keywords) {
62
+ max-height: auto;
63
+ }
64
+ }
65
+ }
66
+ }
67
+ .content {
68
+ padding: var(--spacing-gap-3);
69
+ }
@@ -0,0 +1,61 @@
1
+ "use client";
2
+
3
+ import {
4
+ createContext,
5
+ type ReactNode,
6
+ useCallback,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+
11
+ interface AccordionContextProps {
12
+ values: boolean[];
13
+ register: () => number;
14
+ update: (index: number, checked: boolean) => void;
15
+ }
16
+ interface AccordionProviderProps {
17
+ children: ReactNode;
18
+ exclusive?: boolean;
19
+ onChange?: (checked: boolean[]) => void;
20
+ }
21
+
22
+ export const AccordionContext = createContext<AccordionContextProps | null>(
23
+ null,
24
+ );
25
+
26
+ export function Accordion({
27
+ children,
28
+ exclusive = false,
29
+ onChange,
30
+ }: AccordionProviderProps) {
31
+ const [values, setValues] = useState<boolean[]>([]);
32
+ const indexRef = useRef(-1);
33
+
34
+ const register = useCallback(() => {
35
+ const index = indexRef.current++;
36
+ setValues((prev) => [...prev, false]);
37
+ return index;
38
+ }, []);
39
+
40
+ const update = useCallback(
41
+ (index: number, checked: boolean) => {
42
+ console.log("index", index, checked);
43
+
44
+ setValues((prev) => {
45
+ let next =
46
+ exclusive && checked ? new Array(prev.length).fill(false) : [...prev];
47
+ next[index] = checked;
48
+ onChange?.(next);
49
+ return next;
50
+ });
51
+ },
52
+ [exclusive, onChange],
53
+ );
54
+ const context = { values, register, update };
55
+
56
+ return (
57
+ <AccordionContext.Provider value={context}>
58
+ {children}
59
+ </AccordionContext.Provider>
60
+ );
61
+ }
@@ -0,0 +1,135 @@
1
+ "use client";
2
+
3
+ import {
4
+ type ComponentProps,
5
+ type ReactNode,
6
+ type ToggleEvent,
7
+ useContext,
8
+ useEffect,
9
+ useRef,
10
+ useState,
11
+ } from "react";
12
+ import { AccordionContext } from "./Accordion";
13
+ import styles from "./Accordion.module.css";
14
+ import { cn, mergeRefs } from "@studiocubics/utils";
15
+ import { GlassCard } from "../../Cards/GlassCard/GlassCard";
16
+
17
+ export type AccordionProps = {
18
+ summary: ReactNode;
19
+ onToggle?: (e: ToggleEvent<HTMLDetailsElement>, current: boolean) => void;
20
+ openMarker?: ReactNode;
21
+ closeMarker?: ReactNode;
22
+ highlightOpen?: boolean;
23
+ slotProps?: {
24
+ marker?: ComponentProps<"span">;
25
+ summary?: ComponentProps<"summary">;
26
+ summaryContent?: ComponentProps<"div">;
27
+ content?: ComponentProps<"div">;
28
+ };
29
+ } & ComponentProps<"details">;
30
+
31
+ export function AccordionItem(props: AccordionProps) {
32
+ const {
33
+ summary,
34
+ open: htmlOpen,
35
+ children,
36
+ className,
37
+ onToggle,
38
+ highlightOpen = true,
39
+ openMarker = <OpenMarkerIcon />,
40
+ closeMarker = <CloseMarkerIcon />,
41
+ slotProps = {},
42
+ ref,
43
+ ...rest
44
+ } = props;
45
+
46
+ const [index, setIndex] = useState<number | null>(null);
47
+ const isRegistered = useRef(false);
48
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(!!htmlOpen);
49
+ const itemRef = useRef<HTMLDetailsElement | null>(null);
50
+ const group = useContext(AccordionContext);
51
+
52
+ const open = group && index !== null ? group.values[index] : uncontrolledOpen;
53
+
54
+ const handleToggle = async (e: ToggleEvent<HTMLDetailsElement>) => {
55
+ const next = e.currentTarget.open;
56
+ if (group && index !== null) group.update(index, next);
57
+ else setUncontrolledOpen(next);
58
+ onToggle?.(e, next);
59
+ if (next) {
60
+ itemRef.current?.scrollIntoView({
61
+ block: "nearest",
62
+ inline: "nearest",
63
+ behavior: "smooth",
64
+ });
65
+ }
66
+ };
67
+
68
+ const componentProps = {
69
+ open,
70
+ onToggle: handleToggle,
71
+ className: cn(className, styles.root, open ? styles.open : ""),
72
+ ref: mergeRefs(ref, itemRef),
73
+ ...rest,
74
+ };
75
+ // Register with group if present
76
+ useEffect(() => {
77
+ if (!group || isRegistered.current) return;
78
+ isRegistered.current = true;
79
+ setIndex(group.register());
80
+ }, [group]);
81
+
82
+ return (
83
+ <details {...componentProps}>
84
+ <summary className={cn(className, styles.summary)}>
85
+ {open && highlightOpen && <GlassCard className={styles.highlight} />}
86
+ <div
87
+ {...slotProps.summaryContent}
88
+ className={cn(
89
+ styles.summaryContent,
90
+ slotProps.summaryContent?.className,
91
+ )}
92
+ >
93
+ {summary}
94
+ </div>
95
+ <span {...slotProps.marker} className={cn(styles.marker)}>
96
+ {open ? closeMarker : openMarker}
97
+ </span>
98
+ </summary>
99
+ <div
100
+ {...slotProps.content}
101
+ className={cn(styles.content, slotProps.content?.className)}
102
+ >
103
+ {children}
104
+ </div>
105
+ </details>
106
+ );
107
+ }
108
+ function OpenMarkerIcon(props: ComponentProps<"svg">) {
109
+ return (
110
+ <svg
111
+ {...props}
112
+ xmlns="http://www.w3.org/2000/svg"
113
+ viewBox="0 0 24 24"
114
+ fill="none"
115
+ strokeWidth="2.3"
116
+ stroke="currentColor"
117
+ >
118
+ <path d="m6 9 6 6 6-6" />
119
+ </svg>
120
+ );
121
+ }
122
+ function CloseMarkerIcon(props: ComponentProps<"svg">) {
123
+ return (
124
+ <svg
125
+ {...props}
126
+ xmlns="http://www.w3.org/2000/svg"
127
+ viewBox="0 0 24 24"
128
+ fill="none"
129
+ strokeWidth="2.3"
130
+ stroke="currentColor"
131
+ >
132
+ <path d="m18 15-6-6-6 6" />
133
+ </svg>
134
+ );
135
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./Accordion";
2
+ export * from "./AccordionItem";
@@ -0,0 +1,64 @@
1
+ .root {
2
+ --badge-outline-color: var(--color-outline);
3
+ --badge-color: var(--color-on-surface);
4
+ --badge-background: var(--color-surface);
5
+ color: var(--badge-color);
6
+ border: 1px solid var(--badge-outline-color);
7
+ background: var(--badge-background);
8
+ width: fit-content;
9
+ font-size: 0.9em;
10
+ display: flex;
11
+ align-items: center;
12
+ flex-wrap: wrap;
13
+ gap: var(--spacing-gap);
14
+ }
15
+ .outlined {
16
+ background: none;
17
+ }
18
+ .contained {
19
+ border: none;
20
+ }
21
+ .size_sm {
22
+ padding: calc(var(--spacing-gap) / 2) calc(1.618 * (var(--spacing-gap) / 2));
23
+ font-size: 0.8em;
24
+ border-radius: var(--shape-br-xs);
25
+ }
26
+ .size_md {
27
+ padding: calc(var(--spacing-gap-2) / 2)
28
+ calc(1.618 * (var(--spacing-gap-2) / 2));
29
+ border-radius: var(--shape-br-sm);
30
+ }
31
+
32
+ .size_lg {
33
+ padding: calc(var(--spacing-gap-3) / 2)
34
+ calc(1.618 * (var(--spacing-gap-3) / 2));
35
+ font-size: 1em;
36
+ border-radius: var(--shape-br-sm);
37
+ }
38
+ .root[data-color="primary"] {
39
+ --badge-color: var(--color-primary);
40
+ --badge-background: none;
41
+ --badge-outline-color: var(--color-primary);
42
+ }
43
+ .root[data-color="secondary"] {
44
+ --badge-color: var(--color-secondary);
45
+ --badge-background: none;
46
+ --badge-outline-color: var(--color-secondary);
47
+ }
48
+ .root[data-color="error"] {
49
+ --badge-color: var(--color-error);
50
+ --badge-background: none;
51
+ --badge-outline-color: var(--color-error);
52
+ }
53
+ .contained[data-color="primary"] {
54
+ --badge-color: var(--color-on-primary);
55
+ --badge-background: var(--color-primary);
56
+ }
57
+ .contained[data-color="secondary"] {
58
+ --badge-color: var(--color-on-secondary);
59
+ --badge-background: var(--color-secondary);
60
+ }
61
+ .contained[data-color="error"] {
62
+ --badge-color: var(--color-on-error);
63
+ --badge-background: var(--color-error);
64
+ }
@@ -0,0 +1,105 @@
1
+ "use client";
2
+
3
+ import type {
4
+ PolymorphicComponentProps,
5
+ PolymorphicComponentType,
6
+ } from "@studiocubics/types";
7
+ import { type ElementType } from "react";
8
+
9
+ import { cn } from "@studiocubics/utils";
10
+ import styles from "./Chip.module.css";
11
+
12
+ const defaultElement = "span";
13
+ type DefaultElement = typeof defaultElement;
14
+
15
+ /**
16
+ * Props specific to the Chip component.
17
+ *
18
+ * These extend the intrinsic element props of whatever element is passed via `as`.
19
+ * @group Chip
20
+ * @category inputs
21
+ */
22
+ export interface ChipBaseProps {
23
+ /** Visual style variant.
24
+ * @default "outlined"
25
+ */
26
+ variant?: "contained" | "outlined";
27
+
28
+ /** Expands width to 100%. */
29
+ fullWidth?: boolean;
30
+
31
+ /** Chip size.
32
+ * @default "md"
33
+ */
34
+ size?: "sm" | "md" | "lg";
35
+
36
+ color?: "primary" | "secondary" | "error";
37
+ }
38
+
39
+ /**
40
+ * Polymorphic props for the Chip component.
41
+ *
42
+ * `C` defines the element type rendered by the component (e.g. `"Chip"`, `"a"`, `"div"`).
43
+ * All intrinsic props for `C` are supported unless overridden by `ChipBaseProps`.
44
+ *
45
+ * @group Chip
46
+ * @category inputs
47
+ */
48
+ export type ChipProps<C extends ElementType = DefaultElement> =
49
+ PolymorphicComponentProps<C, ChipBaseProps>;
50
+
51
+ /**
52
+ * Base implementation for the Chip component.
53
+ *
54
+ * This is a polymorphic component that defaults to rendering a `<Chip>`.
55
+ * Use the `as` prop to change the underlying element.
56
+ *
57
+ * @typeParam C - The intrinsic or custom element type to render.
58
+ *
59
+ * @group Chip
60
+ * @category inputs
61
+ */
62
+ function ChipBase<C extends ElementType = DefaultElement>(props: ChipProps<C>) {
63
+ const {
64
+ as,
65
+ className,
66
+ variant = "outlined",
67
+ size = "md",
68
+ fullWidth = false,
69
+ color,
70
+ square,
71
+ ...restProps
72
+ } = props;
73
+ const Component = (as || defaultElement) as ElementType;
74
+
75
+ const componentProps = {
76
+ className: cn(
77
+ className,
78
+ styles.root,
79
+ square ? styles.square : "",
80
+ fullWidth ? styles.fullWidth : "",
81
+ styles[`size_${size}`],
82
+ styles[variant]
83
+ ),
84
+ "data-color": color,
85
+ ...restProps,
86
+ };
87
+
88
+ return <Component {...componentProps} />;
89
+ }
90
+ ChipBase.displayName = "Chip";
91
+
92
+ /**
93
+ * A polymorphic Chip component.
94
+ *
95
+ * ```tsx
96
+ * <Chip as="a" href="/docs">Read docs</Chip>
97
+ * ```
98
+ *
99
+ * @group Chip
100
+ * @category inputs
101
+ */
102
+ export const Chip = ChipBase as PolymorphicComponentType<
103
+ ChipBaseProps,
104
+ DefaultElement
105
+ >;
@@ -0,0 +1,95 @@
1
+ .root {
2
+ position: relative;
3
+ display: flex;
4
+ flex-direction: column;
5
+ padding: var(--spacing-gap-2);
6
+ border-radius: var(--shape-br-md);
7
+ overflow: hidden;
8
+ transition:
9
+ padding var(--transition-time) var(--transition-tf),
10
+ width 0.1s var(--transition-time) var(--transition-tf),
11
+ color var(--transition-time) var(--transition-tf),
12
+ background var(--transition-time) var(--transition-tf);
13
+ }
14
+ .clickable {
15
+ cursor: pointer;
16
+ &:hover {
17
+ color: var(--color-on-primary-container);
18
+ background: var(--color-primary-container);
19
+ }
20
+ }
21
+ .main {
22
+ display: flex;
23
+ align-items: center;
24
+ gap: var(--spacing-gap-2);
25
+ }
26
+ .image {
27
+ display: flex;
28
+ justify-content: center;
29
+ align-items: center;
30
+ & > img {
31
+ width: 48px;
32
+ height: 48px;
33
+ object-fit: cover;
34
+ overflow: hidden;
35
+ border-radius: 50%;
36
+ }
37
+ }
38
+ .details {
39
+ color: inherit;
40
+ display: flex;
41
+ flex-direction: column;
42
+ & h4 {
43
+ font-weight: bold;
44
+ font-size: 1.1em;
45
+ overflow: hidden;
46
+ white-space: nowrap;
47
+ text-overflow: ellipsis;
48
+ text-align: left;
49
+ }
50
+ & > p {
51
+ text-align: left;
52
+ font-size: 0.9em;
53
+ font-weight: 400;
54
+ overflow: hidden;
55
+ white-space: nowrap;
56
+ text-overflow: ellipsis;
57
+ color: var(--color-on-background-faint);
58
+ }
59
+ }
60
+ .desc {
61
+ font-size: 0.9em;
62
+ }
63
+ .image-only {
64
+ padding: 0;
65
+ & .main,
66
+ & .image {
67
+ width: 100%;
68
+ }
69
+ .details {
70
+ display: none;
71
+ }
72
+ .desc {
73
+ display: none;
74
+ }
75
+ }
76
+ .square {
77
+ .main {
78
+ flex-direction: column;
79
+ align-items: center;
80
+ }
81
+ .details {
82
+ & > h4,
83
+ & > p {
84
+ text-align: center;
85
+ }
86
+ }
87
+ .desc {
88
+ display: none;
89
+ }
90
+ }
91
+ .compact {
92
+ .desc {
93
+ display: none;
94
+ }
95
+ }