margo-ui 2.0.1 → 3.0.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/package.json +1 -1
- package/src/components/checkbox/checkbox.css +28 -0
- package/src/components/checkbox/checkbox.tsx +30 -0
- package/src/components/checkbox/style.ts +5 -0
- package/src/components/checkbox/type.ts +5 -0
- package/src/components/chip/style.ts +3 -2
- package/src/components/input/style.ts +1 -1
- package/src/components/layer/layer.tsx +8 -0
- package/src/components/popover/popover.css +18 -0
- package/src/components/popover/popover.tsx +113 -0
- package/src/components/popover/style.ts +39 -0
- package/src/components/popover/type.ts +19 -0
- package/src/components/select/select.tsx +49 -0
- package/src/components/select/style.ts +15 -0
- package/src/components/select/type.ts +13 -0
- package/src/components/splash/splash.tsx +3 -5
- package/src/components/splash/style.ts +3 -1
- package/src/components/toggle/style.ts +4 -0
- package/src/components/toggle/toggle.css +18 -0
- package/src/components/toggle/toggle.tsx +10 -0
- package/src/components/toggle/type.ts +3 -0
- package/src/css/theme.css +0 -1
- package/src/css/variables.css +5 -28
- package/src/index.ts +9 -2
- package/src/utils/support/support.ts +2 -0
- package/src/components/snippet/highlight.ts +0 -83
- package/src/components/snippet/snippet.tsx +0 -66
- package/src/components/snippet/style.ts +0 -25
- package/src/components/snippet/type.ts +0 -26
package/package.json
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.margo-checkbox {
|
|
2
|
+
appearance: none;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.margo-checkbox::before {
|
|
6
|
+
content: "";
|
|
7
|
+
display: block;
|
|
8
|
+
block-size: 100%;
|
|
9
|
+
inline-size: 100%;
|
|
10
|
+
background-color: currentColor;
|
|
11
|
+
clip-path: polygon(14% 44%, 0 60%, 39% 100%, 100% 16%, 85% 0, 39% 69%);
|
|
12
|
+
scale: 0.6;
|
|
13
|
+
opacity: 0;
|
|
14
|
+
transition:
|
|
15
|
+
scale 160ms ease-out,
|
|
16
|
+
opacity 160ms ease-out;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.margo-checkbox:checked::before {
|
|
20
|
+
scale: 1;
|
|
21
|
+
opacity: 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.margo-checkbox:indeterminate::before {
|
|
25
|
+
clip-path: inset(44% 12% 44% 12% round 1px);
|
|
26
|
+
scale: 1;
|
|
27
|
+
opacity: 1;
|
|
28
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
4
|
+
import { checkboxBaseClassName } from "./style.js";
|
|
5
|
+
|
|
6
|
+
import type { CheckboxProps } from "./type.js";
|
|
7
|
+
|
|
8
|
+
import "./checkbox.css";
|
|
9
|
+
|
|
10
|
+
export const Checkbox = ({ indeterminate = false, className, ref, ...rest }: CheckboxProps) => {
|
|
11
|
+
const checkboxRef = useRef<HTMLInputElement>(null);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (checkboxRef.current) checkboxRef.current.indeterminate = indeterminate;
|
|
15
|
+
}, [indeterminate]);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<input
|
|
19
|
+
{...rest}
|
|
20
|
+
ref={(node) => {
|
|
21
|
+
checkboxRef.current = node;
|
|
22
|
+
|
|
23
|
+
if (typeof ref === "function") ref(node);
|
|
24
|
+
else if (ref) ref.current = node;
|
|
25
|
+
}}
|
|
26
|
+
type="checkbox"
|
|
27
|
+
className={cn(checkboxBaseClassName, className)}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const checkboxBaseClassName = `margo-checkbox size-5 flex-none cursor-pointer rounded-md border-2 border-border
|
|
2
|
+
bg-main p-1 text-on-main enabled:hover:border-on-main focus-visible:border-primary focus-visible:outline
|
|
3
|
+
focus-visible:outline-offset-1 focus-visible:outline-on-main checked:border-primary indeterminate:border-primary
|
|
4
|
+
select-none origin-center transition-transform duration-[120ms] ease-out active:scale-[0.95] disabled:cursor-auto
|
|
5
|
+
disabled:opacity-40`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export const chipBaseClassName =
|
|
2
|
-
|
|
1
|
+
export const chipBaseClassName = `bg-on-main/8 margo-text-box-trim flex items-center justify-center px-3 py-0.5
|
|
2
|
+
whitespace-nowrap lowercase flex-none leading-none rounded-md text-mc shadow-chip
|
|
3
|
+
focus-visible:outline focus-visible:outline-offset-1 focus-visible:outline-on-main`;
|
|
3
4
|
|
|
4
5
|
export const chipActiveClassName = "bg-on-main text-main";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const inputBaseClassName = `group/input flex w-full cursor-text items-center gap-1 rounded-lg border-2 px-3 py-2 text-on-main
|
|
2
2
|
text-sm select-none min-h-[2.5rem] bg-low
|
|
3
|
-
border-transparent text-medium hover:border-on-main hover:text-on-main hover:shadow-
|
|
3
|
+
border-transparent text-medium hover:border-on-main hover:text-on-main hover:shadow-item
|
|
4
4
|
focus-within:border-primary focus-within:text-on-main
|
|
5
5
|
has-[input:disabled]:pointer-events-none has-[input:disabled]:cursor-auto has-[input:disabled]:opacity-40`;
|
|
6
6
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
|
|
3
3
|
import { cn } from "../../utils/cn/cn.js";
|
|
4
|
+
import { isMargoLayerSupported } from "../../utils/support/support.js";
|
|
4
5
|
import { layerBaseClassName } from "./style.js";
|
|
5
6
|
|
|
6
7
|
import type { MouseEvent, SyntheticEvent } from "react";
|
|
@@ -21,6 +22,13 @@ export const Layer = ({ open = false, onClose, dismissible = true, className, ch
|
|
|
21
22
|
const layer = layerRef.current;
|
|
22
23
|
|
|
23
24
|
if (!layer) return;
|
|
25
|
+
|
|
26
|
+
if (!isMargoLayerSupported()) {
|
|
27
|
+
setIsMounted(open);
|
|
28
|
+
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
if (open && !layer.open) layer.showModal();
|
|
25
33
|
if (open || !layer.open) return;
|
|
26
34
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
.margo-popover {
|
|
2
|
+
opacity: 0;
|
|
3
|
+
scale: 0.98;
|
|
4
|
+
transition:
|
|
5
|
+
opacity var(--margo-popover-duration) var(--margo-popover-easing),
|
|
6
|
+
scale var(--margo-popover-duration) var(--margo-popover-easing),
|
|
7
|
+
visibility var(--margo-popover-duration) var(--margo-popover-easing);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.margo-popover:not([data-margo-open="true"]) {
|
|
11
|
+
visibility: hidden;
|
|
12
|
+
pointer-events: none;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.margo-popover[data-margo-open="true"] {
|
|
16
|
+
opacity: 1;
|
|
17
|
+
scale: 1;
|
|
18
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { Tag } from "react-renderable";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
6
|
+
import {
|
|
7
|
+
popoverAnchorClassName,
|
|
8
|
+
popoverBaseClassName,
|
|
9
|
+
popoverBodyClassName,
|
|
10
|
+
popoverOriginClassName,
|
|
11
|
+
popoverPositionClassName,
|
|
12
|
+
} from "./style.js";
|
|
13
|
+
|
|
14
|
+
import type { ElementType } from "react";
|
|
15
|
+
import type { PopoverAnchorProps, PopoverBodyProps, PopoverProps } from "./type.js";
|
|
16
|
+
|
|
17
|
+
import "./popover.css";
|
|
18
|
+
|
|
19
|
+
export const Popover = ({
|
|
20
|
+
open = false,
|
|
21
|
+
onClose,
|
|
22
|
+
dismissible = true,
|
|
23
|
+
position = "down",
|
|
24
|
+
align = "center",
|
|
25
|
+
className,
|
|
26
|
+
children,
|
|
27
|
+
...rest
|
|
28
|
+
}: PopoverProps) => {
|
|
29
|
+
const popoverRef = useRef<HTMLDivElement>(null);
|
|
30
|
+
const childrenRef = useRef(children);
|
|
31
|
+
|
|
32
|
+
const [isMounted, setIsMounted] = useState(open);
|
|
33
|
+
|
|
34
|
+
if (open) childrenRef.current = children;
|
|
35
|
+
if (open && !isMounted) setIsMounted(true);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const popover = popoverRef.current;
|
|
39
|
+
|
|
40
|
+
if (!popover || open || !isMounted) return;
|
|
41
|
+
|
|
42
|
+
const controller = new AbortController();
|
|
43
|
+
|
|
44
|
+
const frame = requestAnimationFrame(() => {
|
|
45
|
+
const animations = popover.getAnimations({ subtree: true }).map((animation) => animation.finished);
|
|
46
|
+
|
|
47
|
+
void Promise.allSettled(animations).then(() => {
|
|
48
|
+
if (controller.signal.aborted) return;
|
|
49
|
+
|
|
50
|
+
setIsMounted(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return () => {
|
|
55
|
+
controller.abort();
|
|
56
|
+
cancelAnimationFrame(frame);
|
|
57
|
+
};
|
|
58
|
+
}, [isMounted, open]);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const popover = popoverRef.current;
|
|
62
|
+
|
|
63
|
+
if (!popover || !open || !dismissible) return;
|
|
64
|
+
|
|
65
|
+
const anchor = popover.offsetParent;
|
|
66
|
+
const controller = new AbortController();
|
|
67
|
+
|
|
68
|
+
const handlePointerDown = (event: PointerEvent) => {
|
|
69
|
+
const target = event.target as Node | null;
|
|
70
|
+
|
|
71
|
+
if (!target || popover.contains(target) || anchor?.contains(target)) return;
|
|
72
|
+
|
|
73
|
+
onClose?.();
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
77
|
+
if (event.key === "Escape") onClose?.();
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
document.addEventListener("pointerdown", handlePointerDown, { signal: controller.signal });
|
|
81
|
+
document.addEventListener("keydown", handleKeyDown, { signal: controller.signal });
|
|
82
|
+
|
|
83
|
+
return () => controller.abort();
|
|
84
|
+
}, [dismissible, onClose, open]);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div
|
|
88
|
+
{...rest}
|
|
89
|
+
ref={popoverRef}
|
|
90
|
+
data-margo-open={open}
|
|
91
|
+
className={cn(
|
|
92
|
+
popoverBaseClassName,
|
|
93
|
+
popoverPositionClassName[position][align],
|
|
94
|
+
popoverOriginClassName[position],
|
|
95
|
+
className,
|
|
96
|
+
)}
|
|
97
|
+
>
|
|
98
|
+
{isMounted ? childrenRef.current : null}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
Popover.Anchor = <T extends ElementType = "div">({ className, children, ...rest }: PopoverAnchorProps<T>) => (
|
|
104
|
+
<Tag {...Tag.forward<T>(rest)} className={cn(popoverAnchorClassName, className)}>
|
|
105
|
+
{children}
|
|
106
|
+
</Tag>
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
Popover.Body = <T extends ElementType = "div">({ className, children, ...rest }: PopoverBodyProps<T>) => (
|
|
110
|
+
<Tag {...Tag.forward<T>(rest)} className={cn(popoverBodyClassName, className)}>
|
|
111
|
+
{children}
|
|
112
|
+
</Tag>
|
|
113
|
+
);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { PopoverAlign, PopoverPosition } from "./type.js";
|
|
2
|
+
|
|
3
|
+
export const popoverAnchorClassName = "relative inline-block size-fit";
|
|
4
|
+
|
|
5
|
+
export const popoverBaseClassName = `margo-popover absolute z-20 m-0 flex w-max max-w-[calc(100vw-2rem)] flex-col
|
|
6
|
+
overflow-clip rounded-lg border-2 border-border bg-main text-on-main shadow-card outline-none`;
|
|
7
|
+
|
|
8
|
+
export const popoverBodyClassName = `flex min-h-0 max-h-56 flex-col gap-1
|
|
9
|
+
overflow-y-auto overscroll-contain w-64 p-2 text-sm`;
|
|
10
|
+
|
|
11
|
+
export const popoverPositionClassName: Record<PopoverPosition, Record<PopoverAlign, string>> = {
|
|
12
|
+
up: {
|
|
13
|
+
start: "bottom-full left-0 my-1",
|
|
14
|
+
center: "bottom-full left-1/2 my-1 -translate-x-1/2",
|
|
15
|
+
end: "right-0 bottom-full my-1",
|
|
16
|
+
},
|
|
17
|
+
down: {
|
|
18
|
+
start: "top-full left-0 my-1",
|
|
19
|
+
center: "top-full left-1/2 my-1 -translate-x-1/2",
|
|
20
|
+
end: "top-full right-0 my-1",
|
|
21
|
+
},
|
|
22
|
+
left: {
|
|
23
|
+
start: "top-0 right-full mx-1",
|
|
24
|
+
center: "top-1/2 right-full mx-1 -translate-y-1/2",
|
|
25
|
+
end: "right-full bottom-0 mx-1",
|
|
26
|
+
},
|
|
27
|
+
right: {
|
|
28
|
+
start: "top-0 left-full mx-1",
|
|
29
|
+
center: "top-1/2 left-full mx-1 -translate-y-1/2",
|
|
30
|
+
end: "bottom-0 left-full mx-1",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const popoverOriginClassName: Record<PopoverPosition, string> = {
|
|
35
|
+
up: "origin-bottom",
|
|
36
|
+
down: "origin-top",
|
|
37
|
+
left: "origin-right",
|
|
38
|
+
right: "origin-left",
|
|
39
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TagProps } from "react-renderable";
|
|
2
|
+
import type { ComponentPropsWithoutRef, ElementType, ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
export type PopoverPosition = "up" | "down" | "left" | "right";
|
|
5
|
+
|
|
6
|
+
export type PopoverAlign = "start" | "center" | "end";
|
|
7
|
+
|
|
8
|
+
export type PopoverProps = ComponentPropsWithoutRef<"div"> & {
|
|
9
|
+
open?: boolean;
|
|
10
|
+
onClose?: () => void;
|
|
11
|
+
dismissible?: boolean;
|
|
12
|
+
position?: PopoverPosition;
|
|
13
|
+
align?: PopoverAlign;
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type PopoverAnchorProps<T extends ElementType = "div"> = TagProps<T>;
|
|
18
|
+
|
|
19
|
+
export type PopoverBodyProps<T extends ElementType = "div"> = TagProps<T>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { MdKeyboardArrowDown } from "react-icons/md";
|
|
2
|
+
|
|
3
|
+
import { List } from "react-renderable";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
6
|
+
import { selectActiveClassName, selectBaseClassName, selectControlClassName, selectIconClassName } from "./style.js";
|
|
7
|
+
|
|
8
|
+
import type { SelectProps } from "./type.js";
|
|
9
|
+
|
|
10
|
+
export const Select = <T,>({
|
|
11
|
+
options = [],
|
|
12
|
+
itemExtractor = null,
|
|
13
|
+
valueExtractor = null,
|
|
14
|
+
placeholder = "",
|
|
15
|
+
canBeEmpty = false,
|
|
16
|
+
active = false,
|
|
17
|
+
clickable = true,
|
|
18
|
+
className,
|
|
19
|
+
...rest
|
|
20
|
+
}: SelectProps<T>) => {
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
data-margo-active={active}
|
|
24
|
+
inert={!clickable || undefined}
|
|
25
|
+
className={cn(selectBaseClassName, active && selectActiveClassName, className)}
|
|
26
|
+
>
|
|
27
|
+
<select {...rest} className={selectControlClassName}>
|
|
28
|
+
{placeholder ? (
|
|
29
|
+
<option value="" disabled={!canBeEmpty} hidden={!canBeEmpty}>
|
|
30
|
+
{placeholder}
|
|
31
|
+
</option>
|
|
32
|
+
) : null}
|
|
33
|
+
|
|
34
|
+
<List
|
|
35
|
+
array={options}
|
|
36
|
+
itemExtractor={({ row, index }) => (
|
|
37
|
+
<option key={valueExtractor?.({ row, index }) ?? index} value={valueExtractor?.({ row, index })}>
|
|
38
|
+
{itemExtractor?.({ row, index })}
|
|
39
|
+
</option>
|
|
40
|
+
)}
|
|
41
|
+
/>
|
|
42
|
+
</select>
|
|
43
|
+
|
|
44
|
+
<span className={selectIconClassName}>
|
|
45
|
+
<MdKeyboardArrowDown className="text-md translate-x-0.5" />
|
|
46
|
+
</span>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const selectBaseClassName = `group/select relative flex min-h-[2.5rem] w-full items-center gap-1 rounded-lg
|
|
2
|
+
border-2 border-transparent bg-low text-sm text-medium select-none
|
|
3
|
+
hover:border-on-main hover:text-on-main hover:shadow-item
|
|
4
|
+
focus-within:border-primary focus-within:text-on-main
|
|
5
|
+
has-[select:disabled]:pointer-events-none has-[select:disabled]:opacity-40`;
|
|
6
|
+
|
|
7
|
+
export const selectActiveClassName = "border-primary";
|
|
8
|
+
|
|
9
|
+
export const selectControlClassName = `min-w-0 flex-1 cursor-pointer appearance-none truncate rounded-lg
|
|
10
|
+
bg-transparent px-3 py-2 text-left text-on-main outline-none disabled:cursor-auto
|
|
11
|
+
has-[option[value='']:checked]:text-medium
|
|
12
|
+
[&>option]:bg-main [&>option]:text-on-main
|
|
13
|
+
[&>option:checked]:bg-primary-darken [&>option:checked]:text-on-main`;
|
|
14
|
+
|
|
15
|
+
export const selectIconClassName = "pointer-events-none mr-3 flex size-4 flex-none items-center justify-center";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from "react";
|
|
2
|
+
|
|
3
|
+
export type SelectOwnProps<T> = {
|
|
4
|
+
options?: readonly T[];
|
|
5
|
+
itemExtractor?: ((args: { row: T; index: number }) => string) | null;
|
|
6
|
+
valueExtractor?: ((args: { row: T; index: number }) => string | number) | null;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
canBeEmpty?: boolean;
|
|
9
|
+
active?: boolean;
|
|
10
|
+
clickable?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type SelectProps<T> = Omit<ComponentPropsWithRef<"select">, "children"> & SelectOwnProps<T>;
|
|
@@ -4,6 +4,8 @@ import type { ElementType } from "react";
|
|
|
4
4
|
import type { SplashProps, SplashServeProps } from "./type.js";
|
|
5
5
|
|
|
6
6
|
import { cn } from "../../utils/cn/cn.js";
|
|
7
|
+
import { splashBaseClassName, splashOverrideClassName } from "./style.js";
|
|
8
|
+
|
|
7
9
|
import "./splash.css";
|
|
8
10
|
|
|
9
11
|
export const Splash = <T extends ElementType = "div">({ initial = true, className, ...rest }: SplashProps<T>) => (
|
|
@@ -14,11 +16,7 @@ export const Splash = <T extends ElementType = "div">({ initial = true, classNam
|
|
|
14
16
|
role="presentation"
|
|
15
17
|
data-margo-splash=""
|
|
16
18
|
data-margo-splash-initial={initial}
|
|
17
|
-
className={cn(
|
|
18
|
-
"fixed inset-0 z-max",
|
|
19
|
-
className && "[clip-path:none] margo-splash-idle:[clip-path:none]",
|
|
20
|
-
className,
|
|
21
|
-
)}
|
|
19
|
+
className={cn(splashBaseClassName, className && splashOverrideClassName, className)}
|
|
22
20
|
/>
|
|
23
21
|
);
|
|
24
22
|
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const toggleBaseClassName = `margo-toggle h-5 w-9 flex-none cursor-pointer rounded-full border-2 border-border
|
|
2
|
+
bg-main p-0.5 text-on-main enabled:hover:border-on-main focus-visible:border-primary focus-visible:outline
|
|
3
|
+
focus-visible:outline-offset-1 focus-visible:outline-on-main checked:border-primary select-none origin-center
|
|
4
|
+
transition-transform duration-[160ms] ease-out active:scale-[0.98] disabled:cursor-auto disabled:opacity-40`;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
.margo-toggle {
|
|
2
|
+
appearance: none;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/* the thumb travels the track's width minus its height: borders and padding cancel out on both axes */
|
|
6
|
+
.margo-toggle::before {
|
|
7
|
+
content: "";
|
|
8
|
+
display: block;
|
|
9
|
+
block-size: 100%;
|
|
10
|
+
aspect-ratio: 1;
|
|
11
|
+
border-radius: 9999px;
|
|
12
|
+
background-color: currentColor;
|
|
13
|
+
transition: translate 160ms ease-out;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.margo-toggle:checked::before {
|
|
17
|
+
translate: var(--margo-toggle-travel, 1rem) 0;
|
|
18
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
2
|
+
import { toggleBaseClassName } from "./style.js";
|
|
3
|
+
|
|
4
|
+
import type { ToggleProps } from "./type.js";
|
|
5
|
+
|
|
6
|
+
import "./toggle.css";
|
|
7
|
+
|
|
8
|
+
export const Toggle = ({ className, ...rest }: ToggleProps) => (
|
|
9
|
+
<input {...rest} type="checkbox" role="switch" className={cn(toggleBaseClassName, className)} />
|
|
10
|
+
);
|
package/src/css/theme.css
CHANGED
package/src/css/variables.css
CHANGED
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
--margo-layer-duration: 750ms;
|
|
35
35
|
--margo-layer-easing: cubic-bezier(0.32, 0.72, 0, 1);
|
|
36
36
|
--margo-layer-z-index: 100;
|
|
37
|
+
|
|
38
|
+
--margo-popover-duration: 200ms;
|
|
39
|
+
--margo-popover-easing: cubic-bezier(0.32, 0.72, 0, 1);
|
|
40
|
+
|
|
37
41
|
--margo-max-z-index: 9999;
|
|
38
42
|
|
|
39
43
|
--margo-grid-max-width: 80rem;
|
|
@@ -60,21 +64,7 @@
|
|
|
60
64
|
--margo-shadow-card: 0 8px 6px -4px rgb(220, 220, 220, 0.5);
|
|
61
65
|
--margo-shadow-button: 0 2px 3px -1px rgb(220, 220, 220, 0.6);
|
|
62
66
|
--margo-shadow-item: 0 3px 3px -3px rgba(139, 139, 139, 0.6);
|
|
63
|
-
--margo-shadow-input: 0 3px 3px -3px rgba(139, 139, 139, 0.6);
|
|
64
67
|
--margo-shadow-chip: 0 1px 3px -1px rgb(0, 0, 0, 0.6);
|
|
65
|
-
|
|
66
|
-
--margo-code-surface: #fbfbfb;
|
|
67
|
-
--margo-code-bar: #f1f1f1;
|
|
68
|
-
--margo-code-plain: #24292f;
|
|
69
|
-
--margo-code-comment: #8b949e;
|
|
70
|
-
--margo-code-string: #0a7d3f;
|
|
71
|
-
--margo-code-keyword: #a5309a;
|
|
72
|
-
--margo-code-number: #b3510f;
|
|
73
|
-
--margo-code-tag: #1f6fd6;
|
|
74
|
-
--margo-code-attr: #7a5cd6;
|
|
75
|
-
--margo-code-fn: #0f7c93;
|
|
76
|
-
--margo-code-type: #1f6fd6;
|
|
77
|
-
--margo-code-punct: #6e7781;
|
|
78
68
|
}
|
|
79
69
|
|
|
80
70
|
.dark {
|
|
@@ -94,25 +84,12 @@
|
|
|
94
84
|
--margo-shadow-card: 0 4px 12px -4px rgb(0, 0, 0, 0.5);
|
|
95
85
|
--margo-shadow-button: 0 2px 6px -2px rgb(0, 0, 0, 0.5);
|
|
96
86
|
--margo-shadow-item: 0 2px 3px -2px rgb(0, 0, 0, 0.5);
|
|
97
|
-
--margo-shadow-input: 0 2px 3px -2px rgb(0, 0, 0, 0.5);
|
|
98
87
|
--margo-shadow-chip: 0 1px 3px -1px rgb(0, 0, 0, 0.5);
|
|
99
|
-
|
|
100
|
-
--margo-code-surface: #0d0d0d;
|
|
101
|
-
--margo-code-bar: #161616;
|
|
102
|
-
--margo-code-plain: #e6e6e6;
|
|
103
|
-
--margo-code-comment: #6b6b6b;
|
|
104
|
-
--margo-code-string: #7ee787;
|
|
105
|
-
--margo-code-keyword: #ff7bd5;
|
|
106
|
-
--margo-code-number: #ffab70;
|
|
107
|
-
--margo-code-tag: #79c0ff;
|
|
108
|
-
--margo-code-attr: #c3a6ff;
|
|
109
|
-
--margo-code-fn: #56d4dd;
|
|
110
|
-
--margo-code-type: #79c0ff;
|
|
111
|
-
--margo-code-punct: #8b949e;
|
|
112
88
|
}
|
|
113
89
|
|
|
114
90
|
@media (prefers-reduced-motion: reduce) {
|
|
115
91
|
:root {
|
|
116
92
|
--margo-layer-duration: 1ms;
|
|
93
|
+
--margo-popover-duration: 1ms;
|
|
117
94
|
}
|
|
118
95
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { Blockquote } from "./components/blockquote/blockquote.js";
|
|
|
2
2
|
export { Button } from "./components/button/button.js";
|
|
3
3
|
export { ButtonMicro } from "./components/button_micro/button_micro.js";
|
|
4
4
|
export { Card } from "./components/card/card.js";
|
|
5
|
+
export { Checkbox } from "./components/checkbox/checkbox.js";
|
|
5
6
|
export { Chip } from "./components/chip/chip.js";
|
|
6
7
|
export { Code } from "./components/code/code.js";
|
|
7
8
|
export { Dialog } from "./components/dialog/dialog.js";
|
|
@@ -11,11 +12,13 @@ export { Layer } from "./components/layer/layer.js";
|
|
|
11
12
|
export { Input } from "./components/input/input.js";
|
|
12
13
|
export { Item } from "./components/item/item.js";
|
|
13
14
|
export { NavigationBar } from "./components/navigation_bar/navigation_bar.js";
|
|
15
|
+
export { Popover } from "./components/popover/popover.js";
|
|
14
16
|
export { ProgressBar } from "./components/progress_bar/progress_bar.js";
|
|
15
17
|
export { Table } from "./components/table/table.js";
|
|
18
|
+
export { Toggle } from "./components/toggle/toggle.js";
|
|
16
19
|
export { Splash } from "./components/splash/splash.js";
|
|
20
|
+
export { Select } from "./components/select/select.js";
|
|
17
21
|
export { Sheet } from "./components/sheet/sheet.js";
|
|
18
|
-
export { Snippet } from "./components/snippet/snippet.js";
|
|
19
22
|
export { Spinner } from "./components/spinner/spinner.js";
|
|
20
23
|
export { Tooltip } from "./components/tooltip/tooltip.js";
|
|
21
24
|
|
|
@@ -31,6 +34,7 @@ export { MetaColorScheme } from "./meta/meta_color_scheme/meta_color_scheme.js";
|
|
|
31
34
|
export { useMargoTheme } from "./hooks/use_theme.js";
|
|
32
35
|
|
|
33
36
|
export { cn } from "./utils/cn/cn.js";
|
|
37
|
+
export { isMargoLayerSupported } from "./utils/support/support.js";
|
|
34
38
|
export { margoTheme, margoThemeClient } from "./utils/theme/theme.js";
|
|
35
39
|
|
|
36
40
|
export type { BlockquoteProps } from "./components/blockquote/type.js";
|
|
@@ -44,6 +48,7 @@ export type {
|
|
|
44
48
|
} from "./components/button/type.js";
|
|
45
49
|
export type { ButtonMicroProps } from "./components/button_micro/type.js";
|
|
46
50
|
export type { CardProps } from "./components/card/type.js";
|
|
51
|
+
export type { CheckboxProps } from "./components/checkbox/type.js";
|
|
47
52
|
export type { ChipProps } from "./components/chip/type.js";
|
|
48
53
|
export type { CodeOwnProps, CodeProps } from "./components/code/type.js";
|
|
49
54
|
export type { DialogBodyProps, DialogFooterProps, DialogProps } from "./components/dialog/type.js";
|
|
@@ -58,6 +63,7 @@ export type { LayerProps } from "./components/layer/type.js";
|
|
|
58
63
|
export type { InputIconProps, InputProps, InputTextProps } from "./components/input/type.js";
|
|
59
64
|
export type { ItemIconProps, ItemLabelProps, ItemProps } from "./components/item/type.js";
|
|
60
65
|
export type { NavigationBarProps } from "./components/navigation_bar/type.js";
|
|
66
|
+
export type { PopoverAlign, PopoverAnchorProps, PopoverBodyProps, PopoverPosition, PopoverProps } from "./components/popover/type.js";
|
|
61
67
|
export type { ProgressBarMode, ProgressBarProps } from "./components/progress_bar/type.js";
|
|
62
68
|
export type {
|
|
63
69
|
TableBodyProps,
|
|
@@ -68,9 +74,10 @@ export type {
|
|
|
68
74
|
TableRowProps,
|
|
69
75
|
} from "./components/table/type.js";
|
|
70
76
|
export type { SplashProps, SplashServeProps } from "./components/splash/type.js";
|
|
77
|
+
export type { SelectOwnProps, SelectProps } from "./components/select/type.js";
|
|
71
78
|
export type { SheetBodyProps, SheetFooterProps, SheetProps, SheetSide } from "./components/sheet/type.js";
|
|
72
|
-
export type { SnippetProps, SnippetToken, SnippetTokenType } from "./components/snippet/type.js";
|
|
73
79
|
export type { SpinnerProps } from "./components/spinner/type.js";
|
|
80
|
+
export type { ToggleProps } from "./components/toggle/type.js";
|
|
74
81
|
export type { TooltipPosition, TooltipProps } from "./components/tooltip/type.js";
|
|
75
82
|
|
|
76
83
|
export type { BackgroundGlowProps } from "./hoc/background_glow/type.js";
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import type { SnippetToken, SnippetTokenType } from "./type.js";
|
|
2
|
-
|
|
3
|
-
const KEYWORDS = [
|
|
4
|
-
"as",
|
|
5
|
-
"async",
|
|
6
|
-
"await",
|
|
7
|
-
"break",
|
|
8
|
-
"case",
|
|
9
|
-
"catch",
|
|
10
|
-
"class",
|
|
11
|
-
"const",
|
|
12
|
-
"continue",
|
|
13
|
-
"default",
|
|
14
|
-
"delete",
|
|
15
|
-
"do",
|
|
16
|
-
"else",
|
|
17
|
-
"export",
|
|
18
|
-
"extends",
|
|
19
|
-
"false",
|
|
20
|
-
"for",
|
|
21
|
-
"from",
|
|
22
|
-
"function",
|
|
23
|
-
"if",
|
|
24
|
-
"implements",
|
|
25
|
-
"import",
|
|
26
|
-
"in",
|
|
27
|
-
"instanceof",
|
|
28
|
-
"interface",
|
|
29
|
-
"let",
|
|
30
|
-
"new",
|
|
31
|
-
"null",
|
|
32
|
-
"of",
|
|
33
|
-
"readonly",
|
|
34
|
-
"return",
|
|
35
|
-
"satisfies",
|
|
36
|
-
"switch",
|
|
37
|
-
"this",
|
|
38
|
-
"throw",
|
|
39
|
-
"true",
|
|
40
|
-
"try",
|
|
41
|
-
"type",
|
|
42
|
-
"typeof",
|
|
43
|
-
"undefined",
|
|
44
|
-
"var",
|
|
45
|
-
"void",
|
|
46
|
-
"while",
|
|
47
|
-
"yield",
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
const PATTERNS: readonly { type: SnippetTokenType; source: string }[] = [
|
|
51
|
-
{ type: "comment", source: "\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\/" },
|
|
52
|
-
{ type: "string", source: "\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|`(?:[^`\\\\]|\\\\.)*`" },
|
|
53
|
-
{ type: "tag", source: "<\\/?[A-Za-z][\\w.$]*" },
|
|
54
|
-
{ type: "keyword", source: `\\b(?:${KEYWORDS.join("|")})\\b` },
|
|
55
|
-
{ type: "number", source: "\\b\\d+(?:\\.\\d+)?\\b" },
|
|
56
|
-
{ type: "attr", source: "\\b[A-Za-z_$][\\w$]*(?=\\s*[=:](?!=))" },
|
|
57
|
-
{ type: "fn", source: "\\b[A-Za-z_$][\\w$]*(?=\\s*\\()" },
|
|
58
|
-
{ type: "type", source: "\\b[A-Z][\\w$]*\\b" },
|
|
59
|
-
{ type: "punct", source: "[{}()\\[\\]<>=;,.:!?|&+\\-*/]" },
|
|
60
|
-
];
|
|
61
|
-
|
|
62
|
-
const TOKENIZER = new RegExp(PATTERNS.map((pattern) => `(${pattern.source})`).join("|"), "g");
|
|
63
|
-
|
|
64
|
-
export const snippetHighlight = (code: string): SnippetToken[] => {
|
|
65
|
-
const tokens: SnippetToken[] = [];
|
|
66
|
-
let cursor = 0;
|
|
67
|
-
let match: RegExpExecArray | null;
|
|
68
|
-
|
|
69
|
-
TOKENIZER.lastIndex = 0;
|
|
70
|
-
|
|
71
|
-
while ((match = TOKENIZER.exec(code)) !== null) {
|
|
72
|
-
if (match.index > cursor) tokens.push({ type: "plain", value: code.slice(cursor, match.index) });
|
|
73
|
-
|
|
74
|
-
const groupIndex = PATTERNS.findIndex((_, index) => match![index + 1] !== undefined);
|
|
75
|
-
|
|
76
|
-
tokens.push({ type: PATTERNS[groupIndex].type, value: match[0] });
|
|
77
|
-
cursor = match.index + match[0].length;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (cursor < code.length) tokens.push({ type: "plain", value: code.slice(cursor) });
|
|
81
|
-
|
|
82
|
-
return tokens;
|
|
83
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
|
|
3
|
-
import { MdCheck, MdContentCopy } from "react-icons/md";
|
|
4
|
-
|
|
5
|
-
import { Guard, List, Swap, Tag } from "react-renderable";
|
|
6
|
-
|
|
7
|
-
import { cn } from "../../utils/cn/cn.js";
|
|
8
|
-
import { Button } from "../button/button.js";
|
|
9
|
-
import { snippetHighlight } from "./highlight.js";
|
|
10
|
-
import {
|
|
11
|
-
snippetBarClassName,
|
|
12
|
-
snippetBaseClassName,
|
|
13
|
-
snippetCodeClassName,
|
|
14
|
-
snippetCopyClassName,
|
|
15
|
-
snippetTitleClassName,
|
|
16
|
-
snippetTokenClassName,
|
|
17
|
-
} from "./style.js";
|
|
18
|
-
|
|
19
|
-
import type { ElementType } from "react";
|
|
20
|
-
import { MaskGradientX } from "../../hoc/mask_gradient_x/mask_gradient_x.js";
|
|
21
|
-
import type { SnippetProps } from "./type.js";
|
|
22
|
-
|
|
23
|
-
export const Snippet = <T extends ElementType = "div">({ snippet, title, className, ...rest }: SnippetProps<T>) => {
|
|
24
|
-
const [isCopied, setIsCopied] = useState(false);
|
|
25
|
-
const source = snippet.trim();
|
|
26
|
-
|
|
27
|
-
const handleCopy = async () => {
|
|
28
|
-
await navigator.clipboard.writeText(source);
|
|
29
|
-
setIsCopied(true);
|
|
30
|
-
window.setTimeout(() => setIsCopied(false), 1500);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<Tag {...Tag.forward<T>(rest)} className={cn(snippetBaseClassName, className)}>
|
|
35
|
-
<div className={snippetBarClassName}>
|
|
36
|
-
<Guard guardIf={!title} shouldHide>
|
|
37
|
-
<span className={snippetTitleClassName}>{title}</span>
|
|
38
|
-
</Guard>
|
|
39
|
-
<Button onClickBlur={handleCopy} aria-label="copy" aria-pressed={isCopied} className={snippetCopyClassName}>
|
|
40
|
-
<Button.Icon
|
|
41
|
-
icon={
|
|
42
|
-
<Swap.Boolean
|
|
43
|
-
swapOn={isCopied}
|
|
44
|
-
components={[<MdContentCopy className="text-sm" />, <MdCheck className="text-sm" />]}
|
|
45
|
-
/>
|
|
46
|
-
}
|
|
47
|
-
/>
|
|
48
|
-
</Button>
|
|
49
|
-
</div>
|
|
50
|
-
<MaskGradientX fade="1rem">
|
|
51
|
-
<pre className={snippetCodeClassName}>
|
|
52
|
-
<code>
|
|
53
|
-
<List
|
|
54
|
-
array={snippetHighlight(source)}
|
|
55
|
-
itemExtractor={({ row, index }) => (
|
|
56
|
-
<span key={index} className={snippetTokenClassName[row.type]}>
|
|
57
|
-
{row.value}
|
|
58
|
-
</span>
|
|
59
|
-
)}
|
|
60
|
-
/>
|
|
61
|
-
</code>
|
|
62
|
-
</pre>
|
|
63
|
-
</MaskGradientX>
|
|
64
|
-
</Tag>
|
|
65
|
-
);
|
|
66
|
-
};
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { SnippetTokenType } from "./type.js";
|
|
2
|
-
|
|
3
|
-
export const snippetBaseClassName = "overflow-hidden rounded-lg border-2 border-border bg-(--margo-code-surface)";
|
|
4
|
-
|
|
5
|
-
export const snippetBarClassName =
|
|
6
|
-
"flex items-center justify-between gap-4 border-b-2 border-border bg-(--margo-code-bar) px-3 py-2";
|
|
7
|
-
|
|
8
|
-
export const snippetTitleClassName = "text-mc uppercase tracking-widest text-medium";
|
|
9
|
-
|
|
10
|
-
export const snippetCopyClassName = "ml-auto bg-neutral/60";
|
|
11
|
-
|
|
12
|
-
export const snippetCodeClassName = "overflow-x-auto p-4 text-xs leading-relaxed";
|
|
13
|
-
|
|
14
|
-
export const snippetTokenClassName: Record<SnippetTokenType, string> = {
|
|
15
|
-
plain: "text-(--margo-code-plain)",
|
|
16
|
-
comment: "text-(--margo-code-comment) italic",
|
|
17
|
-
string: "text-(--margo-code-string)",
|
|
18
|
-
keyword: "text-(--margo-code-keyword)",
|
|
19
|
-
number: "text-(--margo-code-number)",
|
|
20
|
-
tag: "text-(--margo-code-tag)",
|
|
21
|
-
attr: "text-(--margo-code-attr)",
|
|
22
|
-
fn: "text-(--margo-code-fn)",
|
|
23
|
-
type: "text-(--margo-code-type)",
|
|
24
|
-
punct: "text-(--margo-code-punct)",
|
|
25
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { TagProps } from "react-renderable";
|
|
2
|
-
import type { ElementType } from "react";
|
|
3
|
-
|
|
4
|
-
export type SnippetTokenType =
|
|
5
|
-
| "plain"
|
|
6
|
-
| "comment"
|
|
7
|
-
| "string"
|
|
8
|
-
| "keyword"
|
|
9
|
-
| "number"
|
|
10
|
-
| "tag"
|
|
11
|
-
| "attr"
|
|
12
|
-
| "fn"
|
|
13
|
-
| "type"
|
|
14
|
-
| "punct";
|
|
15
|
-
|
|
16
|
-
export type SnippetToken = {
|
|
17
|
-
type: SnippetTokenType;
|
|
18
|
-
value: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type SnippetOwnProps = {
|
|
22
|
-
snippet: string;
|
|
23
|
-
title?: string;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export type SnippetProps<T extends ElementType = "div"> = TagProps<T, SnippetOwnProps>;
|