lines-overlay 0.1.1
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/components/config-button.tsx +54 -0
- package/components/config-options.tsx +76 -0
- package/components/data.ts +52 -0
- package/components/index.ts +3 -0
- package/components/move-lines-button.tsx +50 -0
- package/dist/components/config-button.d.ts +6 -0
- package/dist/components/config-button.js +17 -0
- package/dist/components/config-options.d.ts +2 -0
- package/dist/components/config-options.js +23 -0
- package/dist/components/data.d.ts +25 -0
- package/dist/components/data.js +28 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +3 -0
- package/dist/components/move-lines-button.d.ts +4 -0
- package/dist/components/move-lines-button.js +30 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lines-overlay.d.ts +1 -0
- package/dist/lines-overlay.js +45 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.js +1 -0
- package/dist/ui/button.d.ts +15 -0
- package/dist/ui/button.js +75 -0
- package/dist/ui/buttons-wrapper.d.ts +8 -0
- package/dist/ui/buttons-wrapper.js +5 -0
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/lucide-icon.d.ts +34 -0
- package/dist/ui/lucide-icon.js +29 -0
- package/dist/ui/separator.d.ts +4 -0
- package/dist/ui/separator.js +7 -0
- package/index.ts +1 -0
- package/lines-overlay-0.1.0.tgz +0 -0
- package/lines-overlay.tsx +104 -0
- package/package.json +28 -0
- package/tsconfig.json +17 -0
- package/types.ts +4 -0
- package/ui/button.tsx +129 -0
- package/ui/buttons-wrapper.tsx +24 -0
- package/ui/index.ts +3 -0
- package/ui/lucide-icon.tsx +50 -0
- package/ui/separator.tsx +25 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ChevronDown, ChevronUp, X } from 'lucide-react';
|
|
2
|
+
import { MouseEvent } from 'react';
|
|
3
|
+
import { StateSetter } from '../types';
|
|
4
|
+
import { Button, Icon, Separator } from '../ui/index';
|
|
5
|
+
|
|
6
|
+
export function ConfigButton({
|
|
7
|
+
onToggleConfig,
|
|
8
|
+
open,
|
|
9
|
+
setShow,
|
|
10
|
+
}: {
|
|
11
|
+
open: boolean;
|
|
12
|
+
onToggleConfig: () => void;
|
|
13
|
+
setShow: StateSetter<boolean>;
|
|
14
|
+
}) {
|
|
15
|
+
const handleClick = (e: MouseEvent<HTMLButtonElement>, item: number) => {
|
|
16
|
+
if (item === 1) {
|
|
17
|
+
onToggleConfig();
|
|
18
|
+
} else {
|
|
19
|
+
e.stopPropagation();
|
|
20
|
+
setShow((v) => !v);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
className="h-10 border pointer-events-auto fixed bottom-2 right-2 bg-white/66
|
|
27
|
+
shadow-sm flex items-center pl-[0.9em] pr-1">
|
|
28
|
+
<span className="font-medium text-sm-button tracking-wide pr-2">Configurar</span>
|
|
29
|
+
|
|
30
|
+
<div className="h-full flex items-center gap-1">
|
|
31
|
+
{[1, 2, 3].map((item) =>
|
|
32
|
+
item !== 2 ? (
|
|
33
|
+
<Button
|
|
34
|
+
variant={'transparent'}
|
|
35
|
+
size="icon-sm"
|
|
36
|
+
data-black
|
|
37
|
+
key={item}
|
|
38
|
+
onClick={(e) => {
|
|
39
|
+
handleClick(e, item);
|
|
40
|
+
}}>
|
|
41
|
+
{item === 1 ? (
|
|
42
|
+
<Icon Icon={open ? ChevronDown : ChevronUp} size={'xl'} strokeWidth="light" />
|
|
43
|
+
) : (
|
|
44
|
+
<Icon Icon={X} size="sm" className="text-red-600" strokeWidth="light" />
|
|
45
|
+
)}
|
|
46
|
+
</Button>
|
|
47
|
+
) : (
|
|
48
|
+
<Separator orientation="vertical" />
|
|
49
|
+
),
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Button } from '../ui';
|
|
2
|
+
import { ConfigOptionsProps, NUMBER_FIELDS, colorOptions } from './data';
|
|
3
|
+
|
|
4
|
+
const css = {
|
|
5
|
+
container: `text-sm pointer-events-auto fixed bottom-13 right-2
|
|
6
|
+
bg-white/94 backdrop-blur-sm shadow-md border border-border/50 p-3 pt-2 w-auto flex flex-col gap-2
|
|
7
|
+
[&>div]:flex [&>div]:gap-2`,
|
|
8
|
+
wrapper: `flex items-end gap-2`,
|
|
9
|
+
inputWrapper: `[&>input]:h-8 w-26`,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function ConfigOptions(props: ConfigOptionsProps) {
|
|
13
|
+
const fieldBindings = {
|
|
14
|
+
lines: { value: props.lines, set: props.setLines },
|
|
15
|
+
gap: { value: props.gap, set: props.setGap },
|
|
16
|
+
opacity: { value: props.opacity, set: props.setOpacity },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className={css.container}>
|
|
21
|
+
{NUMBER_FIELDS.map((field) => {
|
|
22
|
+
const binding = fieldBindings[field.key];
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="w-full border-b border-border/66 pb-3" key={field.key}>
|
|
26
|
+
<div className={css.wrapper}>
|
|
27
|
+
<div className={css.inputWrapper}>
|
|
28
|
+
<label className="block text-xs font-medium mb-1">{field.label}</label>
|
|
29
|
+
<input
|
|
30
|
+
className="w-full h-8 rounded border border-border px-2 text-sm"
|
|
31
|
+
type="number"
|
|
32
|
+
step={field.step}
|
|
33
|
+
value={binding.value}
|
|
34
|
+
onChange={(e) => binding.set(+e.target.value)}
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
<div className="flex gap-1.25 mt-1">
|
|
38
|
+
{field.quick.map((v) => {
|
|
39
|
+
return (
|
|
40
|
+
<Button
|
|
41
|
+
selected={binding.value === v}
|
|
42
|
+
key={v}
|
|
43
|
+
data-option
|
|
44
|
+
variant="ghost"
|
|
45
|
+
size="icon-sm"
|
|
46
|
+
className="text-sm-button font-medium"
|
|
47
|
+
onClick={() => binding.set(v)}>
|
|
48
|
+
{v}
|
|
49
|
+
</Button>
|
|
50
|
+
);
|
|
51
|
+
})}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
})}
|
|
57
|
+
|
|
58
|
+
<div className="mt-2">
|
|
59
|
+
<span className="block text-xs font-medium mb-1">Cor</span>
|
|
60
|
+
<div className="flex gap-2">
|
|
61
|
+
{colorOptions.map((c) => (
|
|
62
|
+
<Button
|
|
63
|
+
key={c.value}
|
|
64
|
+
variant="ghost"
|
|
65
|
+
size="icon-sm"
|
|
66
|
+
title={c.name}
|
|
67
|
+
className="rounded-full"
|
|
68
|
+
onClick={() => props.setColor(c.value)}>
|
|
69
|
+
<span className="block size-4/5 rounded-full" style={{ backgroundColor: c.value }} />
|
|
70
|
+
</Button>
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type FieldBinding = {
|
|
2
|
+
value: number;
|
|
3
|
+
set: (v: number) => void;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type ConfigOptionsProps = {
|
|
7
|
+
lines: number;
|
|
8
|
+
gap: number;
|
|
9
|
+
opacity: number;
|
|
10
|
+
color: string;
|
|
11
|
+
setLines: (v: number) => void;
|
|
12
|
+
setGap: (v: number) => void;
|
|
13
|
+
setOpacity: (v: number) => void;
|
|
14
|
+
setColor: (v: string) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type NumberFieldConfig = {
|
|
18
|
+
key: 'lines' | 'gap' | 'opacity';
|
|
19
|
+
label: string;
|
|
20
|
+
step?: number;
|
|
21
|
+
quick: number[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const NUMBER_FIELDS: NumberFieldConfig[] = [
|
|
25
|
+
{
|
|
26
|
+
key: 'lines',
|
|
27
|
+
label: 'Linhas',
|
|
28
|
+
quick: [8, 12, 16, 24],
|
|
29
|
+
step: 4,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
key: 'gap',
|
|
33
|
+
label: 'Gap',
|
|
34
|
+
quick: [16, 24, 32, 40, 44],
|
|
35
|
+
step: 4,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: 'opacity',
|
|
39
|
+
label: 'Opacidade',
|
|
40
|
+
quick: [0.2, 0.3, 0.5, 0.7],
|
|
41
|
+
step: 0.05,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export const colorOptions = [
|
|
46
|
+
{ name: 'Azul', value: '#2563eb' },
|
|
47
|
+
{ name: 'Amarelo', value: '#eab308' },
|
|
48
|
+
{ name: 'Verde', value: '#16a34a' },
|
|
49
|
+
{ name: 'Roxo', value: '#7c3aed' },
|
|
50
|
+
{ name: 'Violeta', value: '#9333ea' },
|
|
51
|
+
{ name: 'Violeta', value: '#d71212' },
|
|
52
|
+
];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Move } from 'lucide-react';
|
|
2
|
+
import { useRef, type MouseEvent as ReactMouseEvent, type RefObject } from 'react';
|
|
3
|
+
import { Button, Icon } from '../ui';
|
|
4
|
+
|
|
5
|
+
export function MoveLinesButton({ targetRef }: { targetRef: RefObject<HTMLDivElement | null> }) {
|
|
6
|
+
const dragging = useRef(false);
|
|
7
|
+
const last = useRef({ x: 0, y: 0 });
|
|
8
|
+
|
|
9
|
+
function onMouseDown(e: ReactMouseEvent<HTMLButtonElement>) {
|
|
10
|
+
dragging.current = true;
|
|
11
|
+
last.current = { x: e.clientX, y: e.clientY };
|
|
12
|
+
document.addEventListener('mousemove', onMove);
|
|
13
|
+
document.addEventListener('mouseup', onUp);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function onMove(e: MouseEvent) {
|
|
17
|
+
if (!dragging.current || !targetRef.current) return;
|
|
18
|
+
|
|
19
|
+
const dx = e.clientX - last.current.x;
|
|
20
|
+
const dy = e.clientY - last.current.y;
|
|
21
|
+
|
|
22
|
+
const el = targetRef.current;
|
|
23
|
+
|
|
24
|
+
el.style.left = `${el.offsetLeft + dx}px`;
|
|
25
|
+
el.style.top = `${el.offsetTop + dy}px`;
|
|
26
|
+
|
|
27
|
+
last.current = { x: e.clientX, y: e.clientY };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function onUp() {
|
|
31
|
+
dragging.current = false;
|
|
32
|
+
document.removeEventListener('mousemove', onMove);
|
|
33
|
+
document.removeEventListener('mouseup', onUp);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
className="pointer-events-auto absolute left-1/2 top-[44%]
|
|
39
|
+
-translate-x-1/2 -translate-y-1/2">
|
|
40
|
+
<Button
|
|
41
|
+
size="icon-sm"
|
|
42
|
+
data-black
|
|
43
|
+
variant="ghost"
|
|
44
|
+
onMouseDown={onMouseDown}
|
|
45
|
+
className="rounded-full bg-white/75 backdrop-blur-xs">
|
|
46
|
+
<Icon Icon={Move} size="lg" strokeWidth="thin" />
|
|
47
|
+
</Button>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronDown, ChevronUp, X } from 'lucide-react';
|
|
3
|
+
import { Button, Icon, Separator } from '../ui/index';
|
|
4
|
+
export function ConfigButton({ onToggleConfig, open, setShow, }) {
|
|
5
|
+
const handleClick = (e, item) => {
|
|
6
|
+
if (item === 1) {
|
|
7
|
+
onToggleConfig();
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
e.stopPropagation();
|
|
11
|
+
setShow((v) => !v);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
return (_jsxs("div", { className: "h-10 border pointer-events-auto fixed bottom-2 right-2 bg-white/66 \r\n shadow-sm flex items-center pl-[0.9em] pr-1", children: [_jsx("span", { className: "font-medium text-sm-button tracking-wide pr-2", children: "Configurar" }), _jsx("div", { className: "h-full flex items-center gap-1", children: [1, 2, 3].map((item) => item !== 2 ? (_jsx(Button, { variant: 'transparent', size: "icon-sm", "data-black": true, onClick: (e) => {
|
|
15
|
+
handleClick(e, item);
|
|
16
|
+
}, children: item === 1 ? (_jsx(Icon, { Icon: open ? ChevronDown : ChevronUp, size: 'xl', strokeWidth: "light" })) : (_jsx(Icon, { Icon: X, size: "sm", className: "text-red-600", strokeWidth: "light" })) }, item)) : (_jsx(Separator, { orientation: "vertical" }))) })] }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '../ui';
|
|
3
|
+
import { NUMBER_FIELDS, colorOptions } from './data';
|
|
4
|
+
const css = {
|
|
5
|
+
container: `text-sm pointer-events-auto fixed bottom-13 right-2
|
|
6
|
+
bg-white/94 backdrop-blur-sm shadow-md border border-border/50 p-3 pt-2 w-auto flex flex-col gap-2
|
|
7
|
+
[&>div]:flex [&>div]:gap-2`,
|
|
8
|
+
wrapper: `flex items-end gap-2`,
|
|
9
|
+
inputWrapper: `[&>input]:h-8 w-26`,
|
|
10
|
+
};
|
|
11
|
+
export function ConfigOptions(props) {
|
|
12
|
+
const fieldBindings = {
|
|
13
|
+
lines: { value: props.lines, set: props.setLines },
|
|
14
|
+
gap: { value: props.gap, set: props.setGap },
|
|
15
|
+
opacity: { value: props.opacity, set: props.setOpacity },
|
|
16
|
+
};
|
|
17
|
+
return (_jsxs("div", { className: css.container, children: [NUMBER_FIELDS.map((field) => {
|
|
18
|
+
const binding = fieldBindings[field.key];
|
|
19
|
+
return (_jsx("div", { className: "w-full border-b border-border/66 pb-3", children: _jsxs("div", { className: css.wrapper, children: [_jsxs("div", { className: css.inputWrapper, children: [_jsx("label", { className: "block text-xs font-medium mb-1", children: field.label }), _jsx("input", { className: "w-full h-8 rounded border border-border px-2 text-sm", type: "number", step: field.step, value: binding.value, onChange: (e) => binding.set(+e.target.value) })] }), _jsx("div", { className: "flex gap-1.25 mt-1", children: field.quick.map((v) => {
|
|
20
|
+
return (_jsx(Button, { selected: binding.value === v, "data-option": true, variant: "ghost", size: "icon-sm", className: "text-sm-button font-medium", onClick: () => binding.set(v), children: v }, v));
|
|
21
|
+
}) })] }) }, field.key));
|
|
22
|
+
}), _jsxs("div", { className: "mt-2", children: [_jsx("span", { className: "block text-xs font-medium mb-1", children: "Cor" }), _jsx("div", { className: "flex gap-2", children: colorOptions.map((c) => (_jsx(Button, { variant: "ghost", size: "icon-sm", title: c.name, className: "rounded-full", onClick: () => props.setColor(c.value), children: _jsx("span", { className: "block size-4/5 rounded-full", style: { backgroundColor: c.value } }) }, c.value))) })] })] }));
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type FieldBinding = {
|
|
2
|
+
value: number;
|
|
3
|
+
set: (v: number) => void;
|
|
4
|
+
};
|
|
5
|
+
export type ConfigOptionsProps = {
|
|
6
|
+
lines: number;
|
|
7
|
+
gap: number;
|
|
8
|
+
opacity: number;
|
|
9
|
+
color: string;
|
|
10
|
+
setLines: (v: number) => void;
|
|
11
|
+
setGap: (v: number) => void;
|
|
12
|
+
setOpacity: (v: number) => void;
|
|
13
|
+
setColor: (v: string) => void;
|
|
14
|
+
};
|
|
15
|
+
export type NumberFieldConfig = {
|
|
16
|
+
key: 'lines' | 'gap' | 'opacity';
|
|
17
|
+
label: string;
|
|
18
|
+
step?: number;
|
|
19
|
+
quick: number[];
|
|
20
|
+
};
|
|
21
|
+
export declare const NUMBER_FIELDS: NumberFieldConfig[];
|
|
22
|
+
export declare const colorOptions: {
|
|
23
|
+
name: string;
|
|
24
|
+
value: string;
|
|
25
|
+
}[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const NUMBER_FIELDS = [
|
|
2
|
+
{
|
|
3
|
+
key: 'lines',
|
|
4
|
+
label: 'Linhas',
|
|
5
|
+
quick: [8, 12, 16, 24],
|
|
6
|
+
step: 4,
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
key: 'gap',
|
|
10
|
+
label: 'Gap',
|
|
11
|
+
quick: [16, 24, 32, 40, 44],
|
|
12
|
+
step: 4,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
key: 'opacity',
|
|
16
|
+
label: 'Opacidade',
|
|
17
|
+
quick: [0.2, 0.3, 0.5, 0.7],
|
|
18
|
+
step: 0.05,
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
export const colorOptions = [
|
|
22
|
+
{ name: 'Azul', value: '#2563eb' },
|
|
23
|
+
{ name: 'Amarelo', value: '#eab308' },
|
|
24
|
+
{ name: 'Verde', value: '#16a34a' },
|
|
25
|
+
{ name: 'Roxo', value: '#7c3aed' },
|
|
26
|
+
{ name: 'Violeta', value: '#9333ea' },
|
|
27
|
+
{ name: 'Violeta', value: '#d71212' },
|
|
28
|
+
];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Move } from 'lucide-react';
|
|
3
|
+
import { useRef } from 'react';
|
|
4
|
+
import { Button, Icon } from '../ui';
|
|
5
|
+
export function MoveLinesButton({ targetRef }) {
|
|
6
|
+
const dragging = useRef(false);
|
|
7
|
+
const last = useRef({ x: 0, y: 0 });
|
|
8
|
+
function onMouseDown(e) {
|
|
9
|
+
dragging.current = true;
|
|
10
|
+
last.current = { x: e.clientX, y: e.clientY };
|
|
11
|
+
document.addEventListener('mousemove', onMove);
|
|
12
|
+
document.addEventListener('mouseup', onUp);
|
|
13
|
+
}
|
|
14
|
+
function onMove(e) {
|
|
15
|
+
if (!dragging.current || !targetRef.current)
|
|
16
|
+
return;
|
|
17
|
+
const dx = e.clientX - last.current.x;
|
|
18
|
+
const dy = e.clientY - last.current.y;
|
|
19
|
+
const el = targetRef.current;
|
|
20
|
+
el.style.left = `${el.offsetLeft + dx}px`;
|
|
21
|
+
el.style.top = `${el.offsetTop + dy}px`;
|
|
22
|
+
last.current = { x: e.clientX, y: e.clientY };
|
|
23
|
+
}
|
|
24
|
+
function onUp() {
|
|
25
|
+
dragging.current = false;
|
|
26
|
+
document.removeEventListener('mousemove', onMove);
|
|
27
|
+
document.removeEventListener('mouseup', onUp);
|
|
28
|
+
}
|
|
29
|
+
return (_jsx("div", { className: "pointer-events-auto absolute left-1/2 top-[44%]\r\n -translate-x-1/2 -translate-y-1/2", children: _jsx(Button, { size: "icon-sm", "data-black": true, variant: "ghost", onMouseDown: onMouseDown, className: "rounded-full bg-white/75 backdrop-blur-xs", children: _jsx(Icon, { Icon: Move, size: "lg", strokeWidth: "thin" }) }) }));
|
|
30
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lines-overlay';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lines-overlay';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function RowGrid(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Eye } from 'lucide-react';
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { ConfigButton, ConfigOptions, MoveLinesButton } from './components/index';
|
|
5
|
+
import { Button, Icon } from './ui';
|
|
6
|
+
function RowGridCore({ show, setShow }) {
|
|
7
|
+
const containerRef = useRef(null);
|
|
8
|
+
const [lines, setLines] = useState(12);
|
|
9
|
+
const [gap, setGap] = useState(24);
|
|
10
|
+
const [opacity, setOpacity] = useState(0.3);
|
|
11
|
+
const [color, setColor] = useState('#d71212');
|
|
12
|
+
const [showConfig, setShowConfig] = useState(false);
|
|
13
|
+
// Toggle por tecla
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const handler = (e) => {
|
|
16
|
+
if (e.ctrlKey && e.key === ';') {
|
|
17
|
+
setShow((v) => !v);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
window.addEventListener('keydown', handler);
|
|
21
|
+
return () => window.removeEventListener('keydown', handler);
|
|
22
|
+
}, []);
|
|
23
|
+
if (!show)
|
|
24
|
+
return null;
|
|
25
|
+
const height = lines * gap;
|
|
26
|
+
return (_jsxs("div", { children: [_jsxs("div", { ref: containerRef, className: "fixed w-full pointer-events-none flex justify-center", style: {
|
|
27
|
+
top: 100,
|
|
28
|
+
left: 0,
|
|
29
|
+
height,
|
|
30
|
+
}, children: [_jsx("div", { className: "w-full", style: {
|
|
31
|
+
height,
|
|
32
|
+
backgroundImage: `repeating-linear-gradient(
|
|
33
|
+
to bottom,
|
|
34
|
+
${color},
|
|
35
|
+
${color} 1px,
|
|
36
|
+
transparent 1px,
|
|
37
|
+
transparent ${gap}px
|
|
38
|
+
)`,
|
|
39
|
+
opacity,
|
|
40
|
+
} }), _jsx(MoveLinesButton, { targetRef: containerRef })] }), _jsx(ConfigButton, { setShow: setShow, onToggleConfig: () => setShowConfig((v) => !v), open: showConfig }), showConfig && (_jsx(ConfigOptions, { lines: lines, gap: gap, opacity: opacity, color: color, setLines: setLines, setGap: setGap, setOpacity: setOpacity, setColor: setColor }))] }));
|
|
41
|
+
}
|
|
42
|
+
export function RowGrid() {
|
|
43
|
+
const [show, setShow] = useState(false);
|
|
44
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Button, { size: "sm", variant: "ghost", style: { visibility: show ? 'hidden' : 'visible' }, onClick: () => setShow((v) => !v), className: "fixed bottom-2 right-2 z-50 text-xs bg-white/66 ", children: ["Mostrar linhas", _jsx(Icon, { Icon: Eye })] }), _jsx(RowGridCore, { setShow: setShow, show: show })] }));
|
|
45
|
+
}
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
3
|
+
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
|
4
|
+
declare const buttonVariants: (props?: ({
|
|
5
|
+
variant?: "default" | "outline" | "ghost" | "transparent" | "link" | "secondary" | "destructive" | null | undefined;
|
|
6
|
+
size?: "default" | "sm" | "lg" | "icon-sm" | "icon" | "icon-md" | "icon-lg" | null | undefined;
|
|
7
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
|
+
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
9
|
+
asChild?: boolean;
|
|
10
|
+
selected?: boolean;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
closeButton?: boolean;
|
|
13
|
+
}
|
|
14
|
+
declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
|
|
15
|
+
export { Button };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
4
|
+
import { cva } from 'class-variance-authority';
|
|
5
|
+
const cn = (...classes) => classes.filter(Boolean).join(' ');
|
|
6
|
+
const buttonVariants = cva('w-auto tracking-wide inline-flex items-center justify-center gap-2 rounded-xs ring-offset-background transition-colors disabled:pointer-events-none disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg]:shrink-0 relative data-w-full:w-full data-round:rounded-full focus:outline-none data-option:rounded-full data-black:text-foreground', {
|
|
7
|
+
variants: {
|
|
8
|
+
variant: {
|
|
9
|
+
default: 'bg-primary hover:bg-primary/90 text-primary-foreground disabled:bg-neutral-300 disabled:text-neutral-500/80 data-active:bg-primary-800 data-hover:bg-primary-600 data-focus:border-3 data-focus:border-selected/75',
|
|
10
|
+
outline: 'border-2 border-primary/88 text-primary bg-transparent hover:bg-gray-50 shadow-xs/12 disabled:bg-neutral-100 disabled:border-neutral-300 disabled:text-neutral-500/75 data-hover:bg-primary-50 data-focus:outline-selected/70 data-focus:outline-2 data-active:bg-primary-100',
|
|
11
|
+
ghost: 'hover:bg-primary-50 border text-primary bg-transparent disabled:bg-neutral-100 disabled:text-neutral-400 disabled:border-none data-hover:bg-primary-50 data-focus:outline-selected/70 data-focus:outline-3 data-focus:border-primary-400 data-active:bg-primary-100',
|
|
12
|
+
transparent: 'bg-transparent text-primary hover:bg-primary-50',
|
|
13
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
14
|
+
secondary: 'bg-primary-50 text-primary hover:bg-primary-100 disabled:bg-neutral-100 disabled:text-neutral-400 disabled:border-none data-hover:bg-primary-50/75 data-focus:outline-3 data-focus:outline-selected/75 data-active:bg-primary-100',
|
|
15
|
+
destructive: 'bg-red-700 text-red-50 hover:bg-red-600 data-hover:bg-red-600 data-focus:outline-3 data-focus:outline-red-200 data-active:bg-red-800',
|
|
16
|
+
},
|
|
17
|
+
size: {
|
|
18
|
+
sm: 'min-h-9 small-button',
|
|
19
|
+
default: `min-h-10`,
|
|
20
|
+
lg: 'min-h-11 large-text large-button',
|
|
21
|
+
'icon-sm': 'size-8',
|
|
22
|
+
icon: 'size-8.5',
|
|
23
|
+
'icon-md': 'size-9.5',
|
|
24
|
+
'icon-lg': 'size-10',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
variant: 'default',
|
|
29
|
+
size: 'default',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
const paddings = {
|
|
33
|
+
default: {
|
|
34
|
+
sm: 'px-[0.93em] py-[0.63885rem]',
|
|
35
|
+
default: 'px-[0.93em] py-[0.73438rem]',
|
|
36
|
+
lg: 'px-[0.93em] py-[0.82813rem]',
|
|
37
|
+
},
|
|
38
|
+
outline: {
|
|
39
|
+
sm: 'px-[0.82716em] py-[0.54794rem]',
|
|
40
|
+
default: 'px-[0.83304em] py-[0.64347rem]',
|
|
41
|
+
lg: 'px-[0.83857em] py-[0.73722rem]',
|
|
42
|
+
},
|
|
43
|
+
ghost: {
|
|
44
|
+
sm: 'px-[0.82841em] py-[0.6076rem]',
|
|
45
|
+
default: 'px-[0.83429em] py-[0.70313rem]',
|
|
46
|
+
lg: 'px-[0.83982em] py-[0.79688rem]',
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const paddingExptions = {
|
|
50
|
+
variants: ['link', 'transparent'],
|
|
51
|
+
sizes: ['icon', 'icon-sm', 'icon-md', 'icon-lg'],
|
|
52
|
+
};
|
|
53
|
+
const getPaddings = (variant, size) => {
|
|
54
|
+
let padding = '';
|
|
55
|
+
if (!paddingExptions.sizes.includes(size) && !paddingExptions.variants.includes(variant)) {
|
|
56
|
+
if (variant === 'destructive' || variant === 'secondary') {
|
|
57
|
+
padding = paddings.default[size];
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
padding = paddings[variant][size];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return padding;
|
|
64
|
+
};
|
|
65
|
+
const Button = React.forwardRef(({ className, variant = 'default', size = "default", asChild = false, selected, disabled, closeButton = false, ...props }, ref) => {
|
|
66
|
+
const Comp = asChild ? Slot : 'button';
|
|
67
|
+
const padding = getPaddings(variant, size);
|
|
68
|
+
const selectedCSS = selected
|
|
69
|
+
? 'border-2 border-selected text-primary bg-primary-50/25 hover:bg-card'
|
|
70
|
+
: '';
|
|
71
|
+
const closeButtonCSS = closeButton ? 'rounded-full text-foreground' : '';
|
|
72
|
+
return (_jsx(Comp, { className: cn(buttonVariants({ variant, size }), selectedCSS, closeButtonCSS, padding, className), ref: ref, disabled: disabled, ...props }));
|
|
73
|
+
});
|
|
74
|
+
Button.displayName = 'Button';
|
|
75
|
+
export { Button };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
const cn = (...classes) => classes.filter(Boolean).join(' ');
|
|
3
|
+
export const ButtonsWrapper = ({ children, className = '', gap = 3, }) => {
|
|
4
|
+
return (_jsx("div", { style: { '--custom-gap': `${gap * 0.25}rem` }, className: cn(`h-auto flex flex-wrap items-center gap-(--custom-gap)`, className), children: children }));
|
|
5
|
+
};
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { LucideIcon as LucideIconType, LucideProps } from 'lucide-react';
|
|
2
|
+
type StrokeWidthValue = keyof typeof weights;
|
|
3
|
+
declare const weights: {
|
|
4
|
+
thin: number;
|
|
5
|
+
light: number;
|
|
6
|
+
normal: number;
|
|
7
|
+
semibold: number;
|
|
8
|
+
bold: number;
|
|
9
|
+
extrabold: number;
|
|
10
|
+
};
|
|
11
|
+
type SizeValue = keyof typeof iconSizes;
|
|
12
|
+
declare const iconSizes: {
|
|
13
|
+
xs: string;
|
|
14
|
+
sm: string;
|
|
15
|
+
base: string;
|
|
16
|
+
md: string;
|
|
17
|
+
lg: string;
|
|
18
|
+
xl: string;
|
|
19
|
+
'2xl': string;
|
|
20
|
+
'3xl': string;
|
|
21
|
+
h6: string;
|
|
22
|
+
h5: string;
|
|
23
|
+
h4: string;
|
|
24
|
+
h3: string;
|
|
25
|
+
h2: string;
|
|
26
|
+
h1: string;
|
|
27
|
+
};
|
|
28
|
+
interface IconProps extends Omit<LucideProps, 'size' | 'strokeWidth'> {
|
|
29
|
+
Icon: LucideIconType;
|
|
30
|
+
size?: SizeValue | string;
|
|
31
|
+
strokeWidth?: StrokeWidthValue | string;
|
|
32
|
+
}
|
|
33
|
+
export declare const Icon: ({ Icon, size, className, strokeWidth, fill }: IconProps) => import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/* Ajuste depois */
|
|
3
|
+
const weights = {
|
|
4
|
+
thin: 2.25,
|
|
5
|
+
light: 2.35,
|
|
6
|
+
normal: 2.65, // valor padrão
|
|
7
|
+
semibold: 2.75,
|
|
8
|
+
bold: 2.85,
|
|
9
|
+
extrabold: 3,
|
|
10
|
+
};
|
|
11
|
+
const iconSizes = {
|
|
12
|
+
xs: '0.937em',
|
|
13
|
+
sm: '0.968em',
|
|
14
|
+
base: '1em',
|
|
15
|
+
md: '1.033em',
|
|
16
|
+
lg: '1.067em',
|
|
17
|
+
xl: '1.138em',
|
|
18
|
+
'2xl': '1.215em',
|
|
19
|
+
'3xl': '1.296em',
|
|
20
|
+
h6: '1.067em',
|
|
21
|
+
h5: '1.138em',
|
|
22
|
+
h4: '1.215em',
|
|
23
|
+
h3: '1.296em',
|
|
24
|
+
h2: '1.383em',
|
|
25
|
+
h1: '1.4757em',
|
|
26
|
+
};
|
|
27
|
+
export const Icon = ({ Icon, size, className, strokeWidth, fill }) => {
|
|
28
|
+
return (_jsx("div", { "data-icon": true, className: "h-3 inline-flex justify-center items-center overflow-visible [&_svg]:shrink-0", children: _jsx(Icon, { size: iconSizes[size] || size || '1.067em', strokeWidth: weights[strokeWidth] || strokeWidth || 2.6, className: className, fill: fill || 'none' }) }));
|
|
29
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
3
|
+
declare const Separator: React.ForwardRefExoticComponent<Omit<SeparatorPrimitive.SeparatorProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
4
|
+
export { Separator };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
4
|
+
const cn = (...classes) => classes.filter(Boolean).join(' ');
|
|
5
|
+
const Separator = React.forwardRef(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (_jsx(SeparatorPrimitive.Root, { ref: ref, decorative: decorative, orientation: orientation, className: cn('shrink-0 bg-border', orientation === 'horizontal' ? 'h-px w-full scale-y-99' : 'h-full w-px scale-x-99', className), ...props })));
|
|
6
|
+
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
|
7
|
+
export { Separator };
|
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lines-overlay';
|
|
Binary file
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Eye } from 'lucide-react';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { ConfigButton, ConfigOptions, MoveLinesButton } from './components/index';
|
|
4
|
+
import { StateSetter } from './types';
|
|
5
|
+
import { Button, Icon } from './ui';
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
setShow: StateSetter<boolean>;
|
|
9
|
+
show: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function RowGridCore({ show, setShow }: Props) {
|
|
13
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
14
|
+
|
|
15
|
+
const [lines, setLines] = useState(12);
|
|
16
|
+
const [gap, setGap] = useState(24);
|
|
17
|
+
const [opacity, setOpacity] = useState(0.3);
|
|
18
|
+
const [color, setColor] = useState('#d71212');
|
|
19
|
+
const [showConfig, setShowConfig] = useState(false);
|
|
20
|
+
|
|
21
|
+
// Toggle por tecla
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const handler = (e: KeyboardEvent) => {
|
|
24
|
+
if (e.ctrlKey && e.key === ';') {
|
|
25
|
+
setShow((v) => !v);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
window.addEventListener('keydown', handler);
|
|
29
|
+
return () => window.removeEventListener('keydown', handler);
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
if (!show) return null;
|
|
33
|
+
|
|
34
|
+
const height = lines * gap;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div>
|
|
38
|
+
<div
|
|
39
|
+
ref={containerRef}
|
|
40
|
+
className="fixed w-full pointer-events-none flex justify-center"
|
|
41
|
+
style={{
|
|
42
|
+
top: 100,
|
|
43
|
+
left: 0,
|
|
44
|
+
height,
|
|
45
|
+
}}>
|
|
46
|
+
{/* linhas */}
|
|
47
|
+
<div
|
|
48
|
+
className="w-full"
|
|
49
|
+
style={{
|
|
50
|
+
height,
|
|
51
|
+
backgroundImage: `repeating-linear-gradient(
|
|
52
|
+
to bottom,
|
|
53
|
+
${color},
|
|
54
|
+
${color} 1px,
|
|
55
|
+
transparent 1px,
|
|
56
|
+
transparent ${gap}px
|
|
57
|
+
)`,
|
|
58
|
+
opacity,
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
{/* Move */}
|
|
62
|
+
<MoveLinesButton targetRef={containerRef} />
|
|
63
|
+
</div>
|
|
64
|
+
{/* Config */}
|
|
65
|
+
<ConfigButton
|
|
66
|
+
setShow={setShow}
|
|
67
|
+
onToggleConfig={() => setShowConfig((v) => !v)}
|
|
68
|
+
open={showConfig}
|
|
69
|
+
/>
|
|
70
|
+
{showConfig && (
|
|
71
|
+
<ConfigOptions
|
|
72
|
+
lines={lines}
|
|
73
|
+
gap={gap}
|
|
74
|
+
opacity={opacity}
|
|
75
|
+
color={color}
|
|
76
|
+
setLines={setLines}
|
|
77
|
+
setGap={setGap}
|
|
78
|
+
setOpacity={setOpacity}
|
|
79
|
+
setColor={setColor}
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function RowGrid() {
|
|
87
|
+
const [show, setShow] = useState(false);
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<>
|
|
91
|
+
<Button
|
|
92
|
+
size="sm"
|
|
93
|
+
variant="ghost"
|
|
94
|
+
style={{ visibility: show ? 'hidden' : 'visible' }}
|
|
95
|
+
onClick={() => setShow((v) => !v)}
|
|
96
|
+
className="fixed bottom-2 right-2 z-50 text-xs bg-white/66 ">
|
|
97
|
+
Mostrar linhas
|
|
98
|
+
<Icon Icon={Eye} />
|
|
99
|
+
</Button>
|
|
100
|
+
|
|
101
|
+
<RowGridCore setShow={setShow} show={show} />
|
|
102
|
+
</>
|
|
103
|
+
);
|
|
104
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lines-overlay",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Overlay de linhas configuráveis para interfaces React.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc --project tsconfig.json",
|
|
10
|
+
"prepare": "npm run build",
|
|
11
|
+
"test": "echo \"Sem testes definidos\""
|
|
12
|
+
},
|
|
13
|
+
"peerDependencies": {
|
|
14
|
+
"react": ">=18",
|
|
15
|
+
"lucide-react": ">=0.462.0"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
19
|
+
"@radix-ui/react-separator": "^1.1.8",
|
|
20
|
+
"class-variance-authority": "^0.7.1"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/react": "^19.2.14",
|
|
24
|
+
"lucide-react": "^0.574.0",
|
|
25
|
+
"react": "^19.2.4",
|
|
26
|
+
"typescript": "^5.6.3"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2019",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["./**/*"],
|
|
15
|
+
"exclude": ["dist", "node_modules"]
|
|
16
|
+
}
|
|
17
|
+
|
package/types.ts
ADDED
package/ui/button.tsx
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
+
|
|
5
|
+
const cn = (...classes: Array<string | false | null | undefined>) =>
|
|
6
|
+
classes.filter(Boolean).join(' ');
|
|
7
|
+
|
|
8
|
+
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
|
9
|
+
|
|
10
|
+
const buttonVariants = cva(
|
|
11
|
+
'w-auto tracking-wide inline-flex items-center justify-center gap-2 rounded-xs ring-offset-background transition-colors disabled:pointer-events-none disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg]:shrink-0 relative data-w-full:w-full data-round:rounded-full focus:outline-none data-option:rounded-full data-black:text-foreground',
|
|
12
|
+
{
|
|
13
|
+
variants: {
|
|
14
|
+
variant: {
|
|
15
|
+
default:
|
|
16
|
+
'bg-primary hover:bg-primary/90 text-primary-foreground disabled:bg-neutral-300 disabled:text-neutral-500/80 data-active:bg-primary-800 data-hover:bg-primary-600 data-focus:border-3 data-focus:border-selected/75',
|
|
17
|
+
outline:
|
|
18
|
+
'border-2 border-primary/88 text-primary bg-transparent hover:bg-gray-50 shadow-xs/12 disabled:bg-neutral-100 disabled:border-neutral-300 disabled:text-neutral-500/75 data-hover:bg-primary-50 data-focus:outline-selected/70 data-focus:outline-2 data-active:bg-primary-100',
|
|
19
|
+
ghost:
|
|
20
|
+
'hover:bg-primary-50 border text-primary bg-transparent disabled:bg-neutral-100 disabled:text-neutral-400 disabled:border-none data-hover:bg-primary-50 data-focus:outline-selected/70 data-focus:outline-3 data-focus:border-primary-400 data-active:bg-primary-100',
|
|
21
|
+
transparent: 'bg-transparent text-primary hover:bg-primary-50',
|
|
22
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
23
|
+
secondary:
|
|
24
|
+
'bg-primary-50 text-primary hover:bg-primary-100 disabled:bg-neutral-100 disabled:text-neutral-400 disabled:border-none data-hover:bg-primary-50/75 data-focus:outline-3 data-focus:outline-selected/75 data-active:bg-primary-100',
|
|
25
|
+
destructive:
|
|
26
|
+
'bg-red-700 text-red-50 hover:bg-red-600 data-hover:bg-red-600 data-focus:outline-3 data-focus:outline-red-200 data-active:bg-red-800',
|
|
27
|
+
},
|
|
28
|
+
size: {
|
|
29
|
+
sm: 'min-h-9 small-button',
|
|
30
|
+
default: `min-h-10`,
|
|
31
|
+
lg: 'min-h-11 large-text large-button',
|
|
32
|
+
'icon-sm': 'size-8',
|
|
33
|
+
icon: 'size-8.5',
|
|
34
|
+
'icon-md': 'size-9.5',
|
|
35
|
+
'icon-lg': 'size-10',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
variant: 'default',
|
|
40
|
+
size: 'default',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const paddings = {
|
|
46
|
+
default: {
|
|
47
|
+
sm: 'px-[0.93em] py-[0.63885rem]',
|
|
48
|
+
default: 'px-[0.93em] py-[0.73438rem]',
|
|
49
|
+
lg: 'px-[0.93em] py-[0.82813rem]',
|
|
50
|
+
},
|
|
51
|
+
outline: {
|
|
52
|
+
sm: 'px-[0.82716em] py-[0.54794rem]',
|
|
53
|
+
default: 'px-[0.83304em] py-[0.64347rem]',
|
|
54
|
+
lg: 'px-[0.83857em] py-[0.73722rem]',
|
|
55
|
+
},
|
|
56
|
+
ghost: {
|
|
57
|
+
sm: 'px-[0.82841em] py-[0.6076rem]',
|
|
58
|
+
default: 'px-[0.83429em] py-[0.70313rem]',
|
|
59
|
+
lg: 'px-[0.83982em] py-[0.79688rem]',
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
type OmitVariant = keyof typeof paddings | 'destructive' | 'secondary';
|
|
64
|
+
type OmitSize = keyof typeof paddings.default;
|
|
65
|
+
const paddingExptions = {
|
|
66
|
+
variants: ['link', 'transparent'],
|
|
67
|
+
sizes: ['icon', 'icon-sm', 'icon-md', 'icon-lg'],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const getPaddings = (variant: OmitVariant, size: OmitSize): string => {
|
|
71
|
+
let padding = '';
|
|
72
|
+
if (!paddingExptions.sizes.includes(size) && !paddingExptions.variants.includes(variant)) {
|
|
73
|
+
if (variant === 'destructive' || variant === 'secondary') {
|
|
74
|
+
padding = paddings.default[size];
|
|
75
|
+
} else {
|
|
76
|
+
padding = paddings[variant][size];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return padding;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export interface ButtonProps
|
|
83
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
84
|
+
asChild?: boolean;
|
|
85
|
+
selected?: boolean;
|
|
86
|
+
disabled?: boolean;
|
|
87
|
+
closeButton?: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
91
|
+
(
|
|
92
|
+
{
|
|
93
|
+
className,
|
|
94
|
+
variant = 'default',
|
|
95
|
+
size = "default",
|
|
96
|
+
asChild = false,
|
|
97
|
+
selected,
|
|
98
|
+
disabled,
|
|
99
|
+
closeButton = false,
|
|
100
|
+
...props
|
|
101
|
+
},
|
|
102
|
+
ref,
|
|
103
|
+
) => {
|
|
104
|
+
const Comp = asChild ? Slot : 'button';
|
|
105
|
+
const padding = getPaddings(variant as OmitVariant, size as OmitSize);
|
|
106
|
+
const selectedCSS = selected
|
|
107
|
+
? 'border-2 border-selected text-primary bg-primary-50/25 hover:bg-card'
|
|
108
|
+
: '';
|
|
109
|
+
const closeButtonCSS = closeButton ? 'rounded-full text-foreground' : '';
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Comp
|
|
113
|
+
className={cn(
|
|
114
|
+
buttonVariants({ variant, size }),
|
|
115
|
+
selectedCSS,
|
|
116
|
+
closeButtonCSS,
|
|
117
|
+
padding,
|
|
118
|
+
className,
|
|
119
|
+
)}
|
|
120
|
+
ref={ref}
|
|
121
|
+
disabled={disabled}
|
|
122
|
+
{...props}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
Button.displayName = 'Button';
|
|
128
|
+
|
|
129
|
+
export { Button };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const cn = (...classes: Array<string | false | null | undefined>) =>
|
|
4
|
+
classes.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
interface ButtonsWrapperProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
gap?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ButtonsWrapper: React.FC<ButtonsWrapperProps> = ({
|
|
13
|
+
children,
|
|
14
|
+
className = '',
|
|
15
|
+
gap = 3,
|
|
16
|
+
}) => {
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
style={{ '--custom-gap': `${gap * 0.25}rem` } as React.CSSProperties}
|
|
20
|
+
className={cn(`h-auto flex flex-wrap items-center gap-(--custom-gap)`, className)}>
|
|
21
|
+
{children}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
};
|
package/ui/index.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { LucideIcon as LucideIconType, LucideProps } from 'lucide-react';
|
|
2
|
+
|
|
3
|
+
type StrokeWidthValue = keyof typeof weights;
|
|
4
|
+
/* Ajuste depois */
|
|
5
|
+
const weights = {
|
|
6
|
+
thin: 2.25,
|
|
7
|
+
light: 2.35,
|
|
8
|
+
normal: 2.65, // valor padrão
|
|
9
|
+
semibold: 2.75,
|
|
10
|
+
bold: 2.85,
|
|
11
|
+
extrabold: 3,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type SizeValue = keyof typeof iconSizes;
|
|
15
|
+
|
|
16
|
+
const iconSizes = {
|
|
17
|
+
xs: '0.937em',
|
|
18
|
+
sm: '0.968em',
|
|
19
|
+
base: '1em',
|
|
20
|
+
md: '1.033em',
|
|
21
|
+
lg: '1.067em',
|
|
22
|
+
xl: '1.138em',
|
|
23
|
+
'2xl': '1.215em',
|
|
24
|
+
'3xl': '1.296em',
|
|
25
|
+
h6: '1.067em',
|
|
26
|
+
h5: '1.138em',
|
|
27
|
+
h4: '1.215em',
|
|
28
|
+
h3: '1.296em',
|
|
29
|
+
h2: '1.383em',
|
|
30
|
+
h1: '1.4757em',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
interface IconProps extends Omit<LucideProps, 'size' | 'strokeWidth'> {
|
|
34
|
+
Icon: LucideIconType;
|
|
35
|
+
size?: SizeValue | string;
|
|
36
|
+
strokeWidth?: StrokeWidthValue | string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const Icon = ({ Icon, size, className, strokeWidth, fill }: IconProps) => {
|
|
40
|
+
return (
|
|
41
|
+
<div data-icon className="h-3 inline-flex justify-center items-center overflow-visible [&_svg]:shrink-0">
|
|
42
|
+
<Icon
|
|
43
|
+
size={iconSizes[size as SizeValue] || size || '1.067em'}
|
|
44
|
+
strokeWidth={weights[strokeWidth as StrokeWidthValue] || strokeWidth || 2.6}
|
|
45
|
+
className={className}
|
|
46
|
+
fill={fill || 'none'}
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
};
|
package/ui/separator.tsx
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
3
|
+
|
|
4
|
+
const cn = (...classes: Array<string | false | null | undefined>) =>
|
|
5
|
+
classes.filter(Boolean).join(' ');
|
|
6
|
+
|
|
7
|
+
const Separator = React.forwardRef<
|
|
8
|
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
9
|
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
10
|
+
>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
|
|
11
|
+
<SeparatorPrimitive.Root
|
|
12
|
+
ref={ref}
|
|
13
|
+
decorative={decorative}
|
|
14
|
+
orientation={orientation}
|
|
15
|
+
className={cn(
|
|
16
|
+
'shrink-0 bg-border',
|
|
17
|
+
orientation === 'horizontal' ? 'h-px w-full scale-y-99' : 'h-full w-px scale-x-99',
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
));
|
|
23
|
+
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
|
24
|
+
|
|
25
|
+
export { Separator };
|