margo-ui 1.1.0 → 1.2.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 +5 -7
- package/src/components/blockquote/blockquote.tsx +11 -0
- package/src/components/blockquote/style.ts +1 -0
- package/src/components/blockquote/type.ts +4 -0
- package/src/components/button/button.tsx +83 -0
- package/src/components/button/style.ts +27 -0
- package/src/components/button/type.ts +33 -0
- package/src/components/card/card.tsx +13 -0
- package/src/components/card/style.ts +4 -0
- package/src/components/card/type.ts +4 -0
- package/src/components/chip/chip.tsx +13 -0
- package/src/components/chip/style.ts +4 -0
- package/src/components/chip/type.ts +8 -0
- package/src/components/progress_bar/progress_bar.css +33 -0
- package/src/components/progress_bar/progress_bar.tsx +35 -0
- package/src/components/progress_bar/style.ts +1 -0
- package/src/components/progress_bar/type.ts +12 -0
- package/src/components/spinner/spinner.tsx +21 -0
- package/src/components/spinner/style.ts +3 -0
- package/src/components/spinner/type.ts +8 -0
- package/src/components/tooltip/style.ts +17 -0
- package/src/components/tooltip/tooltip.tsx +50 -0
- package/src/components/tooltip/type.ts +17 -0
- package/src/css/theme.css +5 -0
- package/src/css/variables.css +21 -11
- package/src/hoc/background_glow/background_glow.tsx +25 -0
- package/src/hoc/background_glow/style.ts +9 -0
- package/src/hoc/background_glow/type.ts +13 -0
- package/src/hoc/border_glow/border_glow.tsx +27 -0
- package/src/hoc/border_glow/style.ts +11 -0
- package/src/hoc/border_glow/type.ts +13 -0
- package/src/{components → hoc/ripple}/ripple.tsx +5 -14
- package/src/hoc/ripple/type.ts +25 -0
- package/src/{hooks → hoc/ripple}/use_ripple.ts +2 -14
- package/src/hooks/use_theme.ts +8 -8
- package/src/index.ts +39 -22
- package/src/utils/theme/theme.ts +26 -0
- package/src/utils/theme/type.ts +12 -0
- package/src/components/background_glow.tsx +0 -45
- package/src/components/border_glow.tsx +0 -45
- package/src/components/button.tsx +0 -98
- package/src/components/card.tsx +0 -26
- package/src/components/chip.tsx +0 -27
- package/src/components/tooltip.tsx +0 -55
- package/src/helpers/theme.ts +0 -31
- /package/src/{components → hoc/background_glow}/background_glow.css +0 -0
- /package/src/{components → hoc/border_glow}/border_glow.css +0 -0
- /package/src/{components → hoc/ripple}/ripple.css +0 -0
- /package/src/{components → meta/meta_color_scheme}/meta_color_scheme.tsx +0 -0
- /package/src/{helpers → utils/cn}/cn.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "margo-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A small React UI kit built on Tailwind CSS v4 design tokens, themeable through plain CSS custom properties.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -36,15 +36,13 @@
|
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"ts:check": "tsc --noEmit",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"release:version": "changeset version",
|
|
43
|
-
"release": "npm run ts:check && changeset publish"
|
|
39
|
+
"changeset:add": "changeset",
|
|
40
|
+
"changeset:bump": "changeset version",
|
|
41
|
+
"build:release": "npm run ts:check && npm pack && changeset publish && git push --follow-tags"
|
|
44
42
|
},
|
|
45
43
|
"dependencies": {
|
|
46
44
|
"clsx": "^2.1.1",
|
|
47
|
-
"react-renderable": "^1.0.
|
|
45
|
+
"react-renderable": "^1.0.4",
|
|
48
46
|
"tailwind-merge": "^3.6.0"
|
|
49
47
|
},
|
|
50
48
|
"peerDependencies": {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Tag } from "react-renderable";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
4
|
+
import { blockquoteBaseClassName } from "./style.js";
|
|
5
|
+
|
|
6
|
+
import type { ElementType } from "react";
|
|
7
|
+
import type { BlockquoteProps } from "./type.js";
|
|
8
|
+
|
|
9
|
+
export const Blockquote = <T extends ElementType = "blockquote">({ className, ...rest }: BlockquoteProps<T>) => (
|
|
10
|
+
<Tag {...Tag.forward<T>(rest, "blockquote")} className={cn(blockquoteBaseClassName, className)} />
|
|
11
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const blockquoteBaseClassName = "border-l-2 border-primary-darken py-0.5 pl-4";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Tag } from "react-renderable";
|
|
2
|
+
|
|
3
|
+
import { Ripple } from "../../hoc/ripple/ripple.js";
|
|
4
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
5
|
+
import {
|
|
6
|
+
buttonActiveClassName,
|
|
7
|
+
buttonBaseClassName,
|
|
8
|
+
buttonGridClassName,
|
|
9
|
+
buttonGridForwardClassName,
|
|
10
|
+
buttonGridReverseClassName,
|
|
11
|
+
buttonIconClassName,
|
|
12
|
+
buttonLabelClassName,
|
|
13
|
+
buttonSlotClassName,
|
|
14
|
+
buttonSurfaceClassName,
|
|
15
|
+
} from "./style.js";
|
|
16
|
+
|
|
17
|
+
import type { CSSProperties, ElementType, MouseEvent } from "react";
|
|
18
|
+
import type { ButtonIconLabelProps, ButtonIconProps, ButtonLabelProps, ButtonProps } from "./type.js";
|
|
19
|
+
|
|
20
|
+
export const Button = <T extends ElementType = "button">({
|
|
21
|
+
clickable = true,
|
|
22
|
+
active = false,
|
|
23
|
+
open = false,
|
|
24
|
+
onClickBlur,
|
|
25
|
+
className,
|
|
26
|
+
onClick,
|
|
27
|
+
...rest
|
|
28
|
+
}: ButtonProps<T>) => {
|
|
29
|
+
const handleClick = (event: MouseEvent<HTMLElement>) => {
|
|
30
|
+
onClick?.(event);
|
|
31
|
+
if (typeof onClickBlur === "function") {
|
|
32
|
+
const target = event.currentTarget;
|
|
33
|
+
onClickBlur(event);
|
|
34
|
+
setTimeout(() => target.blur(), 150);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Ripple>
|
|
40
|
+
<Tag
|
|
41
|
+
{...Tag.forward<T>(rest, "button")}
|
|
42
|
+
data-open={open}
|
|
43
|
+
onClick={handleClick}
|
|
44
|
+
className={cn(
|
|
45
|
+
buttonBaseClassName,
|
|
46
|
+
active ? buttonActiveClassName : buttonSurfaceClassName,
|
|
47
|
+
!clickable && "pointer-events-none",
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
/>
|
|
51
|
+
</Ripple>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
Button.Icon = ({ icon, className = null }: ButtonIconProps) => (
|
|
56
|
+
<span className={cn(buttonIconClassName, className)}>{icon}</span>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
Button.Label = ({ label, className = null }: ButtonLabelProps) => (
|
|
60
|
+
<span className={cn(buttonLabelClassName, className)}>{label}</span>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
Button.IconLabel = ({
|
|
64
|
+
icon,
|
|
65
|
+
label,
|
|
66
|
+
gap = "0.5rem",
|
|
67
|
+
reverse = false,
|
|
68
|
+
side = "end",
|
|
69
|
+
className = null,
|
|
70
|
+
}: ButtonIconLabelProps) => (
|
|
71
|
+
<span
|
|
72
|
+
style={{ "--button-gap": gap } as CSSProperties}
|
|
73
|
+
className={cn(
|
|
74
|
+
buttonGridClassName,
|
|
75
|
+
side === "start" && "[direction:rtl]",
|
|
76
|
+
reverse ? buttonGridReverseClassName : buttonGridForwardClassName,
|
|
77
|
+
className,
|
|
78
|
+
)}
|
|
79
|
+
>
|
|
80
|
+
{reverse ? label : icon}
|
|
81
|
+
<span className={buttonSlotClassName}>{reverse ? icon : label}</span>
|
|
82
|
+
</span>
|
|
83
|
+
);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const buttonBaseClassName = `group/button flex h-8 w-fit shrink-0 items-center px-2 rounded-lg border-2 text-on-main
|
|
2
|
+
hover:border-on-main hover:text-on-main focus-visible:margo-border-gradient-primary
|
|
3
|
+
focus-visible:outline focus-visible:outline-offset-1 focus-visible:outline-on-main shadow-button`;
|
|
4
|
+
|
|
5
|
+
export const buttonSurfaceClassName = "border-border bg-main";
|
|
6
|
+
|
|
7
|
+
export const buttonActiveClassName = "margo-border-gradient-primary";
|
|
8
|
+
|
|
9
|
+
export const buttonIconClassName =
|
|
10
|
+
"relative z-10 flex size-4 min-w-0 shrink-0 items-center justify-center [direction:ltr]";
|
|
11
|
+
|
|
12
|
+
export const buttonLabelClassName =
|
|
13
|
+
"relative z-10 flex items-center text-sm whitespace-nowrap lowercase [direction:ltr] px-0.5";
|
|
14
|
+
|
|
15
|
+
export const buttonGridClassName =
|
|
16
|
+
"grid items-center gap-0 transition-[grid-template-columns,gap] duration-[180ms] ease-in-out";
|
|
17
|
+
|
|
18
|
+
export const buttonGridForwardClassName = `grid-cols-[1rem_0fr] group-hover/button:grid-cols-[1rem_1fr] group-hover/button:gap-(--button-gap)
|
|
19
|
+
group-focus-visible/button:grid-cols-[1rem_1fr] group-focus-visible/button:gap-(--button-gap)
|
|
20
|
+
group-data-[open=true]/button:grid-cols-[1rem_1fr] group-data-[open=true]/button:gap-(--button-gap)`;
|
|
21
|
+
|
|
22
|
+
export const buttonGridReverseClassName = `grid-cols-[auto_0rem] group-hover/button:grid-cols-[auto_1rem] group-hover/button:gap-(--button-gap)
|
|
23
|
+
group-focus-visible/button:grid-cols-[auto_1rem] group-focus-visible/button:gap-(--button-gap)
|
|
24
|
+
group-data-[open=true]/button:grid-cols-[auto_1rem] group-data-[open=true]/button:gap-(--button-gap)`;
|
|
25
|
+
|
|
26
|
+
export const buttonSlotClassName = `min-w-0 overflow-hidden opacity-0 transition-opacity duration-[180ms] ease-in
|
|
27
|
+
group-hover/button:opacity-100 group-focus-visible/button:opacity-100 group-data-[open=true]/button:opacity-100`;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { TagProps } from "react-renderable";
|
|
2
|
+
import type { ElementType, MouseEventHandler, ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
export type ButtonIconLabelSide = "start" | "end";
|
|
5
|
+
|
|
6
|
+
export type ButtonOwnProps = {
|
|
7
|
+
clickable?: boolean;
|
|
8
|
+
active?: boolean;
|
|
9
|
+
open?: boolean;
|
|
10
|
+
onClick?: MouseEventHandler<HTMLElement>;
|
|
11
|
+
onClickBlur?: MouseEventHandler<HTMLElement>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type ButtonProps<T extends ElementType = "button"> = TagProps<T, ButtonOwnProps>;
|
|
15
|
+
|
|
16
|
+
export type ButtonIconProps = {
|
|
17
|
+
icon: ReactNode;
|
|
18
|
+
className?: string | null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type ButtonLabelProps = {
|
|
22
|
+
label: string;
|
|
23
|
+
className?: string | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ButtonIconLabelProps = {
|
|
27
|
+
icon: ReactNode;
|
|
28
|
+
label: ReactNode;
|
|
29
|
+
gap?: string;
|
|
30
|
+
reverse?: boolean;
|
|
31
|
+
side?: ButtonIconLabelSide;
|
|
32
|
+
className?: string | null;
|
|
33
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Tag } from "react-renderable";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
4
|
+
import { cardBaseClassName, cardFocusClassName } from "./style.js";
|
|
5
|
+
|
|
6
|
+
import type { ElementType } from "react";
|
|
7
|
+
import type { CardProps } from "./type.js";
|
|
8
|
+
|
|
9
|
+
export const Card = <T extends ElementType = "div">({ className, children, ...rest }: CardProps<T>) => (
|
|
10
|
+
<Tag {...Tag.forward<T>(rest)} className={cn(cardBaseClassName, cardFocusClassName, className)}>
|
|
11
|
+
{children}
|
|
12
|
+
</Tag>
|
|
13
|
+
);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Tag } from "react-renderable";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
4
|
+
import { chipActiveClassName, chipBaseClassName } from "./style.js";
|
|
5
|
+
|
|
6
|
+
import type { ElementType } from "react";
|
|
7
|
+
import type { ChipProps } from "./type.js";
|
|
8
|
+
|
|
9
|
+
export const Chip = <T extends ElementType = "div">({ active = false, className, children, ...rest }: ChipProps<T>) => (
|
|
10
|
+
<Tag {...Tag.forward<T>(rest)} className={cn(chipBaseClassName, className, active && chipActiveClassName)}>
|
|
11
|
+
{children}
|
|
12
|
+
</Tag>
|
|
13
|
+
);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
.margo-progress-bar::before {
|
|
2
|
+
content: "";
|
|
3
|
+
position: absolute;
|
|
4
|
+
inset: 0;
|
|
5
|
+
background: var(
|
|
6
|
+
--margo-progress-bar-fill,
|
|
7
|
+
linear-gradient(to right, var(--color-primary-darken), var(--color-primary))
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.margo-progress-bar[data-mode="determinate"]::before {
|
|
12
|
+
clip-path: inset(0 calc(100% - var(--margo-progress-bar-value, 0%)) 0 0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.margo-progress-bar[data-mode="determinate"][data-animate="true"]::before {
|
|
16
|
+
transition: clip-path 300ms ease-out;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.margo-progress-bar[data-mode="indeterminate"]::before {
|
|
20
|
+
inset: 0 auto 0 0;
|
|
21
|
+
width: 40%;
|
|
22
|
+
animation: margo-progress-bar-indeterminate 800ms ease-in-out infinite;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@keyframes margo-progress-bar-indeterminate {
|
|
26
|
+
from {
|
|
27
|
+
transform: translateX(-100%);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
to {
|
|
31
|
+
transform: translateX(250%);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Tag } from "react-renderable";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
4
|
+
import { progressBarBaseClassName } from "./style.js";
|
|
5
|
+
|
|
6
|
+
import type { CSSProperties, ElementType } from "react";
|
|
7
|
+
import type { ProgressBarProps } from "./type.js";
|
|
8
|
+
|
|
9
|
+
import "./progress_bar.css";
|
|
10
|
+
|
|
11
|
+
export const ProgressBar = <T extends ElementType = "div">({
|
|
12
|
+
animate = true,
|
|
13
|
+
className,
|
|
14
|
+
mode = "indeterminate",
|
|
15
|
+
style,
|
|
16
|
+
value = 0,
|
|
17
|
+
...rest
|
|
18
|
+
}: ProgressBarProps<T>) => {
|
|
19
|
+
const isDeterminate = mode === "determinate";
|
|
20
|
+
const progress = Math.min(100, Math.max(0, value));
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Tag
|
|
24
|
+
{...Tag.forward<T>(rest)}
|
|
25
|
+
role="progressbar"
|
|
26
|
+
aria-valuemin={isDeterminate ? 0 : undefined}
|
|
27
|
+
aria-valuemax={isDeterminate ? 100 : undefined}
|
|
28
|
+
aria-valuenow={isDeterminate ? progress : undefined}
|
|
29
|
+
data-mode={mode}
|
|
30
|
+
data-animate={animate}
|
|
31
|
+
style={{ ...style, "--margo-progress-bar-value": `${progress}%` } as CSSProperties}
|
|
32
|
+
className={cn(progressBarBaseClassName, className)}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const progressBarBaseClassName = "margo-progress-bar relative h-0.5 overflow-hidden";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TagProps } from "react-renderable";
|
|
2
|
+
import type { ElementType } from "react";
|
|
3
|
+
|
|
4
|
+
export type ProgressBarMode = "determinate" | "indeterminate";
|
|
5
|
+
|
|
6
|
+
export type ProgressBarOwnProps = {
|
|
7
|
+
animate?: boolean;
|
|
8
|
+
mode?: ProgressBarMode;
|
|
9
|
+
value?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ProgressBarProps<T extends ElementType = "div"> = TagProps<T, ProgressBarOwnProps>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Tag } from "react-renderable";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
4
|
+
import { spinnerBaseClassName, spinnerIconClassName } from "./style.js";
|
|
5
|
+
|
|
6
|
+
import type { ElementType } from "react";
|
|
7
|
+
import type { SpinnerProps } from "./type.js";
|
|
8
|
+
|
|
9
|
+
export const Spinner = <T extends ElementType = "span">({ className, label = "Loading", ...rest }: SpinnerProps<T>) => (
|
|
10
|
+
<Tag
|
|
11
|
+
{...Tag.forward<T>(rest, "span")}
|
|
12
|
+
role="status"
|
|
13
|
+
aria-label={label}
|
|
14
|
+
className={cn(spinnerBaseClassName, className)}
|
|
15
|
+
>
|
|
16
|
+
<svg viewBox="0 0 24 24" aria-hidden className={spinnerIconClassName}>
|
|
17
|
+
<circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" strokeOpacity="0.25" strokeWidth="3" />
|
|
18
|
+
<path d="M12 3a9 9 0 0 1 9 9" fill="none" stroke="currentColor" strokeLinecap="round" strokeWidth="3" />
|
|
19
|
+
</svg>
|
|
20
|
+
</Tag>
|
|
21
|
+
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TooltipPosition } from "./type.js";
|
|
2
|
+
|
|
3
|
+
export const tooltipAnchorClassName = "group relative inline-block size-fit";
|
|
4
|
+
|
|
5
|
+
export const tooltipBubbleClassName = `pointer-events-none absolute z-10 rounded-sm bg-on-main px-2 py-0.5 text-xs text-main lowercase
|
|
6
|
+
whitespace-nowrap opacity-0`;
|
|
7
|
+
|
|
8
|
+
export const tooltipTransitionClassName = "transition-opacity duration-[250ms] ease-in-out";
|
|
9
|
+
|
|
10
|
+
export const tooltipVisibleClassName = "group-hover:opacity-100 group-focus-within:opacity-100";
|
|
11
|
+
|
|
12
|
+
export const tooltipPositionClassName: Record<TooltipPosition, string> = {
|
|
13
|
+
up: "bottom-[120%] left-1/2 -translate-x-1/2",
|
|
14
|
+
down: "top-[120%] left-1/2 -translate-x-1/2",
|
|
15
|
+
left: "top-1/2 right-[calc(100%+0.5rem)] -translate-y-1/2",
|
|
16
|
+
right: "top-1/2 left-[calc(100%+0.5rem)] -translate-y-1/2",
|
|
17
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { Tag } from "react-renderable";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
6
|
+
import {
|
|
7
|
+
tooltipAnchorClassName,
|
|
8
|
+
tooltipBubbleClassName,
|
|
9
|
+
tooltipPositionClassName,
|
|
10
|
+
tooltipTransitionClassName,
|
|
11
|
+
tooltipVisibleClassName,
|
|
12
|
+
} from "./style.js";
|
|
13
|
+
|
|
14
|
+
import type { ElementType, ReactNode } from "react";
|
|
15
|
+
import type { TooltipProps } from "./type.js";
|
|
16
|
+
|
|
17
|
+
export const Tooltip = <T extends ElementType = "div">({
|
|
18
|
+
ariaLabel,
|
|
19
|
+
children,
|
|
20
|
+
className,
|
|
21
|
+
id,
|
|
22
|
+
placeholder,
|
|
23
|
+
position = "down",
|
|
24
|
+
...rest
|
|
25
|
+
}: TooltipProps<T>) => {
|
|
26
|
+
const placeholderRef = useRef<ReactNode>(null);
|
|
27
|
+
|
|
28
|
+
const isFilled = placeholder !== null && placeholder !== undefined;
|
|
29
|
+
|
|
30
|
+
if (isFilled) placeholderRef.current = placeholder;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Tag {...Tag.forward<T>(rest)} className={cn(tooltipAnchorClassName, className)}>
|
|
34
|
+
{children}
|
|
35
|
+
<span
|
|
36
|
+
id={id}
|
|
37
|
+
role="tooltip"
|
|
38
|
+
aria-label={isFilled ? undefined : ariaLabel}
|
|
39
|
+
className={cn(
|
|
40
|
+
tooltipBubbleClassName,
|
|
41
|
+
tooltipTransitionClassName,
|
|
42
|
+
isFilled && tooltipVisibleClassName,
|
|
43
|
+
tooltipPositionClassName[position],
|
|
44
|
+
)}
|
|
45
|
+
>
|
|
46
|
+
{placeholderRef.current}
|
|
47
|
+
</span>
|
|
48
|
+
</Tag>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TagProps } from "react-renderable";
|
|
2
|
+
import type { ElementType, ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
export type TooltipPosition = "up" | "down" | "left" | "right";
|
|
5
|
+
|
|
6
|
+
export type TooltipOwnProps = {
|
|
7
|
+
ariaLabel?: string;
|
|
8
|
+
id?: string;
|
|
9
|
+
placeholder?: ReactNode;
|
|
10
|
+
position?: TooltipPosition;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type TooltipProps<T extends ElementType = "div"> = Omit<
|
|
15
|
+
TagProps<T, TooltipOwnProps>,
|
|
16
|
+
"aria-label"
|
|
17
|
+
>;
|
package/src/css/theme.css
CHANGED
|
@@ -23,6 +23,11 @@
|
|
|
23
23
|
--color-medium: var(--margo-color-medium);
|
|
24
24
|
--color-border: var(--margo-color-border);
|
|
25
25
|
|
|
26
|
+
/* Shadows */
|
|
27
|
+
--shadow-card: var(--margo-shadow-card);
|
|
28
|
+
--shadow-button: var(--margo-shadow-button);
|
|
29
|
+
--shadow-chip: var(--margo-shadow-chip);
|
|
30
|
+
|
|
26
31
|
/* Border widths */
|
|
27
32
|
--border-width-2: var(--margo-border-width-2);
|
|
28
33
|
|
package/src/css/variables.css
CHANGED
|
@@ -30,34 +30,44 @@
|
|
|
30
30
|
--margo-font-weight-black: 900;
|
|
31
31
|
|
|
32
32
|
--margo-border-width-2: 1.5px;
|
|
33
|
+
|
|
34
|
+
--margo-shadow-card-color: 0 0 0;
|
|
35
|
+
--margo-shadow-button-color: 0 0 0;
|
|
36
|
+
--margo-shadow-chip-color: 0 0 0;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
/* Colors Theme Light */
|
|
36
39
|
:root {
|
|
37
40
|
color-scheme: light;
|
|
38
41
|
|
|
39
42
|
--margo-color-neutral: #ffffff;
|
|
40
43
|
--margo-color-on-neutral: #000000;
|
|
41
|
-
--margo-color-main: #
|
|
42
|
-
--margo-color-on-main: #
|
|
43
|
-
--margo-color-primary: #
|
|
44
|
-
--margo-color-primary-darken: #
|
|
44
|
+
--margo-color-main: #f6f6f6;
|
|
45
|
+
--margo-color-on-main: #151515;
|
|
46
|
+
--margo-color-primary: #57e550;
|
|
47
|
+
--margo-color-primary-darken: #45b93f;
|
|
45
48
|
--margo-color-secondary: #d3d3d3;
|
|
46
49
|
--margo-color-medium: #7e7e7e;
|
|
47
|
-
--margo-color-border: #
|
|
50
|
+
--margo-color-border: #dcdcdc;
|
|
51
|
+
|
|
52
|
+
--margo-shadow-card: 0 4px 12px -4px rgb(var(--margo-shadow-card-color) / 0.1);
|
|
53
|
+
--margo-shadow-button: 0 2px 6px -2px rgb(var(--margo-shadow-button-color) / 0.1);
|
|
54
|
+
--margo-shadow-chip: 0 1px 3px -1px rgb(var(--margo-shadow-chip-color) / 0.5);
|
|
48
55
|
}
|
|
49
56
|
|
|
50
|
-
/* Colors Theme Dark */
|
|
51
57
|
.dark {
|
|
52
58
|
color-scheme: dark;
|
|
53
59
|
|
|
54
60
|
--margo-color-neutral: #000000;
|
|
55
61
|
--margo-color-on-neutral: #ffffff;
|
|
56
|
-
--margo-color-main: #
|
|
57
|
-
--margo-color-on-main: #
|
|
58
|
-
--margo-color-primary: #
|
|
59
|
-
--margo-color-primary-darken: #
|
|
62
|
+
--margo-color-main: #151515;
|
|
63
|
+
--margo-color-on-main: #eeeeee;
|
|
64
|
+
--margo-color-primary: #57e550;
|
|
65
|
+
--margo-color-primary-darken: #45b93f;
|
|
60
66
|
--margo-color-secondary: #2c2c2c;
|
|
61
67
|
--margo-color-medium: #999999;
|
|
62
68
|
--margo-color-border: #2c2c2c;
|
|
69
|
+
|
|
70
|
+
--margo-shadow-card: 0 4px 12px -4px rgb(var(--margo-shadow-card-color) / 0.5);
|
|
71
|
+
--margo-shadow-button: 0 2px 6px -2px rgb(var(--margo-shadow-button-color) / 0.5);
|
|
72
|
+
--margo-shadow-chip: 0 1px 3px -1px rgb(var(--margo-shadow-chip-color) / 0.5);
|
|
63
73
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { cloneElement } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
4
|
+
import { backgroundGlowBaseClassName, backgroundGlowSizeClassName } from "./style.js";
|
|
5
|
+
|
|
6
|
+
import type { PointerEvent } from "react";
|
|
7
|
+
import type { BackgroundGlowProps } from "./type.js";
|
|
8
|
+
|
|
9
|
+
import "./background_glow.css";
|
|
10
|
+
|
|
11
|
+
export const BackgroundGlow = ({ children, size = "md" }: BackgroundGlowProps) => {
|
|
12
|
+
const handlePointerMove = (event: PointerEvent<HTMLElement>) => {
|
|
13
|
+
const bounds = event.currentTarget.getBoundingClientRect();
|
|
14
|
+
|
|
15
|
+
event.currentTarget.style.setProperty("--margo-background-glow-x", `${event.clientX - bounds.left}px`);
|
|
16
|
+
event.currentTarget.style.setProperty("--margo-background-glow-y", `${event.clientY - bounds.top}px`);
|
|
17
|
+
|
|
18
|
+
children.props.onPointerMove?.(event);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return cloneElement(children, {
|
|
22
|
+
className: cn(backgroundGlowBaseClassName, backgroundGlowSizeClassName[size], children.props.className),
|
|
23
|
+
onPointerMove: handlePointerMove,
|
|
24
|
+
});
|
|
25
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BackgroundGlowSize } from "./type.js";
|
|
2
|
+
|
|
3
|
+
export const backgroundGlowBaseClassName = "relative isolate margo-background-glow";
|
|
4
|
+
|
|
5
|
+
export const backgroundGlowSizeClassName: Record<BackgroundGlowSize, string> = {
|
|
6
|
+
sm: "margo-background-glow--size-sm",
|
|
7
|
+
md: "margo-background-glow--size-md",
|
|
8
|
+
lg: "margo-background-glow--size-lg",
|
|
9
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PointerEventHandler, ReactElement } from "react";
|
|
2
|
+
|
|
3
|
+
export type BackgroundGlowSize = "sm" | "md" | "lg";
|
|
4
|
+
|
|
5
|
+
export type BackgroundGlowChildProps = {
|
|
6
|
+
className?: string;
|
|
7
|
+
onPointerMove?: PointerEventHandler<HTMLElement>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type BackgroundGlowProps = {
|
|
11
|
+
children: ReactElement<BackgroundGlowChildProps>;
|
|
12
|
+
size?: BackgroundGlowSize;
|
|
13
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cloneElement } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
4
|
+
import { borderGlowBaseClassName, borderGlowPositionClassName } from "./style.js";
|
|
5
|
+
|
|
6
|
+
import type { PointerEvent } from "react";
|
|
7
|
+
import type { BorderGlowProps } from "./type.js";
|
|
8
|
+
|
|
9
|
+
import "./border_glow.css";
|
|
10
|
+
|
|
11
|
+
export const BorderGlow = ({ children, position = "all" }: BorderGlowProps) => {
|
|
12
|
+
const handlePointerMove = (event: PointerEvent<HTMLElement>) => {
|
|
13
|
+
const bounds = event.currentTarget.getBoundingClientRect();
|
|
14
|
+
const x = event.clientX - bounds.left;
|
|
15
|
+
const y = event.clientY - bounds.top;
|
|
16
|
+
|
|
17
|
+
event.currentTarget.style.setProperty("--margo-border-glow-x", `${position === "right" ? bounds.width - x : x}px`);
|
|
18
|
+
event.currentTarget.style.setProperty("--margo-border-glow-y", `${position === "down" ? bounds.height - y : y}px`);
|
|
19
|
+
|
|
20
|
+
children.props.onPointerMove?.(event);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return cloneElement(children, {
|
|
24
|
+
className: cn(borderGlowBaseClassName, borderGlowPositionClassName[position], children.props.className),
|
|
25
|
+
onPointerMove: handlePointerMove,
|
|
26
|
+
});
|
|
27
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BorderGlowPosition } from "./type.js";
|
|
2
|
+
|
|
3
|
+
export const borderGlowBaseClassName = "relative isolate margo-border-glow";
|
|
4
|
+
|
|
5
|
+
export const borderGlowPositionClassName: Record<BorderGlowPosition, string> = {
|
|
6
|
+
up: "margo-border-glow--edge-top",
|
|
7
|
+
down: "margo-border-glow--edge-bottom",
|
|
8
|
+
left: "margo-border-glow--edge-left",
|
|
9
|
+
right: "margo-border-glow--edge-right",
|
|
10
|
+
all: "margo-border-glow--ring",
|
|
11
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PointerEventHandler, ReactElement } from "react";
|
|
2
|
+
|
|
3
|
+
export type BorderGlowPosition = "up" | "down" | "left" | "right" | "all";
|
|
4
|
+
|
|
5
|
+
export type BorderGlowChildProps = {
|
|
6
|
+
className?: string;
|
|
7
|
+
onPointerMove?: PointerEventHandler<HTMLElement>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type BorderGlowProps = {
|
|
11
|
+
children: ReactElement<BorderGlowChildProps>;
|
|
12
|
+
position?: BorderGlowPosition;
|
|
13
|
+
};
|
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
import { cloneElement } from "react";
|
|
2
2
|
|
|
3
3
|
import { Guard } from "react-renderable";
|
|
4
|
-
import { cn } from "../helpers/cn";
|
|
5
|
-
import { useRipple } from "../hooks/use_ripple";
|
|
6
4
|
|
|
7
|
-
import
|
|
5
|
+
import { cn } from "../../utils/cn/cn.js";
|
|
6
|
+
import { useRipple } from "./use_ripple.js";
|
|
8
7
|
|
|
9
|
-
import "
|
|
10
|
-
|
|
11
|
-
export type RippleChildProps = {
|
|
12
|
-
children?: ReactNode;
|
|
13
|
-
className?: string;
|
|
14
|
-
onPointerDown?: PointerEventHandler<HTMLElement>;
|
|
15
|
-
};
|
|
8
|
+
import type { PointerEvent } from "react";
|
|
9
|
+
import type { RippleProps } from "./type.js";
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
children: ReactElement<RippleChildProps>;
|
|
19
|
-
rippleClassName?: string;
|
|
20
|
-
};
|
|
11
|
+
import "./ripple.css";
|
|
21
12
|
|
|
22
13
|
export const Ripple = ({ children, rippleClassName = "bg-primary-darken" }: RippleProps) => {
|
|
23
14
|
const { endRipple, ripple, startRipple } = useRipple();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PointerEvent, PointerEventHandler, ReactElement, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export type RippleChildProps = {
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
className?: string;
|
|
6
|
+
onPointerDown?: PointerEventHandler<HTMLElement>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type RippleProps = {
|
|
10
|
+
children: ReactElement<RippleChildProps>;
|
|
11
|
+
rippleClassName?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type RippleState = {
|
|
15
|
+
id: number;
|
|
16
|
+
size: number;
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type UseRipple = {
|
|
22
|
+
ripple: RippleState | null;
|
|
23
|
+
endRipple: () => void;
|
|
24
|
+
startRipple: (event: PointerEvent<HTMLElement>) => void;
|
|
25
|
+
};
|
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
|
|
3
3
|
import type { PointerEvent } from "react";
|
|
4
|
-
|
|
5
|
-
type Ripple = {
|
|
6
|
-
id: number;
|
|
7
|
-
size: number;
|
|
8
|
-
x: number;
|
|
9
|
-
y: number;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
type UseRipple = {
|
|
13
|
-
ripple: Ripple | null;
|
|
14
|
-
endRipple: () => void;
|
|
15
|
-
startRipple: (event: PointerEvent<HTMLElement>) => void;
|
|
16
|
-
};
|
|
4
|
+
import type { RippleState, UseRipple } from "./type.js";
|
|
17
5
|
|
|
18
6
|
export const useRipple = (): UseRipple => {
|
|
19
|
-
const [ripple, setRipple] = useState<
|
|
7
|
+
const [ripple, setRipple] = useState<RippleState | null>(null);
|
|
20
8
|
|
|
21
9
|
const startRipple = (event: PointerEvent<HTMLElement>) => {
|
|
22
10
|
const bounds = event.currentTarget.getBoundingClientRect();
|
package/src/hooks/use_theme.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { margoThemeClient } from "../utils/theme/theme.js";
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type { MargoTheme } from "../utils/theme/type.js";
|
|
6
6
|
|
|
7
|
-
export const
|
|
8
|
-
const [theme, setThemeState] = useState<
|
|
7
|
+
export const useMargoTheme = (): [MargoTheme, (next: MargoTheme | ((current: MargoTheme) => MargoTheme)) => void] => {
|
|
8
|
+
const [theme, setThemeState] = useState<MargoTheme>(() => margoThemeClient.get());
|
|
9
9
|
|
|
10
10
|
useEffect(() => {
|
|
11
11
|
if (typeof document === "undefined") return;
|
|
12
12
|
|
|
13
13
|
const root = document.documentElement;
|
|
14
|
-
setThemeState(
|
|
14
|
+
setThemeState(margoThemeClient.get());
|
|
15
15
|
|
|
16
16
|
const observer = new MutationObserver(() => {
|
|
17
|
-
setThemeState(
|
|
17
|
+
setThemeState(margoThemeClient.get());
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
observer.observe(root, { attributes: true, attributeFilter: ["class"] });
|
|
@@ -22,8 +22,8 @@ export const useTheme = (): [Theme, (next: Theme | ((current: Theme) => Theme))
|
|
|
22
22
|
return () => observer.disconnect();
|
|
23
23
|
}, []);
|
|
24
24
|
|
|
25
|
-
const setTheme = useCallback((next:
|
|
26
|
-
|
|
25
|
+
const setTheme = useCallback((next: MargoTheme | ((current: MargoTheme) => MargoTheme)) => {
|
|
26
|
+
margoThemeClient.set(typeof next === "function" ? next(margoThemeClient.get()) : next);
|
|
27
27
|
}, []);
|
|
28
28
|
|
|
29
29
|
return [theme, setTheme];
|
package/src/index.ts
CHANGED
|
@@ -1,22 +1,39 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
export
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
export type {
|
|
21
|
-
|
|
22
|
-
export type {
|
|
1
|
+
export { Blockquote } from "./components/blockquote/blockquote.js";
|
|
2
|
+
export { Button } from "./components/button/button.js";
|
|
3
|
+
export { Card } from "./components/card/card.js";
|
|
4
|
+
export { Chip } from "./components/chip/chip.js";
|
|
5
|
+
export { ProgressBar } from "./components/progress_bar/progress_bar.js";
|
|
6
|
+
export { Spinner } from "./components/spinner/spinner.js";
|
|
7
|
+
export { Tooltip } from "./components/tooltip/tooltip.js";
|
|
8
|
+
|
|
9
|
+
export { BackgroundGlow } from "./hoc/background_glow/background_glow.js";
|
|
10
|
+
export { BorderGlow } from "./hoc/border_glow/border_glow.js";
|
|
11
|
+
export { Ripple } from "./hoc/ripple/ripple.js";
|
|
12
|
+
|
|
13
|
+
export { MetaColorScheme } from "./meta/meta_color_scheme/meta_color_scheme.js";
|
|
14
|
+
|
|
15
|
+
export { useMargoTheme } from "./hooks/use_theme.js";
|
|
16
|
+
|
|
17
|
+
export { cn } from "./utils/cn/cn.js";
|
|
18
|
+
export { margoTheme, margoThemeClient } from "./utils/theme/theme.js";
|
|
19
|
+
|
|
20
|
+
export type { BlockquoteProps } from "./components/blockquote/type.js";
|
|
21
|
+
|
|
22
|
+
export type {
|
|
23
|
+
ButtonIconLabelProps,
|
|
24
|
+
ButtonIconLabelSide,
|
|
25
|
+
ButtonIconProps,
|
|
26
|
+
ButtonLabelProps,
|
|
27
|
+
ButtonProps,
|
|
28
|
+
} from "./components/button/type.js";
|
|
29
|
+
export type { CardProps } from "./components/card/type.js";
|
|
30
|
+
export type { ChipProps } from "./components/chip/type.js";
|
|
31
|
+
export type { ProgressBarMode, ProgressBarProps } from "./components/progress_bar/type.js";
|
|
32
|
+
export type { SpinnerProps } from "./components/spinner/type.js";
|
|
33
|
+
export type { TooltipPosition, TooltipProps } from "./components/tooltip/type.js";
|
|
34
|
+
|
|
35
|
+
export type { BackgroundGlowProps, BackgroundGlowSize } from "./hoc/background_glow/type.js";
|
|
36
|
+
export type { BorderGlowPosition, BorderGlowProps } from "./hoc/border_glow/type.js";
|
|
37
|
+
export type { RippleProps } from "./hoc/ripple/type.js";
|
|
38
|
+
|
|
39
|
+
export type { MargoTheme, MargoThemeClient } from "./utils/theme/type.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { MargoTheme, MargoThemeClient, MargoThemeConstants } from "./type.js";
|
|
2
|
+
|
|
3
|
+
export const margoTheme: MargoThemeConstants = {
|
|
4
|
+
LIGHT: "light",
|
|
5
|
+
DARK: "dark",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const get = (): MargoTheme => {
|
|
9
|
+
if (typeof document === "undefined") return margoTheme.DARK;
|
|
10
|
+
|
|
11
|
+
return document.documentElement.classList.contains(margoTheme.DARK) ? margoTheme.DARK : margoTheme.LIGHT;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const set = (next: MargoTheme) => {
|
|
15
|
+
if (typeof document === "undefined") return;
|
|
16
|
+
|
|
17
|
+
document.documentElement.classList.toggle(margoTheme.DARK, next === margoTheme.DARK);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const toggle = () => set(get() === margoTheme.DARK ? margoTheme.LIGHT : margoTheme.DARK);
|
|
21
|
+
|
|
22
|
+
export const margoThemeClient: MargoThemeClient = {
|
|
23
|
+
get,
|
|
24
|
+
set,
|
|
25
|
+
toggle,
|
|
26
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type MargoTheme = "light" | "dark";
|
|
2
|
+
|
|
3
|
+
export type MargoThemeConstants = {
|
|
4
|
+
readonly LIGHT: MargoTheme;
|
|
5
|
+
readonly DARK: MargoTheme;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type MargoThemeClient = {
|
|
9
|
+
get: () => MargoTheme;
|
|
10
|
+
set: (next: MargoTheme) => void;
|
|
11
|
+
toggle: () => void;
|
|
12
|
+
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { cloneElement } from "react";
|
|
2
|
-
|
|
3
|
-
import { cn } from "../helpers/cn";
|
|
4
|
-
|
|
5
|
-
import type { PointerEvent, PointerEventHandler, ReactElement } from "react";
|
|
6
|
-
|
|
7
|
-
import "./background_glow.css";
|
|
8
|
-
|
|
9
|
-
export type BackgroundGlowSize = "sm" | "md" | "lg";
|
|
10
|
-
|
|
11
|
-
export type BackgroundGlowChildProps = {
|
|
12
|
-
className?: string;
|
|
13
|
-
onPointerMove?: PointerEventHandler<HTMLElement>;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export type BackgroundGlowProps = {
|
|
17
|
-
children: ReactElement<BackgroundGlowChildProps>;
|
|
18
|
-
size?: BackgroundGlowSize;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const BACKGROUND_GLOW_SIZE_CLASS: Record<BackgroundGlowSize, string> = {
|
|
22
|
-
sm: "margo-background-glow--size-sm",
|
|
23
|
-
md: "margo-background-glow--size-md",
|
|
24
|
-
lg: "margo-background-glow--size-lg",
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export const BackgroundGlow = ({ children, size = "md" }: BackgroundGlowProps) => {
|
|
28
|
-
const handlePointerMove = (event: PointerEvent<HTMLElement>) => {
|
|
29
|
-
const bounds = event.currentTarget.getBoundingClientRect();
|
|
30
|
-
|
|
31
|
-
event.currentTarget.style.setProperty("--margo-background-glow-x", `${event.clientX - bounds.left}px`);
|
|
32
|
-
event.currentTarget.style.setProperty("--margo-background-glow-y", `${event.clientY - bounds.top}px`);
|
|
33
|
-
|
|
34
|
-
children.props.onPointerMove?.(event);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
return cloneElement(children, {
|
|
38
|
-
className: cn(
|
|
39
|
-
"relative isolate margo-background-glow",
|
|
40
|
-
BACKGROUND_GLOW_SIZE_CLASS[size],
|
|
41
|
-
children.props.className,
|
|
42
|
-
),
|
|
43
|
-
onPointerMove: handlePointerMove,
|
|
44
|
-
});
|
|
45
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { cloneElement } from "react";
|
|
2
|
-
|
|
3
|
-
import { cn } from "../helpers/cn";
|
|
4
|
-
|
|
5
|
-
import type { PointerEvent, PointerEventHandler, ReactElement } from "react";
|
|
6
|
-
|
|
7
|
-
import "./border_glow.css";
|
|
8
|
-
|
|
9
|
-
export type BorderGlowPosition = "up" | "down" | "left" | "right" | "all";
|
|
10
|
-
|
|
11
|
-
export type BorderGlowChildProps = {
|
|
12
|
-
className?: string;
|
|
13
|
-
onPointerMove?: PointerEventHandler<HTMLElement>;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export type BorderGlowProps = {
|
|
17
|
-
children: ReactElement<BorderGlowChildProps>;
|
|
18
|
-
position?: BorderGlowPosition;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const BORDER_GLOW_POSITION_CLASS: Record<BorderGlowPosition, string> = {
|
|
22
|
-
up: "margo-border-glow--edge-top",
|
|
23
|
-
down: "margo-border-glow--edge-bottom",
|
|
24
|
-
left: "margo-border-glow--edge-left",
|
|
25
|
-
right: "margo-border-glow--edge-right",
|
|
26
|
-
all: "margo-border-glow--ring",
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const BorderGlow = ({ children, position = "all" }: BorderGlowProps) => {
|
|
30
|
-
const handlePointerMove = (event: PointerEvent<HTMLElement>) => {
|
|
31
|
-
const bounds = event.currentTarget.getBoundingClientRect();
|
|
32
|
-
const x = event.clientX - bounds.left;
|
|
33
|
-
const y = event.clientY - bounds.top;
|
|
34
|
-
|
|
35
|
-
event.currentTarget.style.setProperty("--margo-border-glow-x", `${position === "right" ? bounds.width - x : x}px`);
|
|
36
|
-
event.currentTarget.style.setProperty("--margo-border-glow-y", `${position === "down" ? bounds.height - y : y}px`);
|
|
37
|
-
|
|
38
|
-
children.props.onPointerMove?.(event);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
return cloneElement(children, {
|
|
42
|
-
className: cn("relative isolate margo-border-glow", BORDER_GLOW_POSITION_CLASS[position], children.props.className),
|
|
43
|
-
onPointerMove: handlePointerMove,
|
|
44
|
-
});
|
|
45
|
-
};
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { Tag } from "react-renderable";
|
|
2
|
-
import { cn } from "../helpers/cn";
|
|
3
|
-
import { Ripple } from "./ripple";
|
|
4
|
-
|
|
5
|
-
import type { CSSProperties, ElementType, MouseEvent, MouseEventHandler, ReactNode } from "react";
|
|
6
|
-
import type { TagProps } from "react-renderable";
|
|
7
|
-
|
|
8
|
-
const CLASSNAME_BASE = `group/button flex h-8 w-fit shrink-0 items-center px-2 rounded-lg border-2 text-on-main
|
|
9
|
-
hover:border-on-main hover:text-on-main focus-visible:margo-border-gradient-primary
|
|
10
|
-
focus-visible:outline focus-visible:outline-offset-1 focus-visible:outline-on-main`;
|
|
11
|
-
const CLASSNAME_SURFACE = "border-border bg-main";
|
|
12
|
-
const CLASSNAME_ICON = "relative z-10 flex size-4 min-w-0 shrink-0 items-center justify-center [direction:ltr]";
|
|
13
|
-
const CLASSNAME_LABEL = "relative z-10 flex items-center text-sm whitespace-nowrap lowercase [direction:ltr] px-0.5";
|
|
14
|
-
const CLASSNAME_GRID = "grid items-center gap-0 transition-[grid-template-columns,gap] duration-[180ms] ease-in-out";
|
|
15
|
-
const CLASSNAME_SLOT = `min-w-0 overflow-hidden opacity-0 transition-opacity duration-[180ms] ease-in
|
|
16
|
-
group-hover/button:opacity-100 group-focus-visible/button:opacity-100 group-data-[open=true]/button:opacity-100`;
|
|
17
|
-
|
|
18
|
-
export type ButtonIconLabelSide = "start" | "end";
|
|
19
|
-
|
|
20
|
-
export type ButtonProps<T extends ElementType = "button"> = TagProps<T> & {
|
|
21
|
-
clickable?: boolean;
|
|
22
|
-
active?: boolean;
|
|
23
|
-
open?: boolean;
|
|
24
|
-
blurOnClick?: boolean;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export const Button = <T extends ElementType = "button">(props: ButtonProps<T>) => {
|
|
28
|
-
const { as, clickable = true, active = false, open = false, blurOnClick = true, ...restProps } = props;
|
|
29
|
-
const nativeProps = { as: as ?? "button", ...restProps } as TagProps<T>;
|
|
30
|
-
const onClick = props.onClick as MouseEventHandler<HTMLElement> | undefined;
|
|
31
|
-
|
|
32
|
-
const handleClick = (event: MouseEvent<HTMLElement>) => {
|
|
33
|
-
onClick?.(event);
|
|
34
|
-
if (blurOnClick && event.detail > 0) setTimeout(() => event.currentTarget.blur(), 150);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<Ripple>
|
|
39
|
-
<Tag
|
|
40
|
-
{...nativeProps}
|
|
41
|
-
data-open={open}
|
|
42
|
-
onClick={handleClick}
|
|
43
|
-
className={cn(
|
|
44
|
-
CLASSNAME_BASE,
|
|
45
|
-
active ? "margo-border-gradient-primary" : CLASSNAME_SURFACE,
|
|
46
|
-
!clickable && "pointer-events-none",
|
|
47
|
-
props.className,
|
|
48
|
-
)}
|
|
49
|
-
/>
|
|
50
|
-
</Ripple>
|
|
51
|
-
);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
Button.Icon = ({ icon, className = null }: { icon: ReactNode; className?: string | null }) => (
|
|
55
|
-
<span className={cn(CLASSNAME_ICON, className)}>{icon}</span>
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
Button.Label = ({ label, className = null }: { label: string; className?: string | null }) => (
|
|
59
|
-
<span className={cn(CLASSNAME_LABEL, className)}>{label}</span>
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
Button.IconLabel = ({
|
|
63
|
-
icon,
|
|
64
|
-
label,
|
|
65
|
-
gap = "0.5rem",
|
|
66
|
-
reverse = false,
|
|
67
|
-
side = "end",
|
|
68
|
-
className = null,
|
|
69
|
-
}: {
|
|
70
|
-
icon: ReactNode;
|
|
71
|
-
label: ReactNode;
|
|
72
|
-
gap?: string;
|
|
73
|
-
reverse?: boolean;
|
|
74
|
-
side?: ButtonIconLabelSide;
|
|
75
|
-
className?: string | null;
|
|
76
|
-
}) => {
|
|
77
|
-
return (
|
|
78
|
-
<span
|
|
79
|
-
style={{ "--button-gap": gap } as CSSProperties}
|
|
80
|
-
className={cn(
|
|
81
|
-
CLASSNAME_GRID,
|
|
82
|
-
side === "start" && "[direction:rtl]",
|
|
83
|
-
!reverse &&
|
|
84
|
-
`grid-cols-[1rem_0fr] group-hover/button:grid-cols-[1rem_1fr] group-hover/button:gap-(--button-gap)
|
|
85
|
-
group-focus-visible/button:grid-cols-[1rem_1fr] group-focus-visible/button:gap-(--button-gap)
|
|
86
|
-
group-data-[open=true]/button:grid-cols-[1rem_1fr] group-data-[open=true]/button:gap-(--button-gap)`,
|
|
87
|
-
reverse &&
|
|
88
|
-
`grid-cols-[auto_0rem] group-hover/button:grid-cols-[auto_1rem] group-hover/button:gap-(--button-gap)
|
|
89
|
-
group-focus-visible/button:grid-cols-[auto_1rem] group-focus-visible/button:gap-(--button-gap)
|
|
90
|
-
group-data-[open=true]/button:grid-cols-[auto_1rem] group-data-[open=true]/button:gap-(--button-gap)`,
|
|
91
|
-
className,
|
|
92
|
-
)}
|
|
93
|
-
>
|
|
94
|
-
{reverse ? label : icon}
|
|
95
|
-
<span className={CLASSNAME_SLOT}>{reverse ? icon : label}</span>
|
|
96
|
-
</span>
|
|
97
|
-
);
|
|
98
|
-
};
|
package/src/components/card.tsx
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Tag } from "react-renderable";
|
|
2
|
-
import { cn } from "../helpers/cn";
|
|
3
|
-
|
|
4
|
-
import type { ElementType } from "react";
|
|
5
|
-
import type { TagProps } from "react-renderable";
|
|
6
|
-
|
|
7
|
-
export type CardProps<T extends ElementType = "div"> = TagProps<T>;
|
|
8
|
-
|
|
9
|
-
export const Card = <T extends ElementType = "div">(props: CardProps<T>) => {
|
|
10
|
-
const { as, className, children, ...restProps } = props;
|
|
11
|
-
const nativeProps = { as: as ?? "div", ...restProps } as TagProps<T>;
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<Tag
|
|
15
|
-
{...nativeProps}
|
|
16
|
-
className={cn(
|
|
17
|
-
"rounded-lg border-2 border-border bg-main p-5 text-on-main",
|
|
18
|
-
`focus:margo-border-gradient-primary focus-visible:outline focus-visible:outline-offset-1
|
|
19
|
-
focus-visible:outline-on-main`,
|
|
20
|
-
className,
|
|
21
|
-
)}
|
|
22
|
-
>
|
|
23
|
-
{children}
|
|
24
|
-
</Tag>
|
|
25
|
-
);
|
|
26
|
-
};
|
package/src/components/chip.tsx
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { Tag } from "react-renderable";
|
|
2
|
-
import { cn } from "../helpers/cn";
|
|
3
|
-
|
|
4
|
-
import type { ElementType } from "react";
|
|
5
|
-
import type { TagProps } from "react-renderable";
|
|
6
|
-
|
|
7
|
-
export type ChipProps<T extends ElementType = "div"> = TagProps<T> & {
|
|
8
|
-
active?: boolean;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export const Chip = <T extends ElementType = "div">(props: ChipProps<T>) => {
|
|
12
|
-
const { as, active = false, className, children, ...restProps } = props;
|
|
13
|
-
const nativeProps = { as: as ?? "div", ...restProps } as TagProps<T>;
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<Tag
|
|
17
|
-
{...nativeProps}
|
|
18
|
-
className={cn(
|
|
19
|
-
"bg-secondary px-3 py-0.5 whitespace-nowrap lowercase flex-none leading-none rounded-md text-mc",
|
|
20
|
-
className,
|
|
21
|
-
active && "bg-on-main text-main",
|
|
22
|
-
)}
|
|
23
|
-
>
|
|
24
|
-
{children}
|
|
25
|
-
</Tag>
|
|
26
|
-
);
|
|
27
|
-
};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { Tag } from "react-renderable";
|
|
2
|
-
import { cn } from "../helpers/cn";
|
|
3
|
-
|
|
4
|
-
import { useRef } from "react";
|
|
5
|
-
|
|
6
|
-
import type { ElementType, ReactNode } from "react";
|
|
7
|
-
import type { TagProps } from "react-renderable";
|
|
8
|
-
|
|
9
|
-
export type TooltipPosition = "up" | "down" | "left" | "right";
|
|
10
|
-
|
|
11
|
-
export type TooltipProps<T extends ElementType = "div"> = Omit<TagProps<T>, "aria-label" | "children" | "id"> & {
|
|
12
|
-
ariaLabel?: string;
|
|
13
|
-
id?: string;
|
|
14
|
-
placeholder?: ReactNode;
|
|
15
|
-
position?: TooltipPosition;
|
|
16
|
-
children?: ReactNode;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const TOOLTIP_POSITION_CLASS: Record<TooltipPosition, string> = {
|
|
20
|
-
up: "bottom-[120%] left-1/2 -translate-x-1/2",
|
|
21
|
-
down: "top-[120%] left-1/2 -translate-x-1/2",
|
|
22
|
-
left: "top-1/2 right-[calc(100%+0.5rem)] -translate-y-1/2",
|
|
23
|
-
right: "top-1/2 left-[calc(100%+0.5rem)] -translate-y-1/2",
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export const Tooltip = <T extends ElementType = "div">(props: TooltipProps<T>) => {
|
|
27
|
-
const { as, ariaLabel, children, className, id, placeholder, position = "down", ...restProps } = props;
|
|
28
|
-
const nativeProps = { as: as ?? "div", ...restProps } as TagProps<T>;
|
|
29
|
-
|
|
30
|
-
const placeholderRef = useRef<ReactNode>(null);
|
|
31
|
-
|
|
32
|
-
const isFilled = placeholder !== null && placeholder !== undefined;
|
|
33
|
-
|
|
34
|
-
if (isFilled) placeholderRef.current = placeholder;
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<Tag {...nativeProps} className={cn("group relative inline-block size-fit", className)}>
|
|
38
|
-
{children}
|
|
39
|
-
<span
|
|
40
|
-
id={id}
|
|
41
|
-
role="tooltip"
|
|
42
|
-
aria-label={isFilled ? undefined : ariaLabel}
|
|
43
|
-
className={cn(
|
|
44
|
-
`pointer-events-none absolute z-10 rounded-sm bg-on-main px-2 py-0.5 text-xs text-main lowercase
|
|
45
|
-
whitespace-nowrap opacity-0`,
|
|
46
|
-
"transition-opacity duration-[250ms] ease-in-out",
|
|
47
|
-
isFilled && "group-hover:opacity-100 group-focus-within:opacity-100",
|
|
48
|
-
TOOLTIP_POSITION_CLASS[position],
|
|
49
|
-
)}
|
|
50
|
-
>
|
|
51
|
-
{placeholderRef.current}
|
|
52
|
-
</span>
|
|
53
|
-
</Tag>
|
|
54
|
-
);
|
|
55
|
-
};
|
package/src/helpers/theme.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export type Theme = "light" | "dark";
|
|
2
|
-
|
|
3
|
-
type ThemeConstants = {
|
|
4
|
-
readonly LIGHT: Theme;
|
|
5
|
-
readonly DARK: Theme;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export const THEME: ThemeConstants = {
|
|
9
|
-
LIGHT: "light",
|
|
10
|
-
DARK: "dark",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const get = (): Theme => {
|
|
14
|
-
if (typeof document === "undefined") return THEME.DARK;
|
|
15
|
-
|
|
16
|
-
return document.documentElement.classList.contains(THEME.DARK) ? THEME.DARK : THEME.LIGHT;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const set = (next: Theme) => {
|
|
20
|
-
if (typeof document === "undefined") return;
|
|
21
|
-
|
|
22
|
-
document.documentElement.classList.toggle(THEME.DARK, next === THEME.DARK);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const toggle = () => set(get() === THEME.DARK ? THEME.LIGHT : THEME.DARK);
|
|
26
|
-
|
|
27
|
-
export const themeClient = {
|
|
28
|
-
get,
|
|
29
|
-
set,
|
|
30
|
-
toggle,
|
|
31
|
-
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|