@bccampus/ui-components 0.2.0 → 0.4.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/dist/banner.js +14 -14
- package/dist/button.d.ts +1 -1
- package/dist/button.js +17 -17
- package/dist/caption.js +7 -8
- package/dist/card.js +34 -34
- package/dist/composite.d.ts +151 -0
- package/dist/composite.js +472 -0
- package/dist/generate-tiles-DuagGD1d.js +244 -0
- package/dist/horizontal-list.js +32 -32
- package/dist/icon-generator.js +43 -270
- package/dist/igenerate-tiles.d.ts +43 -0
- package/dist/igenerate-tiles.js +7 -0
- package/dist/index-U7DVCmS_.js +76 -0
- package/dist/input.js +4 -4
- package/dist/masked-image-generator.js +18 -18
- package/dist/navigation-menu.d.ts +27 -0
- package/dist/navigation-menu.js +1139 -0
- package/dist/overlay.js +2 -2
- package/dist/page-header.d.ts +5 -1
- package/dist/page-header.js +10 -9
- package/dist/page-section.js +14 -14
- package/dist/page.js +4 -4
- package/dist/search-input.js +11 -11
- package/dist/tag.js +8 -9
- package/dist/ui-components.d.ts +30 -2
- package/dist/ui-components.js +47 -37
- package/package.json +17 -1
- package/src/components/ui/banner.tsx +2 -2
- package/src/components/ui/button.tsx +8 -7
- package/src/components/ui/card.tsx +5 -5
- package/src/components/ui/composite/CompositeData.ts +215 -0
- package/src/components/ui/composite/CompositeDataItem.ts +144 -0
- package/src/components/ui/composite/composite-component-item.tsx +50 -0
- package/src/components/ui/composite/composite-component.tsx +100 -0
- package/src/components/ui/composite/composite-data-context.tsx +31 -0
- package/src/components/ui/composite/index.ts +4 -0
- package/src/components/ui/composite/types.ts +81 -0
- package/src/components/ui/horizontal-list.tsx +2 -2
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/navigation-menu.tsx +165 -0
- package/src/components/ui/page-header.tsx +13 -5
- package/src/components/ui/page-section.tsx +8 -8
- package/src/components/ui/page.tsx +3 -1
- package/src/components/ui/popover.tsx +46 -0
- package/src/hooks/use-effect-after-mount.ts +27 -0
- package/src/hooks/use-id.ts +5 -0
- package/src/hooks/use-keyboard-event.ts +144 -0
- package/src/lib/object.ts +48 -0
- package/src/lib/set-operations.ts +52 -0
- package/src/styles/theme.css +7 -7
- package/src/styles/typography.css +334 -341
- package/tsconfig.node.json +25 -25
- package/vite.config.ts +7 -3
- package/vite.ladle.config.ts +17 -0
- package/dist/index-DlfV3JTY.js +0 -70
- package/dist/jsx-runtime-BzflLqGi.js +0 -282
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
|
|
2
|
+
import { ChevronDownIcon } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
import { buttonVariants } from "./button";
|
|
6
|
+
|
|
7
|
+
function NavigationMenu({
|
|
8
|
+
className,
|
|
9
|
+
children,
|
|
10
|
+
noViewport = false,
|
|
11
|
+
mega = false,
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
|
|
14
|
+
noViewport?: boolean;
|
|
15
|
+
mega?: boolean;
|
|
16
|
+
}) {
|
|
17
|
+
return (
|
|
18
|
+
<NavigationMenuPrimitive.Root
|
|
19
|
+
data-slot="navigation-menu"
|
|
20
|
+
data-mega={mega}
|
|
21
|
+
data-viewport={!noViewport || mega}
|
|
22
|
+
className={cn(
|
|
23
|
+
"group/navigation-menu flex max-w-max flex-1 items-center justify-center",
|
|
24
|
+
{ relative: !mega },
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
{(!noViewport || mega) && <NavigationMenuViewport mega={mega} />}
|
|
31
|
+
</NavigationMenuPrimitive.Root>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function NavigationMenuList({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
|
|
36
|
+
return (
|
|
37
|
+
<NavigationMenuPrimitive.List
|
|
38
|
+
data-slot="navigation-menu-list"
|
|
39
|
+
className={cn("group flex flex-1 list-none items-center justify-center gap-1", className)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function NavigationMenuItem({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
|
|
46
|
+
return (
|
|
47
|
+
<NavigationMenuPrimitive.Item data-slot="navigation-menu-item" className={cn("relative", className)} {...props} />
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const navigationMenuTriggerStyle = buttonVariants({
|
|
52
|
+
variant: "ghost",
|
|
53
|
+
className:
|
|
54
|
+
"data-[state=open]:hover:text-secondary data-[state=open]:focus:text-secondary data-[state=open]:text-secondary",
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
function NavigationMenuTrigger({
|
|
58
|
+
className,
|
|
59
|
+
children,
|
|
60
|
+
...props
|
|
61
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
|
|
62
|
+
return (
|
|
63
|
+
<NavigationMenuPrimitive.Trigger
|
|
64
|
+
data-slot="navigation-menu-trigger"
|
|
65
|
+
className={cn(navigationMenuTriggerStyle, "group", className)}
|
|
66
|
+
{...props}
|
|
67
|
+
>
|
|
68
|
+
{children}{" "}
|
|
69
|
+
<ChevronDownIcon
|
|
70
|
+
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
/>
|
|
73
|
+
</NavigationMenuPrimitive.Trigger>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function NavigationMenuContent({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
|
|
78
|
+
return (
|
|
79
|
+
<NavigationMenuPrimitive.Content
|
|
80
|
+
data-slot="navigation-menu-content"
|
|
81
|
+
className={cn(
|
|
82
|
+
"max-md:h-[calc(100vh-var(--spacing-page-nav))] top-0 left-0 min-w-xs w-auto absolute group-data-[mega=true]/navigation-menu:w-full",
|
|
83
|
+
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52",
|
|
84
|
+
"group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0",
|
|
85
|
+
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
|
|
86
|
+
className
|
|
87
|
+
)}
|
|
88
|
+
{...props}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function NavigationMenuViewport({
|
|
94
|
+
className,
|
|
95
|
+
mega = false,
|
|
96
|
+
...props
|
|
97
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport> & {
|
|
98
|
+
mega?: boolean;
|
|
99
|
+
}) {
|
|
100
|
+
return (
|
|
101
|
+
<div
|
|
102
|
+
className={cn("absolute top-full left-0 isolate z-50 flex justify-center bg-popover", {
|
|
103
|
+
"min-w-xs": !mega,
|
|
104
|
+
"w-full px-(--spacing-section)": mega,
|
|
105
|
+
})}
|
|
106
|
+
>
|
|
107
|
+
<NavigationMenuPrimitive.Viewport
|
|
108
|
+
data-slot="navigation-menu-viewport"
|
|
109
|
+
className={cn(
|
|
110
|
+
"origin-top-center duration-150 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in",
|
|
111
|
+
"bg-popover text-popover-foreground relative overflow-hidden w-full h-[var(--radix-navigation-menu-viewport-height)] overscroll-contain",
|
|
112
|
+
{
|
|
113
|
+
"mt-1.5 rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]": !mega,
|
|
114
|
+
"border-b-1 border-complement-1-50": mega,
|
|
115
|
+
},
|
|
116
|
+
className
|
|
117
|
+
)}
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function NavigationMenuLink({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
|
|
125
|
+
return (
|
|
126
|
+
<NavigationMenuPrimitive.Link
|
|
127
|
+
data-slot="navigation-menu-link"
|
|
128
|
+
className={cn(
|
|
129
|
+
"[&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
|
|
130
|
+
className
|
|
131
|
+
)}
|
|
132
|
+
{...props}
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function NavigationMenuIndicator({
|
|
138
|
+
className,
|
|
139
|
+
...props
|
|
140
|
+
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
|
|
141
|
+
return (
|
|
142
|
+
<NavigationMenuPrimitive.Indicator
|
|
143
|
+
data-slot="navigation-menu-indicator"
|
|
144
|
+
className={cn(
|
|
145
|
+
"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
|
|
146
|
+
className
|
|
147
|
+
)}
|
|
148
|
+
{...props}
|
|
149
|
+
>
|
|
150
|
+
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
|
|
151
|
+
</NavigationMenuPrimitive.Indicator>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export {
|
|
156
|
+
NavigationMenu,
|
|
157
|
+
NavigationMenuList,
|
|
158
|
+
NavigationMenuItem,
|
|
159
|
+
NavigationMenuContent,
|
|
160
|
+
NavigationMenuTrigger,
|
|
161
|
+
NavigationMenuLink,
|
|
162
|
+
NavigationMenuIndicator,
|
|
163
|
+
NavigationMenuViewport,
|
|
164
|
+
navigationMenuTriggerStyle,
|
|
165
|
+
};
|
|
@@ -1,14 +1,22 @@
|
|
|
1
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
1
2
|
import { PageSection } from "./page-section";
|
|
2
3
|
import { LogoBCcampusWithTagline } from "@bccampus/media-kit";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
export type PageHeaderProps = React.ComponentProps<"div"> & {
|
|
6
|
+
asChild?: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function PageHeader({ children, asChild, ...props }: PageHeaderProps) {
|
|
10
|
+
const Comp = asChild ? Slot : "div";
|
|
5
11
|
return (
|
|
6
|
-
<PageSection py="none" className="sticky top-0 right-0 left-0 z-
|
|
7
|
-
<
|
|
8
|
-
<
|
|
12
|
+
<PageSection py="none" className="sticky top-0 right-0 left-0 z-50 bg-background" {...props}>
|
|
13
|
+
<Comp className="h-page-nav overflow-hidden flex flex-row flex-nowrap border-b-1 border-b-complement-1-50 gap-2 sm:gap-12 justify-between items-center sm:justify-start">
|
|
14
|
+
<div className="py-4 h-full">
|
|
15
|
+
<LogoBCcampusWithTagline height="100%" variant="color" />
|
|
16
|
+
</div>
|
|
9
17
|
|
|
10
18
|
{children}
|
|
11
|
-
</
|
|
19
|
+
</Comp>
|
|
12
20
|
</PageSection>
|
|
13
21
|
);
|
|
14
22
|
}
|
|
@@ -5,17 +5,17 @@ const pageSectionVariants = cva("group @container/page-section relative w-full",
|
|
|
5
5
|
variants: {
|
|
6
6
|
px: {
|
|
7
7
|
none: "px-0",
|
|
8
|
-
default: "px-section",
|
|
9
|
-
sm: "px-
|
|
10
|
-
lg: "px-section-lg",
|
|
11
|
-
xl: "px-section-xl",
|
|
8
|
+
default: "px-(--spacing-section)",
|
|
9
|
+
sm: "px-(--spacing-section-sm)",
|
|
10
|
+
lg: "px-(--spacing-section-lg)",
|
|
11
|
+
xl: "px-(--spacing-section-xl)",
|
|
12
12
|
},
|
|
13
13
|
py: {
|
|
14
14
|
none: "py-0",
|
|
15
|
-
default: "py-section",
|
|
16
|
-
sm: "py-
|
|
17
|
-
lg: "py-section-lg",
|
|
18
|
-
xl: "py-section-xl",
|
|
15
|
+
default: "py-(--spacing-section)",
|
|
16
|
+
sm: "py-(--spacing-section-sm)",
|
|
17
|
+
lg: "py-(--spacing-section-lg)",
|
|
18
|
+
xl: "py-(--spacing-section-xl)",
|
|
19
19
|
},
|
|
20
20
|
},
|
|
21
21
|
defaultVariants: {
|
|
@@ -3,7 +3,9 @@ import { cn } from "@/lib/utils";
|
|
|
3
3
|
export type PageProps = React.ComponentProps<"div">;
|
|
4
4
|
|
|
5
5
|
function Page({ className, ...props }: PageProps) {
|
|
6
|
-
return
|
|
6
|
+
return (
|
|
7
|
+
<div data-slot="page" className={cn("group/page @container/page relative w-full", className)} {...props} />
|
|
8
|
+
);
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
export { Page };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
function Popover({
|
|
7
|
+
...props
|
|
8
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
|
9
|
+
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function PopoverTrigger({
|
|
13
|
+
...props
|
|
14
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
15
|
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function PopoverContent({
|
|
19
|
+
className,
|
|
20
|
+
align = "center",
|
|
21
|
+
sideOffset = 4,
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
|
24
|
+
return (
|
|
25
|
+
<PopoverPrimitive.Portal>
|
|
26
|
+
<PopoverPrimitive.Content
|
|
27
|
+
data-slot="popover-content"
|
|
28
|
+
align={align}
|
|
29
|
+
sideOffset={sideOffset}
|
|
30
|
+
className={cn(
|
|
31
|
+
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
|
32
|
+
className
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
</PopoverPrimitive.Portal>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function PopoverAnchor({
|
|
41
|
+
...props
|
|
42
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
|
43
|
+
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DependencyList, EffectCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
|
|
5
|
+
export function useEffectAfterMount(fn: EffectCallback, dependencies?: DependencyList) {
|
|
6
|
+
const mounted = useRef(false);
|
|
7
|
+
|
|
8
|
+
useEffect(
|
|
9
|
+
() => () => {
|
|
10
|
+
mounted.current = false;
|
|
11
|
+
},
|
|
12
|
+
[]
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
useEffect(
|
|
16
|
+
() => {
|
|
17
|
+
if (mounted.current) {
|
|
18
|
+
return fn();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
mounted.current = true;
|
|
22
|
+
return undefined;
|
|
23
|
+
},
|
|
24
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
25
|
+
dependencies
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { KeyboardEvent, KeyboardEventHandler } from 'react';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
const MODIFIER_KEYS = new Set(['ctrl', 'shift', 'alt', 'meta']);
|
|
5
|
+
|
|
6
|
+
const KEY_MAPPINGS: Record<string, string> = {
|
|
7
|
+
' ': 'space',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export interface KeyBindings {
|
|
11
|
+
[sequence: string]: (event: KeyboardEvent) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface KeybindingLookupItem {
|
|
15
|
+
sequence: Set<string>;
|
|
16
|
+
handler: (event: KeyboardEvent) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface UseKeyboardEventOptions {
|
|
20
|
+
eventKeyProp: 'key' | 'code';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isSequenceEqual<T>(sequenceA: Set<T>, sequenceB: Set<T>) {
|
|
24
|
+
if (sequenceA.size !== sequenceB.size) return false;
|
|
25
|
+
|
|
26
|
+
for (const element of sequenceB) {
|
|
27
|
+
if (!sequenceA.has(element)) return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseKeybindings(bindings: KeyBindings) {
|
|
34
|
+
const parsedKeybindings: KeybindingLookupItem[] = [];
|
|
35
|
+
for (const [sequence, handler] of Object.entries(bindings)) {
|
|
36
|
+
const parsedSequence = sequence
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.trim()
|
|
39
|
+
.split(/\s*\+\s*/);
|
|
40
|
+
|
|
41
|
+
if (parsedSequence.length === 1 && MODIFIER_KEYS.has(parsedSequence[0])) {
|
|
42
|
+
console.error(`[useKeyboardEvent] \`${sequence}\`: A key sequence cannot be only a modifier key.`);
|
|
43
|
+
}
|
|
44
|
+
else if (parsedSequence.includes('')) {
|
|
45
|
+
console.error(`[useKeyboardEvent] \`${sequence}\`: Unknown key defined in the sequence.`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
parsedKeybindings.push({
|
|
49
|
+
sequence: new Set(parsedSequence),
|
|
50
|
+
handler,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return parsedKeybindings;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const defaultOptions: UseKeyboardEventOptions = {
|
|
59
|
+
eventKeyProp: 'key',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export function keyboardEventHandler(
|
|
63
|
+
bindings: KeyBindings,
|
|
64
|
+
options: UseKeyboardEventOptions = defaultOptions
|
|
65
|
+
): KeyboardEventHandler {
|
|
66
|
+
const _options = { ...options, ...defaultOptions }
|
|
67
|
+
|
|
68
|
+
const keyBindings = parseKeybindings(bindings);
|
|
69
|
+
|
|
70
|
+
return (event: KeyboardEvent) => {
|
|
71
|
+
const keySequence = new Set<string>();
|
|
72
|
+
const eventKey = event[_options.eventKeyProp];
|
|
73
|
+
|
|
74
|
+
if (event.ctrlKey) keySequence.add('ctrl');
|
|
75
|
+
|
|
76
|
+
if (event.shiftKey) keySequence.add('shift');
|
|
77
|
+
|
|
78
|
+
if (event.altKey) keySequence.add('alt');
|
|
79
|
+
|
|
80
|
+
if (event.metaKey) keySequence.add('meta');
|
|
81
|
+
|
|
82
|
+
if (!KEY_MAPPINGS[eventKey]) keySequence.add(eventKey.toLowerCase());
|
|
83
|
+
else keySequence.add(KEY_MAPPINGS[eventKey]);
|
|
84
|
+
|
|
85
|
+
const matchedSequence = keyBindings.find(keyBinding => isSequenceEqual(keySequence, keyBinding.sequence));
|
|
86
|
+
if (matchedSequence) {
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
event.stopPropagation();
|
|
89
|
+
matchedSequence.handler(event);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns a `KeyboardEventHandler`
|
|
96
|
+
* that checks the defined key binding sequences against a keyboard event
|
|
97
|
+
* and executes the handler of the first matched key binding.
|
|
98
|
+
*
|
|
99
|
+
* Limitations:
|
|
100
|
+
* - Space character (` `) cannot be used in the key sequences.
|
|
101
|
+
* Use the `space` keyword instead.
|
|
102
|
+
* - Plus character (`+`) cannot be used in the key sequences.
|
|
103
|
+
* Use `shit + =` instead.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```
|
|
107
|
+
* export function Input({ value, onChange }: Props) {
|
|
108
|
+
* const [inputValue, setInputValue] = useState<string>('');
|
|
109
|
+
*
|
|
110
|
+
* const clearInput = () => {
|
|
111
|
+
* setInputValue('');
|
|
112
|
+
* };
|
|
113
|
+
*
|
|
114
|
+
* const addItem = () => {
|
|
115
|
+
* if (inputValue) {
|
|
116
|
+
* onChange([...value, inputValue]);
|
|
117
|
+
* clearInput();
|
|
118
|
+
* }
|
|
119
|
+
* };
|
|
120
|
+
*
|
|
121
|
+
* const deleteAll = () => {
|
|
122
|
+
* onChange([]);
|
|
123
|
+
* clearInput();
|
|
124
|
+
* };
|
|
125
|
+
*
|
|
126
|
+
* const handleKeyDown = useKeyboardEvent({
|
|
127
|
+
* 'enter': addItem,
|
|
128
|
+
* 'escape': clearInput,
|
|
129
|
+
* 'ctrl+c': clearInput,
|
|
130
|
+
* 'ctrl + shift + c': deleteAll,
|
|
131
|
+
* });
|
|
132
|
+
*
|
|
133
|
+
* return (
|
|
134
|
+
* <input
|
|
135
|
+
* value={inputValue}
|
|
136
|
+
* onChange={event => setInputValue(event.target.value)}
|
|
137
|
+
* onKeyDown={handleKeyDown}
|
|
138
|
+
* />;
|
|
139
|
+
* }
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export function useKeyboardEvent(bindings: KeyBindings, options?: UseKeyboardEventOptions) {
|
|
143
|
+
return useMemo(() => keyboardEventHandler(bindings, options), [bindings, options]);
|
|
144
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
export const get = <T extends object>(object: T, prop: string) => prop
|
|
4
|
+
.split('.')
|
|
5
|
+
.reduce<any>((reducedObject, key) => (reducedObject && key in reducedObject) ? reducedObject[key] : undefined, object);
|
|
6
|
+
|
|
7
|
+
export const set = <T extends object, V>(object: T, prop: string, value: V) => {
|
|
8
|
+
const propChunks = prop.split('.');
|
|
9
|
+
const lastChunk = propChunks.pop();
|
|
10
|
+
if (!lastChunk) return object;
|
|
11
|
+
|
|
12
|
+
const ref = propChunks.reduce<any>((reducedObject, key) => {
|
|
13
|
+
reducedObject[key] = {};
|
|
14
|
+
return reducedObject[key];
|
|
15
|
+
}, object);
|
|
16
|
+
|
|
17
|
+
ref[lastChunk] = value;
|
|
18
|
+
|
|
19
|
+
return object;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const pick = <T extends object>(object: T, props: string[]) => {
|
|
23
|
+
|
|
24
|
+
return props.reduce<Record<string, unknown>>((result, key) => {
|
|
25
|
+
|
|
26
|
+
set(result, key, get(object, key));
|
|
27
|
+
|
|
28
|
+
return result;
|
|
29
|
+
}, {}) as Partial<T>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const omit = <T extends object>(object: T, props: string[]) => {
|
|
33
|
+
const result: Partial<T> = { ...object };
|
|
34
|
+
|
|
35
|
+
props.forEach(prop => {
|
|
36
|
+
const propChunks = prop.split('.');
|
|
37
|
+
const lastChunk = propChunks.pop();
|
|
38
|
+
if (lastChunk) {
|
|
39
|
+
const ref = propChunks.reduce<any>((reducedObject, key) => (reducedObject && key in reducedObject) ? reducedObject[key] : undefined, result);
|
|
40
|
+
if (ref && lastChunk in ref) delete ref[lastChunk];
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const isObject = (object: unknown) => (typeof object === 'object' && !Array.isArray(object) && object !== null);
|
|
48
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
export function isSuperset(set: Iterable<any>, subset: Iterable<any>) {
|
|
4
|
+
const _set = set instanceof Set ? set : new Set(set);
|
|
5
|
+
|
|
6
|
+
for (const elem of subset) {
|
|
7
|
+
if (!_set.has(elem)) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function union(setA: Iterable<any>, setB: Iterable<any>) {
|
|
15
|
+
const _union = new Set(setA);
|
|
16
|
+
for (const elem of setB) {
|
|
17
|
+
_union.add(elem);
|
|
18
|
+
}
|
|
19
|
+
return _union;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function intersection(setA: Iterable<any>, setB: Iterable<any>) {
|
|
23
|
+
const _setA = setA instanceof Set ? setA : new Set(setA);
|
|
24
|
+
|
|
25
|
+
const _intersection = new Set();
|
|
26
|
+
for (const elem of setB) {
|
|
27
|
+
if (_setA.has(elem)) {
|
|
28
|
+
_intersection.add(elem);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return _intersection;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function symmetricDifference(setA: Iterable<any>, setB: Iterable<any>) {
|
|
35
|
+
const _difference = new Set(setA);
|
|
36
|
+
for (const elem of setB) {
|
|
37
|
+
if (_difference.has(elem)) {
|
|
38
|
+
_difference.delete(elem);
|
|
39
|
+
} else {
|
|
40
|
+
_difference.add(elem);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return _difference;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function difference(setA: Iterable<any>, setB: Iterable<any>) {
|
|
47
|
+
const _difference = new Set(setA);
|
|
48
|
+
for (const elem of setB) {
|
|
49
|
+
_difference.delete(elem);
|
|
50
|
+
}
|
|
51
|
+
return _difference;
|
|
52
|
+
}
|
package/src/styles/theme.css
CHANGED
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
--secondary: oklch(0.5393 0.0909 200);
|
|
70
70
|
--secondary-foreground: oklch(0.985 0 0);
|
|
71
71
|
--muted: oklch(0.967 0.001 286.375);
|
|
72
|
-
--muted-foreground: oklch(0.
|
|
72
|
+
--muted-foreground: oklch(0.45 0.0236 285.938);
|
|
73
73
|
--accent: oklch(0.967 0.001 286.375);
|
|
74
74
|
--accent-foreground: oklch(0.3741 0.0774 245.65);
|
|
75
75
|
--destructive: oklch(0.577 0.245 27.325);
|
|
@@ -91,11 +91,11 @@
|
|
|
91
91
|
--sidebar-ring: oklch(0.5393 0.0909 199.73);
|
|
92
92
|
|
|
93
93
|
/* Responsive Layout Spacing */
|
|
94
|
-
--section-p: --spacing(
|
|
95
|
-
--card-p: --spacing(
|
|
94
|
+
--section-p: --spacing(4);
|
|
95
|
+
--card-p: --spacing(4);
|
|
96
96
|
--page-gap: --spacing(0);
|
|
97
|
-
--card-gap: --spacing(
|
|
98
|
-
--card-area-gap: --spacing(
|
|
97
|
+
--card-gap: --spacing(4);
|
|
98
|
+
--card-area-gap: --spacing(4);
|
|
99
99
|
--card-item-group-gap: --spacing(2);
|
|
100
100
|
|
|
101
101
|
@media (width >= 40rem) {
|
|
@@ -169,8 +169,8 @@
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
@utility areas-* {
|
|
172
|
-
grid-template-areas: --value(*, [*]);
|
|
173
|
-
grid-template-columns: --modifier(*, [*]);
|
|
172
|
+
grid-template-areas: --value(*, [ *]);
|
|
173
|
+
grid-template-columns: --modifier(*, [ *]);
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
@utility stick-to-page {
|