@c-rex/ui 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -3
- package/src/badge.tsx +31 -31
- package/src/drawer.tsx +133 -0
- package/src/hooks/index.tsx +29 -0
- package/src/sheet.tsx +1 -1
- package/src/sidebar.tsx +163 -119
- package/src/hooks/use-mobile.tsx +0 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c-rex/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src"
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
12
|
"exports": {
|
|
13
|
+
"./hooks": {
|
|
14
|
+
"types": "./src/hooks/index.tsx",
|
|
15
|
+
"import": "./src/hooks/index.tsx"
|
|
16
|
+
},
|
|
13
17
|
"./collapsible": {
|
|
14
18
|
"types": "./src/collapsible.tsx",
|
|
15
19
|
"import": "./src/collapsible.tsx"
|
|
@@ -18,6 +22,10 @@
|
|
|
18
22
|
"types": "./src/alert.tsx",
|
|
19
23
|
"import": "./src/alert.tsx"
|
|
20
24
|
},
|
|
25
|
+
"./drawer": {
|
|
26
|
+
"types": "./src/drawer.tsx",
|
|
27
|
+
"import": "./src/drawer.tsx"
|
|
28
|
+
},
|
|
21
29
|
"./breadcrumb": {
|
|
22
30
|
"types": "./src/breadcrumb.tsx",
|
|
23
31
|
"import": "./src/breadcrumb.tsx"
|
|
@@ -152,7 +160,7 @@
|
|
|
152
160
|
"@radix-ui/react-checkbox": "^1.3.2",
|
|
153
161
|
"@radix-ui/react-collapsible": "^1.1.11",
|
|
154
162
|
"@radix-ui/react-context-menu": "^2.2.15",
|
|
155
|
-
"@radix-ui/react-dialog": "^1.1.
|
|
163
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
156
164
|
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
|
157
165
|
"@radix-ui/react-label": "^2.1.7",
|
|
158
166
|
"@radix-ui/react-popover": "^1.1.6",
|
|
@@ -173,6 +181,7 @@
|
|
|
173
181
|
"react-dom": "^18.3.1",
|
|
174
182
|
"sonner": "^2.0.5",
|
|
175
183
|
"tailwind-merge": "^3.3.0",
|
|
176
|
-
"tw-animate-css": "^1.2.9"
|
|
184
|
+
"tw-animate-css": "^1.2.9",
|
|
185
|
+
"vaul": "^1.1.2"
|
|
177
186
|
}
|
|
178
187
|
}
|
package/src/badge.tsx
CHANGED
|
@@ -5,42 +5,42 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|
|
5
5
|
import { cn } from "@c-rex/utils"
|
|
6
6
|
|
|
7
7
|
const badgeVariants = cva(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
8
|
+
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default:
|
|
13
|
+
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
14
|
+
secondary:
|
|
15
|
+
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
16
|
+
destructive:
|
|
17
|
+
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
18
|
+
outline:
|
|
19
|
+
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: "default",
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
function Badge({
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
className,
|
|
30
|
+
variant,
|
|
31
|
+
asChild = false,
|
|
32
|
+
...props
|
|
33
33
|
}: React.ComponentProps<"span"> &
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
|
35
|
+
const Comp = asChild ? Slot : "span"
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
return (
|
|
38
|
+
<Comp
|
|
39
|
+
data-slot="badge"
|
|
40
|
+
className={cn(badgeVariants({ variant }), className)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export { Badge, badgeVariants }
|
package/src/drawer.tsx
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Drawer as DrawerPrimitive } from "vaul"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@c-rex/utils"
|
|
5
|
+
|
|
6
|
+
function Drawer({
|
|
7
|
+
...props
|
|
8
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
|
9
|
+
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function DrawerTrigger({
|
|
13
|
+
...props
|
|
14
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
|
15
|
+
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function DrawerPortal({
|
|
19
|
+
...props
|
|
20
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
|
21
|
+
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function DrawerClose({
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
|
27
|
+
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function DrawerOverlay({
|
|
31
|
+
className,
|
|
32
|
+
...props
|
|
33
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
|
|
34
|
+
return (
|
|
35
|
+
<DrawerPrimitive.Overlay
|
|
36
|
+
data-slot="drawer-overlay"
|
|
37
|
+
className={cn(
|
|
38
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function DrawerContent({
|
|
47
|
+
className,
|
|
48
|
+
children,
|
|
49
|
+
...props
|
|
50
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
|
|
51
|
+
return (
|
|
52
|
+
<DrawerPortal data-slot="drawer-portal">
|
|
53
|
+
<DrawerOverlay />
|
|
54
|
+
<DrawerPrimitive.Content
|
|
55
|
+
data-slot="drawer-content"
|
|
56
|
+
className={cn(
|
|
57
|
+
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
|
|
58
|
+
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
|
|
59
|
+
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
|
|
60
|
+
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
|
61
|
+
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
|
|
62
|
+
className
|
|
63
|
+
)}
|
|
64
|
+
{...props}
|
|
65
|
+
>
|
|
66
|
+
<div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
|
|
67
|
+
{children}
|
|
68
|
+
</DrawerPrimitive.Content>
|
|
69
|
+
</DrawerPortal>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="drawer-header"
|
|
77
|
+
className={cn(
|
|
78
|
+
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
|
|
79
|
+
className
|
|
80
|
+
)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
data-slot="drawer-footer"
|
|
90
|
+
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
|
91
|
+
{...props}
|
|
92
|
+
/>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function DrawerTitle({
|
|
97
|
+
className,
|
|
98
|
+
...props
|
|
99
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
|
|
100
|
+
return (
|
|
101
|
+
<DrawerPrimitive.Title
|
|
102
|
+
data-slot="drawer-title"
|
|
103
|
+
className={cn("text-foreground font-semibold", className)}
|
|
104
|
+
{...props}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function DrawerDescription({
|
|
110
|
+
className,
|
|
111
|
+
...props
|
|
112
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
|
|
113
|
+
return (
|
|
114
|
+
<DrawerPrimitive.Description
|
|
115
|
+
data-slot="drawer-description"
|
|
116
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
Drawer,
|
|
124
|
+
DrawerPortal,
|
|
125
|
+
DrawerOverlay,
|
|
126
|
+
DrawerTrigger,
|
|
127
|
+
DrawerClose,
|
|
128
|
+
DrawerContent,
|
|
129
|
+
DrawerHeader,
|
|
130
|
+
DrawerFooter,
|
|
131
|
+
DrawerTitle,
|
|
132
|
+
DrawerDescription,
|
|
133
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { BREAKPOINTS, DEVICE_OPTIONS } from "@c-rex/constants";
|
|
3
|
+
import { DeviceType } from "@c-rex/types";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const useBreakpoint = (): DeviceType => {
|
|
7
|
+
const [device, setDevice] = useState<DeviceType>(DEVICE_OPTIONS.MOBILE);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
function handleResize() {
|
|
11
|
+
const width = window.innerWidth;
|
|
12
|
+
|
|
13
|
+
if (width < BREAKPOINTS.md) {
|
|
14
|
+
setDevice(DEVICE_OPTIONS.MOBILE);
|
|
15
|
+
} else if (width < BREAKPOINTS.lg) {
|
|
16
|
+
setDevice(DEVICE_OPTIONS.TABLET);
|
|
17
|
+
} else {
|
|
18
|
+
setDevice(DEVICE_OPTIONS.DESKTOP);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
handleResize();
|
|
23
|
+
window.addEventListener("resize", handleResize);
|
|
24
|
+
|
|
25
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
return device;
|
|
29
|
+
};
|
package/src/sheet.tsx
CHANGED
package/src/sidebar.tsx
CHANGED
|
@@ -3,19 +3,13 @@
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Slot } from "@radix-ui/react-slot";
|
|
5
5
|
import { VariantProps, cva } from "class-variance-authority";
|
|
6
|
-
import { PanelLeft } from "lucide-react";
|
|
6
|
+
import { Info, PanelLeft } from "lucide-react";
|
|
7
7
|
import { cn } from "@c-rex/utils";
|
|
8
|
-
import {
|
|
8
|
+
import { useBreakpoint } from "./hooks";
|
|
9
9
|
import { Button } from "./button";
|
|
10
10
|
import { Input } from "./input";
|
|
11
11
|
import { Separator } from "./separator";
|
|
12
|
-
import {
|
|
13
|
-
Sheet,
|
|
14
|
-
SheetContent,
|
|
15
|
-
SheetDescription,
|
|
16
|
-
SheetHeader,
|
|
17
|
-
SheetTitle,
|
|
18
|
-
} from "./sheet";
|
|
12
|
+
import { Sheet, SheetContent } from "./sheet";
|
|
19
13
|
import { Skeleton } from "./skeleton";
|
|
20
14
|
import {
|
|
21
15
|
Tooltip,
|
|
@@ -23,15 +17,16 @@ import {
|
|
|
23
17
|
TooltipProvider,
|
|
24
18
|
TooltipTrigger,
|
|
25
19
|
} from "./tooltip";
|
|
20
|
+
import { DEVICE_OPTIONS } from "@c-rex/constants";
|
|
26
21
|
|
|
27
22
|
const SIDEBAR_COOKIE_NAME = "sidebar_state";
|
|
28
23
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
|
29
24
|
const SIDEBAR_WIDTH = "16rem";
|
|
30
25
|
const SIDEBAR_WIDTH_MOBILE = "18rem";
|
|
26
|
+
const RIGHT_SIDEBAR_WIDTH = "19rem";
|
|
31
27
|
const SIDEBAR_WIDTH_ICON = "3rem";
|
|
32
|
-
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
|
33
28
|
|
|
34
|
-
type
|
|
29
|
+
type SidebarContextType = {
|
|
35
30
|
state: "expanded" | "collapsed";
|
|
36
31
|
open: boolean;
|
|
37
32
|
setOpen: (open: boolean) => void;
|
|
@@ -39,122 +34,153 @@ type SidebarContext = {
|
|
|
39
34
|
setOpenMobile: (open: boolean) => void;
|
|
40
35
|
isMobile: boolean;
|
|
41
36
|
toggleSidebar: () => void;
|
|
37
|
+
side: "left" | "right";
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type MultiSidebarContextType = {
|
|
41
|
+
leftSidebar: SidebarContextType;
|
|
42
|
+
rightSidebar: SidebarContextType;
|
|
42
43
|
};
|
|
43
44
|
|
|
44
|
-
const
|
|
45
|
+
const MultiSidebarContext = React.createContext<MultiSidebarContextType | null>(null);
|
|
45
46
|
|
|
46
|
-
function
|
|
47
|
-
const context = React.useContext(
|
|
47
|
+
function useMultiSidebar() {
|
|
48
|
+
const context = React.useContext(MultiSidebarContext);
|
|
48
49
|
if (!context) {
|
|
49
|
-
throw new Error(
|
|
50
|
+
throw new Error(
|
|
51
|
+
"useMultiSidebar must be used within a MultiSidebarProvider."
|
|
52
|
+
);
|
|
50
53
|
}
|
|
51
|
-
|
|
52
54
|
return context;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
const
|
|
57
|
+
const MultiSidebarProvider = React.forwardRef<
|
|
56
58
|
HTMLDivElement,
|
|
57
59
|
React.ComponentProps<"div"> & {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
defaultLeftOpen?: boolean;
|
|
61
|
+
defaultRightOpen?: boolean;
|
|
62
|
+
leftOpen?: boolean;
|
|
63
|
+
rightOpen?: boolean;
|
|
64
|
+
onLeftOpenChange?: (open: boolean) => void;
|
|
65
|
+
onRightOpenChange?: (open: boolean) => void;
|
|
61
66
|
}
|
|
62
67
|
>(
|
|
63
68
|
(
|
|
64
69
|
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
defaultLeftOpen = true,
|
|
71
|
+
defaultRightOpen = true,
|
|
72
|
+
leftOpen: leftOpenProp,
|
|
73
|
+
rightOpen: rightOpenProp,
|
|
74
|
+
onLeftOpenChange: setLeftOpenProp,
|
|
75
|
+
onRightOpenChange: setRightOpenProp,
|
|
68
76
|
className,
|
|
69
77
|
style,
|
|
70
78
|
children,
|
|
71
79
|
...props
|
|
72
80
|
},
|
|
73
|
-
ref
|
|
81
|
+
ref
|
|
74
82
|
) => {
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const setOpen = React.useCallback(
|
|
83
|
+
const device = useBreakpoint();
|
|
84
|
+
const isMobile = (device === DEVICE_OPTIONS.MOBILE || device === DEVICE_OPTIONS.TABLET);
|
|
85
|
+
|
|
86
|
+
// Left Sidebar State
|
|
87
|
+
const [_leftOpen, _setLeftOpen] = React.useState(defaultLeftOpen);
|
|
88
|
+
const leftOpen = leftOpenProp ?? _leftOpen;
|
|
89
|
+
const setLeftOpen = React.useCallback(
|
|
83
90
|
(value: boolean | ((value: boolean) => boolean)) => {
|
|
84
|
-
const openState = typeof value === "function" ? value(
|
|
85
|
-
if (
|
|
86
|
-
|
|
91
|
+
const openState = typeof value === "function" ? value(leftOpen) : value;
|
|
92
|
+
if (setLeftOpenProp) {
|
|
93
|
+
setLeftOpenProp(openState);
|
|
87
94
|
} else {
|
|
88
|
-
|
|
95
|
+
_setLeftOpen(openState);
|
|
89
96
|
}
|
|
90
|
-
|
|
91
|
-
// This sets the cookie to keep the sidebar state.
|
|
92
|
-
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
|
97
|
+
document.cookie = `${SIDEBAR_COOKIE_NAME}:left=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
|
93
98
|
},
|
|
94
|
-
[
|
|
99
|
+
[setLeftOpenProp, leftOpen]
|
|
95
100
|
);
|
|
96
101
|
|
|
97
|
-
//
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
|
|
109
|
-
(event.metaKey || event.ctrlKey)
|
|
110
|
-
) {
|
|
111
|
-
event.preventDefault();
|
|
112
|
-
toggleSidebar();
|
|
102
|
+
// Right Sidebar State
|
|
103
|
+
const [_rightOpen, _setRightOpen] = React.useState(defaultRightOpen);
|
|
104
|
+
const rightOpen = rightOpenProp ?? _rightOpen;
|
|
105
|
+
const setRightOpen = React.useCallback(
|
|
106
|
+
(value: boolean | ((value: boolean) => boolean)) => {
|
|
107
|
+
const openState =
|
|
108
|
+
typeof value === "function" ? value(rightOpen) : value;
|
|
109
|
+
if (setRightOpenProp) {
|
|
110
|
+
setRightOpenProp(openState);
|
|
111
|
+
} else {
|
|
112
|
+
_setRightOpen(openState);
|
|
113
113
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}, [toggleSidebar]);
|
|
114
|
+
document.cookie = `${SIDEBAR_COOKIE_NAME}:right=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
|
115
|
+
},
|
|
116
|
+
[setRightOpenProp, rightOpen]
|
|
117
|
+
);
|
|
119
118
|
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
const
|
|
119
|
+
// Mobile state for each sidebar
|
|
120
|
+
const [leftOpenMobile, setLeftOpenMobile] = React.useState(false);
|
|
121
|
+
const [rightOpenMobile, setRightOpenMobile] = React.useState(false);
|
|
123
122
|
|
|
124
|
-
const
|
|
123
|
+
const toggleLeftSidebar = React.useCallback(() => {
|
|
124
|
+
return isMobile
|
|
125
|
+
? setLeftOpenMobile((open) => !open)
|
|
126
|
+
: setLeftOpen((open) => !open);
|
|
127
|
+
}, [isMobile, setLeftOpen, setLeftOpenMobile]);
|
|
128
|
+
const toggleRightSidebar = React.useCallback(() => {
|
|
129
|
+
return isMobile
|
|
130
|
+
? setRightOpenMobile((open) => !open)
|
|
131
|
+
: setRightOpen((open) => !open);
|
|
132
|
+
}, [isMobile, setRightOpen, setRightOpenMobile]);
|
|
133
|
+
// Sidebar contexts
|
|
134
|
+
const leftSidebarContext: SidebarContextType = React.useMemo(
|
|
125
135
|
() => ({
|
|
126
|
-
state,
|
|
127
|
-
open,
|
|
128
|
-
setOpen,
|
|
136
|
+
state: leftOpen ? "expanded" : "collapsed",
|
|
137
|
+
open: leftOpen,
|
|
138
|
+
setOpen: setLeftOpen,
|
|
139
|
+
openMobile: leftOpenMobile,
|
|
140
|
+
setOpenMobile: setLeftOpenMobile,
|
|
129
141
|
isMobile,
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
toggleSidebar,
|
|
142
|
+
toggleSidebar: toggleLeftSidebar,
|
|
143
|
+
side: "left",
|
|
133
144
|
}),
|
|
134
|
-
[
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
145
|
+
[leftOpen, setLeftOpen, leftOpenMobile, isMobile, toggleLeftSidebar]
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const rightSidebarContext: SidebarContextType = React.useMemo(
|
|
149
|
+
() => ({
|
|
150
|
+
state: rightOpen ? "expanded" : "collapsed",
|
|
151
|
+
open: rightOpen,
|
|
152
|
+
setOpen: setRightOpen,
|
|
153
|
+
openMobile: rightOpenMobile,
|
|
154
|
+
setOpenMobile: setRightOpenMobile,
|
|
138
155
|
isMobile,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
]
|
|
156
|
+
toggleSidebar: toggleRightSidebar,
|
|
157
|
+
side: "right",
|
|
158
|
+
}),
|
|
159
|
+
[rightOpen, setRightOpen, rightOpenMobile, isMobile, toggleRightSidebar]
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const contextValue = React.useMemo<MultiSidebarContextType>(
|
|
163
|
+
() => ({
|
|
164
|
+
leftSidebar: leftSidebarContext,
|
|
165
|
+
rightSidebar: rightSidebarContext,
|
|
166
|
+
}),
|
|
167
|
+
[leftSidebarContext, rightSidebarContext]
|
|
143
168
|
);
|
|
144
169
|
|
|
145
170
|
return (
|
|
146
|
-
<
|
|
171
|
+
<MultiSidebarContext.Provider value={contextValue}>
|
|
147
172
|
<TooltipProvider delayDuration={0}>
|
|
148
173
|
<div
|
|
149
174
|
style={
|
|
150
175
|
{
|
|
151
176
|
"--sidebar-width": SIDEBAR_WIDTH,
|
|
152
177
|
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
|
|
178
|
+
"--right-sidebar-width": RIGHT_SIDEBAR_WIDTH,
|
|
153
179
|
...style,
|
|
154
180
|
} as React.CSSProperties
|
|
155
181
|
}
|
|
156
182
|
className={cn(
|
|
157
|
-
"
|
|
183
|
+
"items-center flex flex-col",
|
|
158
184
|
className,
|
|
159
185
|
)}
|
|
160
186
|
ref={ref}
|
|
@@ -163,16 +189,16 @@ const SidebarProvider = React.forwardRef<
|
|
|
163
189
|
{children}
|
|
164
190
|
</div>
|
|
165
191
|
</TooltipProvider>
|
|
166
|
-
</
|
|
192
|
+
</MultiSidebarContext.Provider>
|
|
167
193
|
);
|
|
168
194
|
},
|
|
169
195
|
);
|
|
170
|
-
|
|
196
|
+
MultiSidebarProvider.displayName = "MultiSidebarProvider";
|
|
171
197
|
|
|
172
198
|
const Sidebar = React.forwardRef<
|
|
173
199
|
HTMLDivElement,
|
|
174
200
|
React.ComponentProps<"div"> & {
|
|
175
|
-
side
|
|
201
|
+
side: "left" | "right";
|
|
176
202
|
variant?: "sidebar" | "floating" | "inset";
|
|
177
203
|
collapsible?: "offcanvas" | "icon" | "none";
|
|
178
204
|
}
|
|
@@ -186,15 +212,21 @@ const Sidebar = React.forwardRef<
|
|
|
186
212
|
children,
|
|
187
213
|
...props
|
|
188
214
|
},
|
|
189
|
-
ref
|
|
215
|
+
ref
|
|
190
216
|
) => {
|
|
191
|
-
const {
|
|
217
|
+
const {
|
|
218
|
+
[side === "left" ? "leftSidebar" : "rightSidebar"]: sidebarContext,
|
|
219
|
+
} = useMultiSidebar();
|
|
220
|
+
const { isMobile, state, openMobile, setOpenMobile } = sidebarContext;
|
|
192
221
|
|
|
193
222
|
if (collapsible === "none") {
|
|
194
223
|
return (
|
|
195
224
|
<aside
|
|
196
225
|
className={cn(
|
|
197
226
|
"flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground",
|
|
227
|
+
side === "left"
|
|
228
|
+
? "w-[--sidebar-width]"
|
|
229
|
+
: "w-[--right-sidebar-width]",
|
|
198
230
|
className,
|
|
199
231
|
)}
|
|
200
232
|
ref={ref}
|
|
@@ -219,10 +251,6 @@ const Sidebar = React.forwardRef<
|
|
|
219
251
|
}
|
|
220
252
|
side={side}
|
|
221
253
|
>
|
|
222
|
-
<SheetHeader className="sr-only">
|
|
223
|
-
<SheetTitle>Sidebar</SheetTitle>
|
|
224
|
-
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
|
|
225
|
-
</SheetHeader>
|
|
226
254
|
<div className="flex h-full w-full flex-col">{children}</div>
|
|
227
255
|
</SheetContent>
|
|
228
256
|
</Sheet>
|
|
@@ -241,62 +269,71 @@ const Sidebar = React.forwardRef<
|
|
|
241
269
|
{/* This is what handles the sidebar gap on desktop */}
|
|
242
270
|
<div
|
|
243
271
|
className={cn(
|
|
244
|
-
"relative
|
|
272
|
+
"duration-200 relative bg-transparent transition-[width] ease-linear",
|
|
245
273
|
"group-data-[collapsible=offcanvas]:w-0",
|
|
246
274
|
"group-data-[side=right]:rotate-180",
|
|
247
275
|
variant === "floating" || variant === "inset"
|
|
248
276
|
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
|
|
249
277
|
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon]",
|
|
278
|
+
side === "left"
|
|
279
|
+
? "w-[--sidebar-width]"
|
|
280
|
+
: "w-[--right-sidebar-width]"
|
|
250
281
|
)}
|
|
251
282
|
/>
|
|
252
283
|
<div
|
|
253
284
|
className={cn(
|
|
254
|
-
"fixed
|
|
285
|
+
"fixed h-full max-h-[calc(100%-81px)] z-10 hidden w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex",
|
|
255
286
|
side === "left"
|
|
256
|
-
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
|
257
|
-
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
|
287
|
+
? "left-0 w-[--sidebar-width] group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
|
288
|
+
: "right-0 w-[--right-sidebar-width] group-data-[collapsible=offcanvas]:right-[calc(var(--right-sidebar-width)*-1)]",
|
|
258
289
|
// Adjust the padding for floating and inset variants.
|
|
259
290
|
variant === "floating" || variant === "inset"
|
|
260
291
|
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
|
|
261
|
-
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r
|
|
262
|
-
className
|
|
292
|
+
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r",
|
|
293
|
+
className
|
|
263
294
|
)}
|
|
264
295
|
{...props}
|
|
265
296
|
>
|
|
266
297
|
<div
|
|
267
298
|
data-sidebar="sidebar"
|
|
268
|
-
className=
|
|
269
|
-
|
|
299
|
+
className={cn(
|
|
300
|
+
"flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow",
|
|
301
|
+
side === "left" && "bg-sidebar"
|
|
302
|
+
)}>
|
|
270
303
|
{children}
|
|
271
304
|
</div>
|
|
272
305
|
</div>
|
|
273
306
|
</div>
|
|
274
307
|
);
|
|
275
|
-
}
|
|
308
|
+
}
|
|
276
309
|
);
|
|
277
310
|
Sidebar.displayName = "Sidebar";
|
|
278
311
|
|
|
279
312
|
const SidebarTrigger = React.forwardRef<
|
|
280
313
|
React.ElementRef<typeof Button>,
|
|
281
|
-
React.ComponentProps<typeof Button>
|
|
282
|
-
>(({ className, onClick, ...props }, ref) => {
|
|
283
|
-
const {
|
|
314
|
+
React.ComponentProps<typeof Button> & { side?: "left" | "right" }
|
|
315
|
+
>(({ className, onClick, side = "left", variant = "ghost", size = "icon", ...props }, ref) => {
|
|
316
|
+
const { [side === "left" ? "leftSidebar" : "rightSidebar"]: sidebarContext } =
|
|
317
|
+
useMultiSidebar();
|
|
318
|
+
const { toggleSidebar } = sidebarContext;
|
|
284
319
|
|
|
285
320
|
return (
|
|
286
321
|
<Button
|
|
287
322
|
ref={ref}
|
|
288
323
|
data-sidebar="trigger"
|
|
289
|
-
variant=
|
|
290
|
-
size=
|
|
291
|
-
className={cn("h-
|
|
324
|
+
variant={variant}
|
|
325
|
+
size={size}
|
|
326
|
+
className={cn("h-8 w-8", className)}
|
|
292
327
|
onClick={(event) => {
|
|
293
328
|
onClick?.(event);
|
|
294
329
|
toggleSidebar();
|
|
295
330
|
}}
|
|
296
331
|
{...props}
|
|
297
332
|
>
|
|
298
|
-
<PanelLeft />
|
|
299
|
-
<span className="sr-only">
|
|
333
|
+
{side === "left" ? <PanelLeft className="!h-5 !w-5" /> : <Info className="!h-5 !w-5" />}
|
|
334
|
+
<span className="sr-only">
|
|
335
|
+
Toggle {side === "left" ? "Left" : "Right"} Sidebar
|
|
336
|
+
</span>
|
|
300
337
|
</Button>
|
|
301
338
|
);
|
|
302
339
|
});
|
|
@@ -304,9 +341,11 @@ SidebarTrigger.displayName = "SidebarTrigger";
|
|
|
304
341
|
|
|
305
342
|
const SidebarRail = React.forwardRef<
|
|
306
343
|
HTMLButtonElement,
|
|
307
|
-
React.ComponentProps<"button">
|
|
308
|
-
>(({ className, ...props }, ref) => {
|
|
309
|
-
const {
|
|
344
|
+
React.ComponentProps<"button"> & { side?: "left" | "right" }
|
|
345
|
+
>(({ className, side = "left", ...props }, ref) => {
|
|
346
|
+
const { [side === "left" ? "leftSidebar" : "rightSidebar"]: sidebarContext } =
|
|
347
|
+
useMultiSidebar();
|
|
348
|
+
const { toggleSidebar } = sidebarContext;
|
|
310
349
|
|
|
311
350
|
return (
|
|
312
351
|
<button
|
|
@@ -339,9 +378,9 @@ const SidebarInset = React.forwardRef<
|
|
|
339
378
|
<main
|
|
340
379
|
ref={ref}
|
|
341
380
|
className={cn(
|
|
342
|
-
"relative flex
|
|
343
|
-
"md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
|
|
344
|
-
className
|
|
381
|
+
"relative flex min-h-svh flex-1 flex-col bg-background",
|
|
382
|
+
"peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
|
|
383
|
+
className
|
|
345
384
|
)}
|
|
346
385
|
{...props}
|
|
347
386
|
/>
|
|
@@ -375,7 +414,7 @@ const SidebarHeader = React.forwardRef<
|
|
|
375
414
|
<div
|
|
376
415
|
ref={ref}
|
|
377
416
|
data-sidebar="header"
|
|
378
|
-
className={cn("flex flex-
|
|
417
|
+
className={cn("flex flex-row gap-2 p-2", className)}
|
|
379
418
|
{...props}
|
|
380
419
|
/>
|
|
381
420
|
);
|
|
@@ -558,10 +597,12 @@ const SidebarMenuButton = React.forwardRef<
|
|
|
558
597
|
asChild?: boolean;
|
|
559
598
|
isActive?: boolean;
|
|
560
599
|
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
|
|
600
|
+
side?: "left" | "right";
|
|
561
601
|
} & VariantProps<typeof sidebarMenuButtonVariants>
|
|
562
602
|
>(
|
|
563
603
|
(
|
|
564
604
|
{
|
|
605
|
+
side = "left",
|
|
565
606
|
asChild = false,
|
|
566
607
|
isActive = false,
|
|
567
608
|
variant = "default",
|
|
@@ -570,10 +611,13 @@ const SidebarMenuButton = React.forwardRef<
|
|
|
570
611
|
className,
|
|
571
612
|
...props
|
|
572
613
|
},
|
|
573
|
-
ref
|
|
614
|
+
ref
|
|
574
615
|
) => {
|
|
575
616
|
const Comp = asChild ? Slot : "button";
|
|
576
|
-
const {
|
|
617
|
+
const {
|
|
618
|
+
[side === "left" ? "leftSidebar" : "rightSidebar"]: sidebarContext,
|
|
619
|
+
} = useMultiSidebar();
|
|
620
|
+
const { isMobile, state } = sidebarContext;
|
|
577
621
|
|
|
578
622
|
const button = (
|
|
579
623
|
<Comp
|
|
@@ -779,9 +823,9 @@ export {
|
|
|
779
823
|
SidebarMenuSub,
|
|
780
824
|
SidebarMenuSubButton,
|
|
781
825
|
SidebarMenuSubItem,
|
|
782
|
-
|
|
826
|
+
MultiSidebarProvider,
|
|
783
827
|
SidebarRail,
|
|
784
828
|
SidebarSeparator,
|
|
785
829
|
SidebarTrigger,
|
|
786
|
-
|
|
787
|
-
};
|
|
830
|
+
useMultiSidebar,
|
|
831
|
+
};
|
package/src/hooks/use-mobile.tsx
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
|
|
3
|
-
const MOBILE_BREAKPOINT = 768;
|
|
4
|
-
|
|
5
|
-
export function useIsMobile() {
|
|
6
|
-
const [isMobile, setIsMobile] = useState<boolean | undefined>(undefined);
|
|
7
|
-
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
10
|
-
const onChange = () => {
|
|
11
|
-
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
12
|
-
};
|
|
13
|
-
mql.addEventListener("change", onChange);
|
|
14
|
-
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
15
|
-
return () => mql.removeEventListener("change", onChange);
|
|
16
|
-
}, []);
|
|
17
|
-
|
|
18
|
-
return !!isMobile;
|
|
19
|
-
}
|