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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "margo-ui",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "description": "React UI kit built on Tailwind CSS v4 design tokens, themeable through plain CSS custom properties.",
5
5
  "keywords": [
6
6
  "react",
@@ -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`;
@@ -0,0 +1,5 @@
1
+ import type { ComponentPropsWithRef } from "react";
2
+
3
+ export type CheckboxProps = Omit<ComponentPropsWithRef<"input">, "type"> & {
4
+ indeterminate?: boolean;
5
+ };
@@ -1,4 +1,5 @@
1
- export const chipBaseClassName =
2
- "bg-on-main/8 margo-text-box-trim flex items-center justify-center px-3 py-0.5 whitespace-nowrap lowercase flex-none leading-none rounded-md text-mc shadow-chip";
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-input
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
 
@@ -1 +1,3 @@
1
- export const splashBaseClassName = "margo-splash";
1
+ export const splashBaseClassName = "fixed inset-0 z-max";
2
+
3
+ export const splashOverrideClassName = "[clip-path:none] margo-splash-idle:[clip-path:none]";
@@ -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
+ );
@@ -0,0 +1,3 @@
1
+ import type { ComponentPropsWithRef } from "react";
2
+
3
+ export type ToggleProps = Omit<ComponentPropsWithRef<"input">, "type" | "role">;
package/src/css/theme.css CHANGED
@@ -29,7 +29,6 @@
29
29
  --shadow-card: var(--margo-shadow-card);
30
30
  --shadow-button: var(--margo-shadow-button);
31
31
  --shadow-item: var(--margo-shadow-item);
32
- --shadow-input: var(--margo-shadow-input);
33
32
  --shadow-chip: var(--margo-shadow-chip);
34
33
 
35
34
  /* Durations */
@@ -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";
@@ -0,0 +1,2 @@
1
+ export const isMargoLayerSupported = (): boolean =>
2
+ typeof HTMLDialogElement !== "undefined" && typeof HTMLDialogElement.prototype.showModal === "function";
@@ -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>;