margo-ui 1.3.2 → 1.3.4
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/button/button.tsx +10 -2
- package/src/components/button/style.ts +4 -0
- package/src/components/button/type.ts +1 -0
- package/src/components/header/style.ts +1 -1
- package/src/components/item/item.tsx +18 -4
- package/src/components/item/style.ts +6 -2
- package/src/components/item/type.ts +3 -1
- package/src/components/layer/layer.tsx +28 -3
- package/src/components/snippet/snippet.tsx +15 -12
- package/src/hoc/pointer_holder/pointer_holder.tsx +40 -0
- package/src/hoc/pointer_holder/type.ts +18 -0
- package/src/hoc/ripple/ripple.tsx +2 -2
- package/src/hoc/ripple/type.ts +1 -0
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -5,11 +5,13 @@ import { cn } from "../../utils/cn/cn.js";
|
|
|
5
5
|
import {
|
|
6
6
|
buttonActiveClassName,
|
|
7
7
|
buttonBaseClassName,
|
|
8
|
+
buttonDisabledClassName,
|
|
8
9
|
buttonGridClassName,
|
|
9
10
|
buttonGridForwardClassName,
|
|
10
11
|
buttonGridReverseClassName,
|
|
11
12
|
buttonIconClassName,
|
|
12
13
|
buttonLabelClassName,
|
|
14
|
+
buttonNotClickableClassName,
|
|
13
15
|
buttonSlotClassName,
|
|
14
16
|
buttonSurfaceClassName,
|
|
15
17
|
} from "./style.js";
|
|
@@ -19,6 +21,7 @@ import type { ButtonIconLabelProps, ButtonIconProps, ButtonLabelProps, ButtonPro
|
|
|
19
21
|
|
|
20
22
|
export const Button = <T extends ElementType = "button">({
|
|
21
23
|
clickable = true,
|
|
24
|
+
disabled = false,
|
|
22
25
|
active = false,
|
|
23
26
|
open = false,
|
|
24
27
|
onClickBlur,
|
|
@@ -27,6 +30,7 @@ export const Button = <T extends ElementType = "button">({
|
|
|
27
30
|
...rest
|
|
28
31
|
}: ButtonProps<T>) => {
|
|
29
32
|
const handleClick = (event: MouseEvent<HTMLElement>) => {
|
|
33
|
+
if (disabled) return;
|
|
30
34
|
onClick?.(event);
|
|
31
35
|
if (typeof onClickBlur === "function") {
|
|
32
36
|
const target = event.currentTarget;
|
|
@@ -36,15 +40,19 @@ export const Button = <T extends ElementType = "button">({
|
|
|
36
40
|
};
|
|
37
41
|
|
|
38
42
|
return (
|
|
39
|
-
<Ripple>
|
|
43
|
+
<Ripple disabled={disabled || !clickable}>
|
|
40
44
|
<Tag
|
|
41
45
|
{...Tag.forward<T>(rest, "button")}
|
|
42
46
|
data-margo-open={open}
|
|
47
|
+
data-margo-disabled={disabled}
|
|
48
|
+
aria-disabled={disabled || undefined}
|
|
49
|
+
tabIndex={disabled || !clickable ? -1 : undefined}
|
|
43
50
|
onClick={handleClick}
|
|
44
51
|
className={cn(
|
|
45
52
|
buttonBaseClassName,
|
|
46
53
|
active ? buttonActiveClassName : buttonSurfaceClassName,
|
|
47
|
-
!clickable &&
|
|
54
|
+
!clickable && buttonNotClickableClassName,
|
|
55
|
+
disabled && buttonDisabledClassName,
|
|
48
56
|
className,
|
|
49
57
|
)}
|
|
50
58
|
/>
|
|
@@ -6,6 +6,10 @@ export const buttonSurfaceClassName = "border-border bg-main";
|
|
|
6
6
|
|
|
7
7
|
export const buttonActiveClassName = "margo-border-gradient-primary";
|
|
8
8
|
|
|
9
|
+
export const buttonNotClickableClassName = "pointer-events-none cursor-auto";
|
|
10
|
+
|
|
11
|
+
export const buttonDisabledClassName = "pointer-events-none cursor-auto opacity-40";
|
|
12
|
+
|
|
9
13
|
export const buttonIconClassName =
|
|
10
14
|
"relative z-10 flex size-4 min-w-0 shrink-0 items-center justify-center [direction:ltr]";
|
|
11
15
|
|
|
@@ -6,8 +6,10 @@ import { cn } from "../../utils/cn/cn.js";
|
|
|
6
6
|
import {
|
|
7
7
|
itemActiveClassName,
|
|
8
8
|
itemBaseClassName,
|
|
9
|
+
itemDisabledClassName,
|
|
9
10
|
itemIconClassName,
|
|
10
11
|
itemLabelClassName,
|
|
12
|
+
itemNotClickableClassName,
|
|
11
13
|
itemSurfaceClassName,
|
|
12
14
|
} from "./style.js";
|
|
13
15
|
|
|
@@ -15,7 +17,9 @@ import type { ElementType, MouseEvent } from "react";
|
|
|
15
17
|
import { Ripple } from "../../hoc/ripple/ripple.js";
|
|
16
18
|
import type { ItemIconProps, ItemLabelProps, ItemProps } from "./type.js";
|
|
17
19
|
|
|
18
|
-
export const Item = <T extends ElementType = "
|
|
20
|
+
export const Item = <T extends ElementType = "button">({
|
|
21
|
+
clickable = true,
|
|
22
|
+
disabled = false,
|
|
19
23
|
active = false,
|
|
20
24
|
onClickBlur,
|
|
21
25
|
onClick,
|
|
@@ -24,6 +28,7 @@ export const Item = <T extends ElementType = "div">({
|
|
|
24
28
|
...rest
|
|
25
29
|
}: ItemProps<T>) => {
|
|
26
30
|
const handleClick = (event: MouseEvent<HTMLElement>) => {
|
|
31
|
+
if (disabled) return;
|
|
27
32
|
onClick?.(event);
|
|
28
33
|
if (typeof onClickBlur === "function") {
|
|
29
34
|
const target = event.currentTarget;
|
|
@@ -33,12 +38,21 @@ export const Item = <T extends ElementType = "div">({
|
|
|
33
38
|
};
|
|
34
39
|
|
|
35
40
|
return (
|
|
36
|
-
<Ripple>
|
|
41
|
+
<Ripple disabled={disabled || !clickable}>
|
|
37
42
|
<Tag
|
|
38
|
-
{...Tag.forward<T>(rest)}
|
|
43
|
+
{...Tag.forward<T>(rest, "button")}
|
|
39
44
|
data-margo-active={active}
|
|
45
|
+
data-margo-disabled={disabled}
|
|
46
|
+
aria-disabled={disabled || undefined}
|
|
47
|
+
tabIndex={disabled || !clickable ? -1 : undefined}
|
|
40
48
|
onClick={handleClick}
|
|
41
|
-
className={cn(
|
|
49
|
+
className={cn(
|
|
50
|
+
itemBaseClassName,
|
|
51
|
+
active ? itemActiveClassName : itemSurfaceClassName,
|
|
52
|
+
!clickable && itemNotClickableClassName,
|
|
53
|
+
disabled && itemDisabledClassName,
|
|
54
|
+
className,
|
|
55
|
+
)}
|
|
42
56
|
>
|
|
43
57
|
{children}
|
|
44
58
|
</Tag>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export const itemBaseClassName = `group/item flex w-full items-center gap-1 rounded-lg border-2 px-3 py-2 text-on-main
|
|
2
|
-
text-sm focus-visible:margo-border-gradient-primary focus-visible:outline
|
|
1
|
+
export const itemBaseClassName = `group/item flex w-full cursor-pointer items-center gap-1 rounded-lg border-2 px-3 py-2 text-on-main
|
|
2
|
+
text-sm focus-visible:margo-border-gradient-primary focus-visible:outline select-none
|
|
3
3
|
focus-visible:outline-offset-1 focus-visible:outline-on-main`;
|
|
4
4
|
|
|
5
5
|
export const itemSurfaceClassName = `border-transparent text-medium hover:border-border hover:text-on-main
|
|
@@ -7,6 +7,10 @@ hover:shadow-item focus-visible:shadow-item`;
|
|
|
7
7
|
|
|
8
8
|
export const itemActiveClassName = "margo-border-gradient-primary text-on-main shadow-item";
|
|
9
9
|
|
|
10
|
+
export const itemNotClickableClassName = "pointer-events-none cursor-auto";
|
|
11
|
+
|
|
12
|
+
export const itemDisabledClassName = "pointer-events-none cursor-auto opacity-40";
|
|
13
|
+
|
|
10
14
|
export const itemIconClassName = `ml-auto flex size-4 shrink-0 items-center justify-center first:ml-0
|
|
11
15
|
[[data-margo-item-slot]+&]:ml-0`;
|
|
12
16
|
|
|
@@ -2,12 +2,14 @@ import type { TagProps } from "react-renderable";
|
|
|
2
2
|
import type { ElementType, MouseEventHandler, ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
export type ItemOwnProps = {
|
|
5
|
+
clickable?: boolean;
|
|
6
|
+
disabled?: boolean;
|
|
5
7
|
active?: boolean;
|
|
6
8
|
onClick?: MouseEventHandler<HTMLElement>;
|
|
7
9
|
onClickBlur?: MouseEventHandler<HTMLElement>;
|
|
8
10
|
};
|
|
9
11
|
|
|
10
|
-
export type ItemProps<T extends ElementType = "
|
|
12
|
+
export type ItemProps<T extends ElementType = "button"> = TagProps<T, ItemOwnProps>;
|
|
11
13
|
|
|
12
14
|
export type ItemIconProps = {
|
|
13
15
|
icon?: ReactNode;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
|
|
3
3
|
import { cn } from "../../utils/cn/cn.js";
|
|
4
4
|
import { layerBaseClassName } from "./style.js";
|
|
@@ -10,13 +10,38 @@ import "./layer.css";
|
|
|
10
10
|
|
|
11
11
|
export const Layer = ({ open = false, onClose, dismissible = true, className, children, ...rest }: LayerProps) => {
|
|
12
12
|
const layerRef = useRef<HTMLDialogElement>(null);
|
|
13
|
+
const childrenRef = useRef(children);
|
|
14
|
+
|
|
15
|
+
const [isMounted, setIsMounted] = useState(open);
|
|
16
|
+
|
|
17
|
+
if (open) childrenRef.current = children;
|
|
18
|
+
if (open && !isMounted) setIsMounted(true);
|
|
13
19
|
|
|
14
20
|
useEffect(() => {
|
|
15
21
|
const layer = layerRef.current;
|
|
16
22
|
|
|
17
23
|
if (!layer) return;
|
|
18
24
|
if (open && !layer.open) layer.showModal();
|
|
19
|
-
if (
|
|
25
|
+
if (open || !layer.open) return;
|
|
26
|
+
|
|
27
|
+
layer.close();
|
|
28
|
+
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
|
|
31
|
+
const frame = requestAnimationFrame(() => {
|
|
32
|
+
const animations = layer.getAnimations({ subtree: true }).map((animation) => animation.finished);
|
|
33
|
+
|
|
34
|
+
void Promise.allSettled(animations).then(() => {
|
|
35
|
+
if (controller.signal.aborted) return;
|
|
36
|
+
|
|
37
|
+
setIsMounted(false);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return () => {
|
|
42
|
+
controller.abort();
|
|
43
|
+
cancelAnimationFrame(frame);
|
|
44
|
+
};
|
|
20
45
|
}, [open]);
|
|
21
46
|
|
|
22
47
|
const handleClick = (event: MouseEvent<HTMLDialogElement>) => {
|
|
@@ -38,7 +63,7 @@ export const Layer = ({ open = false, onClose, dismissible = true, className, ch
|
|
|
38
63
|
onClick={handleClick}
|
|
39
64
|
className={cn(layerBaseClassName, className)}
|
|
40
65
|
>
|
|
41
|
-
{
|
|
66
|
+
{isMounted ? childrenRef.current : null}
|
|
42
67
|
</dialog>
|
|
43
68
|
);
|
|
44
69
|
};
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from "./style.js";
|
|
18
18
|
|
|
19
19
|
import type { ElementType } from "react";
|
|
20
|
+
import { MaskGradientX } from "../../hoc/mask_gradient_x/mask_gradient_x.js";
|
|
20
21
|
import type { SnippetProps } from "./type.js";
|
|
21
22
|
|
|
22
23
|
export const Snippet = <T extends ElementType = "div">({ snippet, title, className, ...rest }: SnippetProps<T>) => {
|
|
@@ -46,18 +47,20 @@ export const Snippet = <T extends ElementType = "div">({ snippet, title, classNa
|
|
|
46
47
|
/>
|
|
47
48
|
</Button>
|
|
48
49
|
</div>
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{row.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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>
|
|
61
64
|
</Tag>
|
|
62
65
|
);
|
|
63
66
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { cloneElement, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import type { PointerEvent } from "react";
|
|
4
|
+
import type { PointerHolderProps } from "./type.js";
|
|
5
|
+
|
|
6
|
+
const readKey = (target: EventTarget | null, attribute: string) => {
|
|
7
|
+
if (!(target instanceof Element)) return null;
|
|
8
|
+
const holder = target.closest(`[${attribute}]`);
|
|
9
|
+
return holder?.getAttribute(attribute) ?? null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const PointerHolder = ({ children, attribute = "data-margo-pointer-holder", disabled = false }: PointerHolderProps) => {
|
|
13
|
+
const [keys, setKeys] = useState<string[]>([]);
|
|
14
|
+
|
|
15
|
+
const hold = (target: EventTarget | null) => {
|
|
16
|
+
const key = readKey(target, attribute);
|
|
17
|
+
|
|
18
|
+
if (key === null) return;
|
|
19
|
+
setKeys((prev) => (prev.includes(key) ? prev : [...prev, key]));
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const release = () => setKeys((prev) => (prev.length ? [] : prev));
|
|
23
|
+
|
|
24
|
+
const child = children({ keys });
|
|
25
|
+
|
|
26
|
+
const handlePointerOver = (event: PointerEvent<HTMLElement>) => {
|
|
27
|
+
if (!disabled && event.pointerType !== "touch") hold(event.target);
|
|
28
|
+
child.props.onPointerOver?.(event);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handlePointerLeave = (event: PointerEvent<HTMLElement>) => {
|
|
32
|
+
release();
|
|
33
|
+
child.props.onPointerLeave?.(event);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return cloneElement(child, {
|
|
37
|
+
onPointerLeave: handlePointerLeave,
|
|
38
|
+
onPointerOver: handlePointerOver,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FocusEventHandler, PointerEventHandler, ReactElement } from "react";
|
|
2
|
+
|
|
3
|
+
export type PointerHolderApi = {
|
|
4
|
+
keys: string[];
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type PointerHolderChildProps = {
|
|
8
|
+
onBlur?: FocusEventHandler<HTMLElement>;
|
|
9
|
+
onFocus?: FocusEventHandler<HTMLElement>;
|
|
10
|
+
onPointerLeave?: PointerEventHandler<HTMLElement>;
|
|
11
|
+
onPointerOver?: PointerEventHandler<HTMLElement>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type PointerHolderProps = {
|
|
15
|
+
children: (api: PointerHolderApi) => ReactElement<PointerHolderChildProps>;
|
|
16
|
+
attribute?: string;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
};
|
|
@@ -10,11 +10,11 @@ import type { RippleProps } from "./type.js";
|
|
|
10
10
|
|
|
11
11
|
import "./ripple.css";
|
|
12
12
|
|
|
13
|
-
export const Ripple = ({ children, rippleClassName = "bg-primary-darken" }: RippleProps) => {
|
|
13
|
+
export const Ripple = ({ children, disabled = false, rippleClassName = "bg-primary-darken" }: RippleProps) => {
|
|
14
14
|
const { endRipple, ripple, startRipple } = useRipple();
|
|
15
15
|
|
|
16
16
|
const handlePointerDown = (event: PointerEvent<HTMLElement>) => {
|
|
17
|
-
startRipple(event);
|
|
17
|
+
if (!disabled) startRipple(event);
|
|
18
18
|
children.props.onPointerDown?.(event);
|
|
19
19
|
};
|
|
20
20
|
|
package/src/hoc/ripple/type.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export { Tooltip } from "./components/tooltip/tooltip.js";
|
|
|
18
18
|
|
|
19
19
|
export { BackgroundGlow } from "./hoc/background_glow/background_glow.js";
|
|
20
20
|
export { BorderGlow } from "./hoc/border_glow/border_glow.js";
|
|
21
|
+
export { PointerHolder } from "./hoc/pointer_holder/pointer_holder.js";
|
|
21
22
|
export { MaskGradientX } from "./hoc/mask_gradient_x/mask_gradient_x.js";
|
|
22
23
|
export { MaskGradientY } from "./hoc/mask_gradient_y/mask_gradient_y.js";
|
|
23
24
|
export { Ripple } from "./hoc/ripple/ripple.js";
|
|
@@ -68,6 +69,7 @@ export type { TooltipPosition, TooltipProps } from "./components/tooltip/type.js
|
|
|
68
69
|
|
|
69
70
|
export type { BackgroundGlowProps } from "./hoc/background_glow/type.js";
|
|
70
71
|
export type { BorderGlowPosition, BorderGlowProps } from "./hoc/border_glow/type.js";
|
|
72
|
+
export type { PointerHolderProps } from "./hoc/pointer_holder/type.js";
|
|
71
73
|
export type { MaskGradientXProps } from "./hoc/mask_gradient_x/type.js";
|
|
72
74
|
export type { MaskGradientYProps } from "./hoc/mask_gradient_y/type.js";
|
|
73
75
|
export type { RippleProps } from "./hoc/ripple/type.js";
|