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.
Files changed (42) hide show
  1. package/components/config-button.tsx +54 -0
  2. package/components/config-options.tsx +76 -0
  3. package/components/data.ts +52 -0
  4. package/components/index.ts +3 -0
  5. package/components/move-lines-button.tsx +50 -0
  6. package/dist/components/config-button.d.ts +6 -0
  7. package/dist/components/config-button.js +17 -0
  8. package/dist/components/config-options.d.ts +2 -0
  9. package/dist/components/config-options.js +23 -0
  10. package/dist/components/data.d.ts +25 -0
  11. package/dist/components/data.js +28 -0
  12. package/dist/components/index.d.ts +3 -0
  13. package/dist/components/index.js +3 -0
  14. package/dist/components/move-lines-button.d.ts +4 -0
  15. package/dist/components/move-lines-button.js +30 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/lines-overlay.d.ts +1 -0
  19. package/dist/lines-overlay.js +45 -0
  20. package/dist/types.d.ts +3 -0
  21. package/dist/types.js +1 -0
  22. package/dist/ui/button.d.ts +15 -0
  23. package/dist/ui/button.js +75 -0
  24. package/dist/ui/buttons-wrapper.d.ts +8 -0
  25. package/dist/ui/buttons-wrapper.js +5 -0
  26. package/dist/ui/index.d.ts +3 -0
  27. package/dist/ui/index.js +3 -0
  28. package/dist/ui/lucide-icon.d.ts +34 -0
  29. package/dist/ui/lucide-icon.js +29 -0
  30. package/dist/ui/separator.d.ts +4 -0
  31. package/dist/ui/separator.js +7 -0
  32. package/index.ts +1 -0
  33. package/lines-overlay-0.1.0.tgz +0 -0
  34. package/lines-overlay.tsx +104 -0
  35. package/package.json +28 -0
  36. package/tsconfig.json +17 -0
  37. package/types.ts +4 -0
  38. package/ui/button.tsx +129 -0
  39. package/ui/buttons-wrapper.tsx +24 -0
  40. package/ui/index.ts +3 -0
  41. package/ui/lucide-icon.tsx +50 -0
  42. 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,3 @@
1
+ export * from "./move-lines-button";
2
+ export * from "./config-button";
3
+ export * from "./config-options"
@@ -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,6 @@
1
+ import { StateSetter } from '../types';
2
+ export declare function ConfigButton({ onToggleConfig, open, setShow, }: {
3
+ open: boolean;
4
+ onToggleConfig: () => void;
5
+ setShow: StateSetter<boolean>;
6
+ }): import("react/jsx-runtime").JSX.Element;
@@ -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,2 @@
1
+ import { ConfigOptionsProps } from './data';
2
+ export declare function ConfigOptions(props: ConfigOptionsProps): import("react/jsx-runtime").JSX.Element;
@@ -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,3 @@
1
+ export * from "./move-lines-button";
2
+ export * from "./config-button";
3
+ export * from "./config-options";
@@ -0,0 +1,3 @@
1
+ export * from "./move-lines-button";
2
+ export * from "./config-button";
3
+ export * from "./config-options";
@@ -0,0 +1,4 @@
1
+ import { type RefObject } from 'react';
2
+ export declare function MoveLinesButton({ targetRef }: {
3
+ targetRef: RefObject<HTMLDivElement | null>;
4
+ }): import("react/jsx-runtime").JSX.Element;
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import { Dispatch, SetStateAction } from 'react';
2
+ export type BooleanSetter = Dispatch<SetStateAction<boolean>>;
3
+ export type StateSetter<T> = Dispatch<SetStateAction<T>>;
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,8 @@
1
+ import React, { ReactNode } from 'react';
2
+ interface ButtonsWrapperProps {
3
+ children: ReactNode;
4
+ className?: string;
5
+ gap?: number;
6
+ }
7
+ export declare const ButtonsWrapper: React.FC<ButtonsWrapperProps>;
8
+ export {};
@@ -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
+ };
@@ -0,0 +1,3 @@
1
+ export * from './button';
2
+ export * from './lucide-icon';
3
+ export * from './separator';
@@ -0,0 +1,3 @@
1
+ export * from './button';
2
+ export * from './lucide-icon';
3
+ export * from './separator';
@@ -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
@@ -0,0 +1,4 @@
1
+ import { Dispatch, SetStateAction } from 'react';
2
+
3
+ export type BooleanSetter = Dispatch<SetStateAction<boolean>>;
4
+ export type StateSetter<T> = Dispatch<SetStateAction<T>>;
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,3 @@
1
+ export * from './button';
2
+ export * from './lucide-icon';
3
+ export * from './separator';
@@ -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
+ };
@@ -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 };