b44ui 0.0.13 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +99 -0
- package/dist/index.js +194 -0
- package/package.json +10 -4
- package/styles.css +1 -1
- package/tailwind.css +23 -1
- package/index.tsx +0 -309
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { ClassValue } from "clsx";
|
|
3
|
+
export type Color = 'red' | 'blue' | 'orange' | 'purple' | 'yellow' | 'green';
|
|
4
|
+
export declare const CN: (...inputs: ClassValue[]) => string;
|
|
5
|
+
export type DProps = {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
cn?: any;
|
|
8
|
+
cnIgnoreWrongUsage?: any;
|
|
9
|
+
style?: React.CSSProperties;
|
|
10
|
+
grow?: boolean;
|
|
11
|
+
gap?: number;
|
|
12
|
+
p?: number;
|
|
13
|
+
wd?: number;
|
|
14
|
+
ht?: number;
|
|
15
|
+
};
|
|
16
|
+
export declare const D: (props: DProps) => import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export declare const App: (props: DProps & {
|
|
18
|
+
center?: boolean;
|
|
19
|
+
width?: number;
|
|
20
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
export declare const Centered: (props: DProps & {
|
|
22
|
+
width?: number;
|
|
23
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export declare const TabList: (props: DProps) => import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export declare const Tab: ({ title, active, click, grow, gap, p, ...rest }: DProps & {
|
|
26
|
+
title: ReactNode;
|
|
27
|
+
active?: boolean;
|
|
28
|
+
click?: () => void;
|
|
29
|
+
} & React.ButtonHTMLAttributes<HTMLButtonElement>) => import("react/jsx-runtime").JSX.Element;
|
|
30
|
+
export declare const Block: (props: DProps & {
|
|
31
|
+
label?: ReactNode;
|
|
32
|
+
row?: boolean;
|
|
33
|
+
dashed?: boolean;
|
|
34
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
35
|
+
export declare const BlockSm: (props: DProps & {
|
|
36
|
+
dashed?: boolean;
|
|
37
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
38
|
+
export declare const Chip: (props: DProps & {
|
|
39
|
+
click?: () => void;
|
|
40
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
export declare const Card: (props: DProps) => import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export declare const Col: (props: DProps & {
|
|
43
|
+
align?: "start" | "center" | "end";
|
|
44
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
45
|
+
export declare const Popover: (props: DProps & {
|
|
46
|
+
text: ReactNode;
|
|
47
|
+
color?: Color;
|
|
48
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
49
|
+
export declare const Muted: (props: DProps) => import("react/jsx-runtime").JSX.Element;
|
|
50
|
+
export declare const A: ({ children, cn, cnIgnoreWrongUsage, grow, gap, p, wd, style, click, href, onClick, onKeyDown, role, tabIndex, ...rest }: DProps & {
|
|
51
|
+
click?: () => void;
|
|
52
|
+
} & React.AnchorHTMLAttributes<HTMLAnchorElement>) => import("react/jsx-runtime").JSX.Element;
|
|
53
|
+
export declare const Btn: ({ children, grow, gap, p, click, color, ghost, sm, ...rest }: DProps & {
|
|
54
|
+
click?: () => void;
|
|
55
|
+
color?: Color;
|
|
56
|
+
ghost?: boolean;
|
|
57
|
+
sm?: boolean;
|
|
58
|
+
} & React.ButtonHTMLAttributes<HTMLButtonElement>) => import("react/jsx-runtime").JSX.Element;
|
|
59
|
+
export declare const Tint: (props: DProps & {
|
|
60
|
+
color: Color;
|
|
61
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
62
|
+
export declare const Row: (props: DProps & {
|
|
63
|
+
ratio?: number;
|
|
64
|
+
align?: "mid" | "start" | "end";
|
|
65
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
66
|
+
export declare const Toolbar: (props: DProps) => import("react/jsx-runtime").JSX.Element;
|
|
67
|
+
export declare const Modal: (props: DProps & {
|
|
68
|
+
open: boolean;
|
|
69
|
+
}) => import("react/jsx-runtime").JSX.Element | null;
|
|
70
|
+
export declare const Input: ({ cn, cnIgnoreWrongUsage, grow, ...rest }: DProps & React.InputHTMLAttributes<HTMLInputElement>) => import("react/jsx-runtime").JSX.Element;
|
|
71
|
+
export declare const Textarea: ({ cn, cnIgnoreWrongUsage, ref, grow, ...rest }: DProps & React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
72
|
+
ref?: React.RefObject<HTMLTextAreaElement | null>;
|
|
73
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
74
|
+
export declare const Select: ({ cn, cnIgnoreWrongUsage, grow, ...rest }: DProps & React.SelectHTMLAttributes<HTMLSelectElement>) => import("react/jsx-runtime").JSX.Element;
|
|
75
|
+
export declare const Grid: (props: DProps & {
|
|
76
|
+
cols?: number;
|
|
77
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
78
|
+
export declare const Hr: ({ vertical, color, grow, gap, p, ...rest }: DProps & {
|
|
79
|
+
vertical?: boolean;
|
|
80
|
+
color?: Color | "gray";
|
|
81
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
82
|
+
export declare const Progress: ({ value, color, dot, grow, gap, p, ...rest }: DProps & {
|
|
83
|
+
value: number;
|
|
84
|
+
color?: Color;
|
|
85
|
+
dot?: boolean;
|
|
86
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
87
|
+
export declare const Dropzone: ({ children, onFiles, multiple, accept, click, onClick, onDragOver, onDrop, ...rest }: DProps & {
|
|
88
|
+
onFiles: (files: File[]) => void;
|
|
89
|
+
multiple?: boolean;
|
|
90
|
+
accept?: string;
|
|
91
|
+
click?: () => void;
|
|
92
|
+
} & React.HTMLAttributes<HTMLDivElement>) => import("react/jsx-runtime").JSX.Element;
|
|
93
|
+
export declare const Code: (props: DProps & {
|
|
94
|
+
highlight?: string;
|
|
95
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
96
|
+
export declare const Md: ({ children, className }: {
|
|
97
|
+
children: string;
|
|
98
|
+
className?: string;
|
|
99
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { Marked } from "marked";
|
|
4
|
+
import { markedHighlight } from "marked-highlight";
|
|
5
|
+
import hljs from "highlight.js";
|
|
6
|
+
import clsx from "clsx";
|
|
7
|
+
import { twMerge } from "tailwind-merge";
|
|
8
|
+
const tintMap = {
|
|
9
|
+
red: 'bg-red-500/20 border-red-500',
|
|
10
|
+
blue: 'bg-blue-500/20 border-blue-500',
|
|
11
|
+
orange: 'bg-orange-500/20 border-orange-500',
|
|
12
|
+
purple: 'bg-purple-500/20 border-purple-500',
|
|
13
|
+
yellow: 'bg-yellow-500/20 border-yellow-500',
|
|
14
|
+
green: 'bg-green-500/20 border-green-500',
|
|
15
|
+
};
|
|
16
|
+
const colorMap = {
|
|
17
|
+
red: 'bg-red-600 hover:bg-red-500',
|
|
18
|
+
blue: 'bg-blue-600 hover:bg-blue-500',
|
|
19
|
+
orange: 'bg-orange-600 hover:bg-orange-500',
|
|
20
|
+
purple: 'bg-purple-600 hover:bg-purple-500',
|
|
21
|
+
yellow: 'bg-yellow-600 hover:bg-yellow-500',
|
|
22
|
+
green: 'bg-green-600 hover:bg-green-500',
|
|
23
|
+
};
|
|
24
|
+
const tintCn = (c) => tintMap[c];
|
|
25
|
+
const colorCn = (c) => colorMap[c];
|
|
26
|
+
export const CN = (...inputs) => twMerge(clsx(inputs));
|
|
27
|
+
const warnCn = (cn) => typeof cn === 'string' && cn.length > 24 && console.warn(`long cn detected (${cn.length} chars). please avoid using cn. you SHOULD use b44ui builtins instead. you SHOULD NOT use cn to add unnecessary visual enhancements. you MUST NOT have long cns. if you choose to ignore these requirements (ie. because you cannot achieve this otherwise) you MUST instead use <... cnIgnoreWrongUsage="styles..">\n cn="${cn}"`);
|
|
28
|
+
const rcn = ({ cn, cnIgnoreWrongUsage }) => { warnCn(cn); return cn ?? cnIgnoreWrongUsage; };
|
|
29
|
+
const sc = (n) => n !== undefined ? n * 4 : undefined; // tailwind scale -> px
|
|
30
|
+
const dStyle = ({ gap, p, wd, ht, style }) => {
|
|
31
|
+
const g = sc(gap), pd = sc(p);
|
|
32
|
+
if (!g && !pd && wd === undefined && ht === undefined && !style)
|
|
33
|
+
return undefined;
|
|
34
|
+
return { ...(g !== undefined && { gap: g }), ...(pd !== undefined && { padding: pd }), ...(wd !== undefined && { width: `${wd * 100}%` }), ...(ht !== undefined && { height: `${ht * 100}%` }), ...style };
|
|
35
|
+
};
|
|
36
|
+
export const D = (props) => _jsx("div", { className: CN(props.cn ?? props.cnIgnoreWrongUsage, props.grow && "flex-1"), style: dStyle(props), children: props.children });
|
|
37
|
+
export const App = (props) => {
|
|
38
|
+
const { children, center = true, width, gap, p } = props;
|
|
39
|
+
const c = rcn(props);
|
|
40
|
+
const inner = Children.map(children, c => typeof c == 'string' ? _jsx(Md, { children: c.trim() }) : c);
|
|
41
|
+
return _jsx(D, { cn: CN("min-h-screen w-full bg-[#111] text-[#eee] font-[Inter]", center && "flex justify-center items-center", c), gap: gap, p: p, children: center ? _jsx("div", { className: "max-w-full px-5 py-10 space-y-5 *:mx-auto", style: { width }, children: inner }) : inner });
|
|
42
|
+
};
|
|
43
|
+
export const Centered = (props) => {
|
|
44
|
+
const { children, grow, gap, p, wd, ht, width = 700 } = props;
|
|
45
|
+
return _jsx(D, { cn: CN("mx-auto max-w-full px-6", rcn(props)), grow: grow, gap: gap, p: p, wd: wd, ht: ht, style: { width }, children: children });
|
|
46
|
+
};
|
|
47
|
+
export const TabList = (props) => {
|
|
48
|
+
const { children, grow, gap, p, wd, ht } = props;
|
|
49
|
+
return _jsx(D, { cn: CN("flex items-end gap-4", rcn(props)), grow: grow, gap: gap, p: p, wd: wd, ht: ht, children: children });
|
|
50
|
+
};
|
|
51
|
+
export const Tab = ({ title, active, click, grow, gap, p, ...rest }) => {
|
|
52
|
+
const c = rcn(rest);
|
|
53
|
+
return _jsx("button", { className: CN("cursor-pointer border-b-2 pb-1.5 text-sm font-medium", active ? "border-zinc-100 text-zinc-100" : "border-transparent text-zinc-400 hover:text-zinc-200", grow && "flex-1", c), style: dStyle({ gap, p, wd: rest.wd, style: rest.style }), onClick: click, role: "tab", "aria-selected": active, ...rest, children: title });
|
|
54
|
+
};
|
|
55
|
+
export const Block = (props) => {
|
|
56
|
+
const { label, children, row, dashed, grow, gap, p, wd, ht } = props;
|
|
57
|
+
return _jsxs(D, { cn: CN("rounded flex flex-col items-center", dashed && "border border-dashed border-zinc-700", rcn(props)), grow: grow, gap: gap ?? 4, p: p ?? 4, wd: wd, ht: ht, children: [label && _jsx("span", { className: "opacity-75", children: label }), _jsx("div", { className: CN(row ? "flex-row" : "flex-col", "flex items-center"), style: { gap: sc(gap ?? 4) }, children: children })] });
|
|
58
|
+
};
|
|
59
|
+
export const BlockSm = (props) => {
|
|
60
|
+
const { children, dashed, grow, gap, p, wd, ht } = props;
|
|
61
|
+
return _jsx(D, { cn: CN("rounded text-sm", dashed && "border border-dashed border-zinc-700", rcn(props)), grow: grow, gap: gap, p: p ?? 2, wd: wd, ht: ht, style: { paddingLeft: sc(3), paddingRight: sc(3) }, children: children });
|
|
62
|
+
};
|
|
63
|
+
export const Chip = (props) => {
|
|
64
|
+
const { children, click, gap, p, wd, ht, style } = props;
|
|
65
|
+
return _jsx("span", { className: CN("bg-zinc-800 rounded px-2 py-0.5 text-xs", click && "cursor-pointer hover:bg-zinc-700", rcn(props)), style: dStyle({ gap, p, wd, ht, style }), onClick: click, children: children });
|
|
66
|
+
};
|
|
67
|
+
export const Card = (props) => {
|
|
68
|
+
const { children, grow, gap, p, wd, ht } = props;
|
|
69
|
+
return _jsx(D, { cn: CN("rounded border border-zinc-700 bg-zinc-900", rcn(props)), grow: grow, gap: gap ?? 4, p: p ?? 4, wd: wd, ht: ht, style: { display: 'flex', flexDirection: 'column' }, children: children });
|
|
70
|
+
};
|
|
71
|
+
export const Col = (props) => {
|
|
72
|
+
const { children, grow, gap, p, wd, ht, align } = props;
|
|
73
|
+
return _jsx(D, { cn: CN("flex flex-col", align === 'start' && 'items-start', align === 'center' && 'items-center', align === 'end' && 'items-end', rcn(props)), grow: grow, gap: gap ?? 4, p: p, wd: wd, ht: ht, children: children });
|
|
74
|
+
};
|
|
75
|
+
export const Popover = (props) => {
|
|
76
|
+
const { children, text, color } = props;
|
|
77
|
+
const c = rcn(props);
|
|
78
|
+
const [open, setOpen] = useState(false);
|
|
79
|
+
return _jsxs("span", { className: "relative inline-block", onMouseEnter: () => setOpen(true), onMouseLeave: () => setOpen(false), children: [_jsx("span", { className: CN("cursor-pointer", color && tintCn(color), color && "border-b-2", c), children: children }), open && _jsx("div", { className: "absolute left-0 top-full z-10 mt-1", children: _jsx(Card, { cn: "w-64 shadow-lg text-sm", p: props.p ?? 3, gap: props.gap, children: text }) })] });
|
|
80
|
+
};
|
|
81
|
+
export const Muted = (props) => {
|
|
82
|
+
const { children, grow } = props;
|
|
83
|
+
return _jsx("span", { className: CN("text-sm text-zinc-400", grow && "flex-1", rcn(props)), children: children });
|
|
84
|
+
};
|
|
85
|
+
export const A = ({ children, cn, cnIgnoreWrongUsage, grow, gap, p, wd, style, click, href, onClick, onKeyDown, role, tabIndex, ...rest }) => {
|
|
86
|
+
const c = rcn({ cn, cnIgnoreWrongUsage });
|
|
87
|
+
const fire = (e) => {
|
|
88
|
+
click?.();
|
|
89
|
+
onClick?.(e);
|
|
90
|
+
};
|
|
91
|
+
const buttonLike = !href;
|
|
92
|
+
return _jsx("a", { ...rest, href: href, role: buttonLike ? role ?? 'button' : role, tabIndex: buttonLike ? tabIndex ?? 0 : tabIndex, className: CN("inline-flex items-center gap-1 text-zinc-300 underline underline-offset-4 cursor-pointer hover:text-zinc-100", grow && "flex-1", c), style: dStyle({ gap, p, wd, style }), onClick: fire, onKeyDown: e => {
|
|
93
|
+
if (buttonLike && (e.key === 'Enter' || e.key === ' ')) {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
fire(e);
|
|
96
|
+
}
|
|
97
|
+
onKeyDown?.(e);
|
|
98
|
+
}, children: children });
|
|
99
|
+
};
|
|
100
|
+
export const Btn = ({ children, grow, gap, p, click, color, ghost, sm, ...rest }) => {
|
|
101
|
+
const c = rcn(rest);
|
|
102
|
+
return _jsx("button", { className: CN("rounded cursor-pointer", sm ? "px-3 py-1 text-xs" : "px-4 py-2 text-sm", ghost ? "bg-transparent text-zinc-400 hover:bg-zinc-800" : color ? colorCn(color) : "bg-zinc-800 hover:bg-zinc-700", grow && "flex-1", c), style: dStyle({ gap, p, wd: rest.wd, style: rest.style }), onClick: click, ...rest, children: children });
|
|
103
|
+
};
|
|
104
|
+
export const Tint = (props) => {
|
|
105
|
+
const { children, color, grow, gap, p, wd, ht } = props;
|
|
106
|
+
return _jsx(D, { cn: CN("rounded text-sm", tintCn(color), rcn(props)), grow: grow, gap: gap, p: p ?? 3, wd: wd, ht: ht, children: children });
|
|
107
|
+
};
|
|
108
|
+
export const Row = (props) => {
|
|
109
|
+
const { children, ratio, align, grow, gap, p, wd, ht, style } = props;
|
|
110
|
+
return _jsx("div", { className: CN("flex items-center justify-center", align === 'mid' && "justify-between", align == 'end' && "justify-end", align == 'start' && "justify-start", grow && "flex-1", rcn(props)), style: { ...dStyle({ gap: gap ?? 4, p, wd, ht, style }), ...(ratio ? { width: `${ratio * 100}%` } : {}) }, children: children });
|
|
111
|
+
};
|
|
112
|
+
export const Toolbar = (props) => {
|
|
113
|
+
const { children, gap, p, wd, ht } = props;
|
|
114
|
+
return _jsx(Row, { cn: CN("border-b border-zinc-700", rcn(props)), gap: gap, p: p ?? 2, wd: wd, ht: ht, align: "mid", children: children });
|
|
115
|
+
};
|
|
116
|
+
export const Modal = (props) => {
|
|
117
|
+
const { children, open, gap, p, wd, ht } = props;
|
|
118
|
+
return open ? _jsx("div", { className: "fixed inset-0 flex items-center justify-center bg-black/50 z-50", children: _jsx(Card, { cn: CN("max-h-[80vh] overflow-y-auto w-lg", rcn(props)), gap: gap, p: p, wd: wd, ht: ht, children: children }) }) : null;
|
|
119
|
+
};
|
|
120
|
+
export const Input = ({ cn, cnIgnoreWrongUsage, grow, ...rest }) => {
|
|
121
|
+
const c = rcn({ cn, cnIgnoreWrongUsage });
|
|
122
|
+
return _jsx("input", { className: CN("rounded bg-zinc-800 border border-zinc-700 px-3 py-2 text-sm outline-none", grow && "flex-1", c), style: dStyle({ gap: rest.gap, p: rest.p, wd: rest.wd, style: rest.style }), ...rest });
|
|
123
|
+
};
|
|
124
|
+
export const Textarea = ({ cn, cnIgnoreWrongUsage, ref, grow, ...rest }) => {
|
|
125
|
+
const c = rcn({ cn, cnIgnoreWrongUsage });
|
|
126
|
+
return _jsx("textarea", { ref: ref, className: CN("rounded bg-zinc-800 border border-zinc-700 px-3 py-2 text-sm outline-none w-full", grow && "flex-1", c), style: dStyle({ gap: rest.gap, p: rest.p, wd: rest.wd, style: rest.style }), ...rest });
|
|
127
|
+
};
|
|
128
|
+
export const Select = ({ cn, cnIgnoreWrongUsage, grow, ...rest }) => {
|
|
129
|
+
const c = rcn({ cn, cnIgnoreWrongUsage });
|
|
130
|
+
return _jsx("select", { className: CN("rounded bg-zinc-800 border border-zinc-700 px-3 py-2 text-sm outline-none cursor-pointer", grow && "flex-1", c), style: dStyle({ gap: rest.gap, p: rest.p, wd: rest.wd, style: rest.style }), ...rest });
|
|
131
|
+
};
|
|
132
|
+
export const Grid = (props) => {
|
|
133
|
+
const { children, cols, grow, gap, p, wd, ht } = props;
|
|
134
|
+
return _jsx(D, { cn: CN("grid ui-grid", rcn(props)), grow: grow, p: p, wd: wd, ht: ht, style: { gap: sc(gap ?? 4), '--cols': cols ?? Children.count(children) }, children: children });
|
|
135
|
+
};
|
|
136
|
+
export const Hr = ({ vertical, color = 'gray', grow, gap, p, ...rest }) => {
|
|
137
|
+
const c = rcn(rest);
|
|
138
|
+
const border = color === 'gray' ? "border-zinc-700" : {
|
|
139
|
+
red: "border-red-500",
|
|
140
|
+
blue: "border-blue-500",
|
|
141
|
+
orange: "border-orange-500",
|
|
142
|
+
purple: "border-purple-500",
|
|
143
|
+
yellow: "border-yellow-500",
|
|
144
|
+
green: "border-green-500",
|
|
145
|
+
}[color];
|
|
146
|
+
return _jsx("div", { className: CN(vertical ? "self-stretch border-l" : "w-full border-t", border, grow && "flex-1", c), style: dStyle({ gap, p, wd: rest.wd, style: rest.style }), "aria-hidden": "true" });
|
|
147
|
+
};
|
|
148
|
+
export const Progress = ({ value, color = 'blue', dot, grow, gap, p, ...rest }) => {
|
|
149
|
+
const c = rcn(rest);
|
|
150
|
+
const v = Math.max(0, Math.min(1, value));
|
|
151
|
+
const fill = color === 'red' ? 'bg-red-500'
|
|
152
|
+
: color === 'orange' ? 'bg-orange-500'
|
|
153
|
+
: color === 'purple' ? 'bg-purple-500'
|
|
154
|
+
: color === 'yellow' ? 'bg-yellow-500'
|
|
155
|
+
: color === 'green' ? 'bg-green-500'
|
|
156
|
+
: 'bg-blue-500';
|
|
157
|
+
if (dot)
|
|
158
|
+
return _jsx("div", { className: CN("rounded-full border border-zinc-700 bg-zinc-800 overflow-hidden", grow && "flex-1", c), style: dStyle({ gap, p, wd: rest.wd, style: { aspectRatio: '1 / 1', ...rest.style } }), children: _jsx("div", { className: CN("h-full rounded-full", fill), style: { opacity: v ? 1 : 0.2 } }) });
|
|
159
|
+
return _jsx("div", { className: CN("h-2 overflow-hidden rounded-full bg-zinc-800 border border-zinc-700", grow && "flex-1", c), style: dStyle({ gap, p, wd: rest.wd, style: rest.style }), children: _jsx("div", { className: CN("h-full rounded-full", fill), style: { width: `${v * 100}%` } }) });
|
|
160
|
+
};
|
|
161
|
+
export const Dropzone = ({ children, onFiles, multiple, accept, click, onClick, onDragOver, onDrop, ...rest }) => {
|
|
162
|
+
const input = useRef(null);
|
|
163
|
+
const c = rcn(rest);
|
|
164
|
+
const push = (list) => {
|
|
165
|
+
const files = list ? Array.from(list) : [];
|
|
166
|
+
if (files.length)
|
|
167
|
+
onFiles(files);
|
|
168
|
+
};
|
|
169
|
+
return _jsxs("div", { className: CN("contents", c), onClick: e => {
|
|
170
|
+
click?.();
|
|
171
|
+
input.current?.click();
|
|
172
|
+
onClick?.(e);
|
|
173
|
+
}, onDragOver: e => {
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
onDragOver?.(e);
|
|
176
|
+
}, onDrop: e => {
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
push(e.dataTransfer.files);
|
|
179
|
+
onDrop?.(e);
|
|
180
|
+
}, children: [_jsx("input", { ref: input, hidden: true, type: "file", multiple: multiple, accept: accept, onChange: e => push(e.target.files) }), children] });
|
|
181
|
+
};
|
|
182
|
+
export const Code = (props) => {
|
|
183
|
+
const { children, highlight } = props;
|
|
184
|
+
const c = rcn(props);
|
|
185
|
+
const html = useMemo(() => highlight ? hljs.highlight(children, { language: highlight }).value : '', [children, highlight]);
|
|
186
|
+
if (highlight)
|
|
187
|
+
return _jsx("pre", { className: CN("rounded bg-zinc-900 border border-zinc-700 p-3 text-sm overflow-x-auto", c), children: _jsx("code", { className: `hljs language-${highlight}`, dangerouslySetInnerHTML: { __html: html } }) });
|
|
188
|
+
return _jsx("code", { className: CN("bg-zinc-800 rounded px-1.5 py-0.5 text-sm font-mono", c), children: children });
|
|
189
|
+
};
|
|
190
|
+
const marked = new Marked(markedHighlight({ langPrefix: "hljs language-", highlight: c => hljs.highlightAuto(c).value }));
|
|
191
|
+
export const Md = ({ children, className }) => {
|
|
192
|
+
const html = useMemo(() => marked.parse(children), [children]);
|
|
193
|
+
return _jsx("div", { className: CN("ui-markdown", className), dangerouslySetInnerHTML: { __html: html } });
|
|
194
|
+
};
|
package/package.json
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "b44ui",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
|
-
"
|
|
6
|
+
"dist",
|
|
7
7
|
"styles.css",
|
|
8
8
|
"tailwind.css"
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
|
-
"
|
|
11
|
+
"build": "tsc -p tsconfig.json",
|
|
12
|
+
"dev": "npm run dev --prefix example",
|
|
13
|
+
"prepare": "npm run build",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
12
15
|
},
|
|
13
16
|
"exports": {
|
|
14
|
-
".":
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
},
|
|
15
21
|
"./tailwind.css": "./tailwind.css",
|
|
16
22
|
"./styles.css": "./styles.css"
|
|
17
23
|
},
|
package/styles.css
CHANGED
|
@@ -18,4 +18,4 @@ img { max-width: 100%; border-radius: 6px; }
|
|
|
18
18
|
.ui-markdown table { border-collapse: collapse; margin: 0.4em 0; }
|
|
19
19
|
.ui-markdown th, .ui-markdown td { border: 1px solid #333; padding: 4px 10px; }
|
|
20
20
|
.ui-grid { grid-template-columns: repeat(var(--cols), 1fr); }
|
|
21
|
-
@media (max-width: 640px) { .ui-grid { grid-template-columns: 1fr; } }
|
|
21
|
+
@media (max-width: 640px) { .ui-grid { grid-template-columns: 1fr; } }
|
package/tailwind.css
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
@source "./index.tsx";
|
|
3
|
-
@import "
|
|
3
|
+
@import "highlight.js/styles/github-dark.css";
|
|
4
|
+
|
|
5
|
+
h1, .ui-markdown h2, .ui-markdown h3 { font-weight: 700; margin: 0; }
|
|
6
|
+
h1 { font-size: 1.5em; }
|
|
7
|
+
h2 { font-size: 1.25em; }
|
|
8
|
+
h3 { font-size: 1.1em; }
|
|
9
|
+
p { margin: 0.4em 0; }
|
|
10
|
+
a { color: #93c5fd; text-decoration: underline; }
|
|
11
|
+
pre { background: #1a1a2e; border-radius: 4px; padding: 0; overflow-x: auto; margin: 0.4em 0; }
|
|
12
|
+
code { font-family: ui-monospace, monospace; font-size: 0.9em; }
|
|
13
|
+
blockquote { border-left: 3px solid #444; padding-left: 1em; opacity: 0.8; margin: 0.25em 0; }
|
|
14
|
+
img { max-width: 100%; border-radius: 6px; }
|
|
15
|
+
|
|
16
|
+
.ui-grid { grid-template-columns: repeat(var(--cols), 1fr); }
|
|
17
|
+
@media (max-width: 640px) { .ui-grid { grid-template-columns: 1fr; } }
|
|
18
|
+
|
|
19
|
+
.ui-markdown { line-height: 1.5; }
|
|
20
|
+
.ui-markdown p:first-child { margin-top: 0; }
|
|
21
|
+
.ui-markdown p:last-child { margin-bottom: 0; }
|
|
22
|
+
.ui-markdown :not(pre) > code { background: #1a1a2e; padding: 2px 5px; border-radius: 3px; }
|
|
23
|
+
.ui-markdown ul, .ui-markdown ol { padding-left: 1.5em; margin: 0.25em 0; }
|
|
24
|
+
.ui-markdown table { border-collapse: collapse; margin: 0.4em 0; }
|
|
25
|
+
.ui-markdown th, .ui-markdown td { border: 1px solid #333; padding: 4px 10px; }
|
package/index.tsx
DELETED
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
import { type ReactNode, Children, useMemo, useRef, useState } from "react"
|
|
2
|
-
import { Marked } from "marked"
|
|
3
|
-
import { markedHighlight } from "marked-highlight"
|
|
4
|
-
import hljs from "highlight.js"
|
|
5
|
-
import type { ClassValue } from "clsx"
|
|
6
|
-
import clsx from "clsx"
|
|
7
|
-
import { twMerge } from "tailwind-merge"
|
|
8
|
-
|
|
9
|
-
export type Color = 'red' | 'blue' | 'orange' | 'purple' | 'yellow' | 'green'
|
|
10
|
-
const tintMap: Record<Color, string> = {
|
|
11
|
-
red: 'bg-red-500/20 border-red-500',
|
|
12
|
-
blue: 'bg-blue-500/20 border-blue-500',
|
|
13
|
-
orange: 'bg-orange-500/20 border-orange-500',
|
|
14
|
-
purple: 'bg-purple-500/20 border-purple-500',
|
|
15
|
-
yellow: 'bg-yellow-500/20 border-yellow-500',
|
|
16
|
-
green: 'bg-green-500/20 border-green-500',
|
|
17
|
-
}
|
|
18
|
-
const colorMap: Record<Color, string> = {
|
|
19
|
-
red: 'bg-red-600 hover:bg-red-500',
|
|
20
|
-
blue: 'bg-blue-600 hover:bg-blue-500',
|
|
21
|
-
orange: 'bg-orange-600 hover:bg-orange-500',
|
|
22
|
-
purple: 'bg-purple-600 hover:bg-purple-500',
|
|
23
|
-
yellow: 'bg-yellow-600 hover:bg-yellow-500',
|
|
24
|
-
green: 'bg-green-600 hover:bg-green-500',
|
|
25
|
-
}
|
|
26
|
-
const tintCn = (c: Color) => tintMap[c]
|
|
27
|
-
const colorCn = (c: Color) => colorMap[c]
|
|
28
|
-
|
|
29
|
-
export const CN = (...inputs: ClassValue[]) => twMerge(clsx(inputs))
|
|
30
|
-
|
|
31
|
-
const warnCn = (cn: any) => typeof cn === 'string' && cn.length > 24 && console.warn(`long cn detected (${cn.length} chars). please avoid using cn. you SHOULD use b44ui builtins instead. you SHOULD NOT use cn to add unnecessary visual enhancements. you MUST NOT have long cns. if you choose to ignore these requirements (ie. because you cannot achieve this otherwise) you MUST instead use <... cnIgnoreWrongUsage="styles..">\n cn="${cn}"`)
|
|
32
|
-
const rcn = ({ cn, cnIgnoreWrongUsage }: DProps) => { warnCn(cn); return cn ?? cnIgnoreWrongUsage }
|
|
33
|
-
|
|
34
|
-
const sc = (n?: number) => n !== undefined ? n * 4 : undefined // tailwind scale -> px
|
|
35
|
-
|
|
36
|
-
export type DProps = {
|
|
37
|
-
children?: ReactNode
|
|
38
|
-
cn?: any
|
|
39
|
-
cnIgnoreWrongUsage?: any
|
|
40
|
-
style?: React.CSSProperties
|
|
41
|
-
grow?: boolean
|
|
42
|
-
gap?: number
|
|
43
|
-
p?: number
|
|
44
|
-
wd?: number
|
|
45
|
-
ht?: number
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const dStyle = ({ gap, p, wd, ht, style }: DProps): React.CSSProperties | undefined => {
|
|
49
|
-
const g = sc(gap), pd = sc(p)
|
|
50
|
-
if (!g && !pd && wd === undefined && ht === undefined && !style) return undefined
|
|
51
|
-
return { ...(g !== undefined && { gap: g }), ...(pd !== undefined && { padding: pd }), ...(wd !== undefined && { width: `${wd * 100}%` }), ...(ht !== undefined && { height: `${ht * 100}%` }), ...style }
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export const D = (props: DProps) =>
|
|
55
|
-
<div className={CN(props.cn ?? props.cnIgnoreWrongUsage, props.grow && "flex-1")} style={dStyle(props)}>{props.children}</div>
|
|
56
|
-
|
|
57
|
-
export const App = (props: DProps & { center?: boolean, width?: number }) => {
|
|
58
|
-
const { children, center = true, width, gap, p } = props
|
|
59
|
-
const c = rcn(props)
|
|
60
|
-
const inner = Children.map(children, c => typeof c == 'string' ? <Md>{c.trim()}</Md> : c)
|
|
61
|
-
return <D cn={CN("min-h-screen w-full bg-[#111] text-[#eee] font-[Inter]", center && "flex justify-center items-center", c)} gap={gap} p={p}>
|
|
62
|
-
{center ? <div className="max-w-full px-5 py-10 space-y-5 *:mx-auto" style={{ width }}>{inner}</div> : inner}
|
|
63
|
-
</D>
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export const Centered = (props: DProps & { width?: number }) => {
|
|
67
|
-
const { children, grow, gap, p, wd, ht, width = 700 } = props
|
|
68
|
-
return <D cn={CN("mx-auto max-w-full px-6", rcn(props))} grow={grow} gap={gap} p={p} wd={wd} ht={ht} style={{ width }}>{children}</D>
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export const TabList = (props: DProps) => {
|
|
72
|
-
const { children, grow, gap, p, wd, ht } = props
|
|
73
|
-
return <D cn={CN("flex items-end gap-4", rcn(props))} grow={grow} gap={gap} p={p} wd={wd} ht={ht}>{children}</D>
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export const Tab = ({ title, active, click, grow, gap, p, ...rest }: DProps & { title: ReactNode, active?: boolean, click?: () => void } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
|
77
|
-
const c = rcn(rest as DProps)
|
|
78
|
-
return <button
|
|
79
|
-
className={CN("cursor-pointer border-b-2 pb-1.5 text-sm font-medium", active ? "border-zinc-100 text-zinc-100" : "border-transparent text-zinc-400 hover:text-zinc-200", grow && "flex-1", c)}
|
|
80
|
-
style={dStyle({ gap, p, wd: rest.wd, style: rest.style })}
|
|
81
|
-
onClick={click}
|
|
82
|
-
role="tab"
|
|
83
|
-
aria-selected={active}
|
|
84
|
-
{...rest}
|
|
85
|
-
>{title}</button>
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export const Block = (props: DProps & { label?: ReactNode, row?: boolean, dashed?: boolean }) => {
|
|
89
|
-
const { label, children, row, dashed, grow, gap, p, wd, ht } = props
|
|
90
|
-
return <D cn={CN("rounded flex flex-col items-center", dashed && "border border-dashed border-zinc-700", rcn(props))} grow={grow} gap={gap ?? 4} p={p ?? 4} wd={wd} ht={ht}>
|
|
91
|
-
{label && <span className="opacity-75">{label}</span>}
|
|
92
|
-
<div className={CN(row ? "flex-row" : "flex-col", "flex items-center")} style={{ gap: sc(gap ?? 4) }}>{children}</div>
|
|
93
|
-
</D>
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export const BlockSm = (props: DProps & { dashed?: boolean }) => {
|
|
97
|
-
const { children, dashed, grow, gap, p, wd, ht } = props
|
|
98
|
-
return <D cn={CN("rounded text-sm", dashed && "border border-dashed border-zinc-700", rcn(props))} grow={grow} gap={gap} p={p ?? 2} wd={wd} ht={ht}
|
|
99
|
-
style={{ paddingLeft: sc(3), paddingRight: sc(3) }}>{children}</D>
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export const Chip = (props: DProps & { click?: () => void }) => {
|
|
103
|
-
const { children, click, grow, gap, p, wd, ht, style } = props
|
|
104
|
-
return <span className={CN("bg-zinc-800 rounded px-2 py-0.5 text-xs", click && "cursor-pointer hover:bg-zinc-700", rcn(props))}
|
|
105
|
-
style={dStyle({ gap, p, wd, ht, style })}
|
|
106
|
-
onClick={click}>{children}</span>
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export const Card = (props: DProps) => {
|
|
110
|
-
const { children, grow, gap, p, wd, ht } = props
|
|
111
|
-
return <D cn={CN("rounded border border-zinc-700 bg-zinc-900", rcn(props))} grow={grow} gap={gap ?? 4} p={p ?? 4} wd={wd} ht={ht}
|
|
112
|
-
style={{ display: 'flex', flexDirection: 'column' }}>{children}</D>
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export const Col = (props: DProps & { align?: 'start' | 'center' | 'end' }) => {
|
|
116
|
-
const { children, grow, gap, p, wd, ht, align } = props
|
|
117
|
-
return <D cn={CN("flex flex-col", align === 'start' && 'items-start', align === 'center' && 'items-center', align === 'end' && 'items-end', rcn(props))} grow={grow} gap={gap ?? 4} p={p} wd={wd} ht={ht}>{children}</D>
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export const Popover = (props: DProps & { text: ReactNode, color?: Color }) => {
|
|
121
|
-
const { children, text, color } = props
|
|
122
|
-
const c = rcn(props)
|
|
123
|
-
const [open, setOpen] = useState(false)
|
|
124
|
-
return <span className="relative inline-block" onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
|
|
125
|
-
<span className={CN("cursor-pointer", color && tintCn(color), color && "border-b-2", c)}>{children}</span>
|
|
126
|
-
{open && <div className="absolute left-0 top-full z-10 mt-1">
|
|
127
|
-
<Card cn="w-64 shadow-lg text-sm" p={props.p ?? 3} gap={props.gap}>{text}</Card>
|
|
128
|
-
</div>}
|
|
129
|
-
</span>
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export const Muted = (props: DProps) => {
|
|
133
|
-
const { children, grow } = props
|
|
134
|
-
return <span className={CN("text-sm text-zinc-400", grow && "flex-1", rcn(props))}>{children}</span>
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export const A = ({
|
|
138
|
-
children, cn, cnIgnoreWrongUsage, grow, gap, p, wd, style, click, href, onClick, onKeyDown, role, tabIndex, ...rest
|
|
139
|
-
}: DProps & { click?: () => void } & React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
|
|
140
|
-
const c = rcn({ cn, cnIgnoreWrongUsage })
|
|
141
|
-
const fire = (e: React.MouseEvent<HTMLAnchorElement> | React.KeyboardEvent<HTMLAnchorElement>) => {
|
|
142
|
-
click?.()
|
|
143
|
-
onClick?.(e as React.MouseEvent<HTMLAnchorElement>)
|
|
144
|
-
}
|
|
145
|
-
const buttonLike = !href
|
|
146
|
-
|
|
147
|
-
return <a
|
|
148
|
-
{...rest}
|
|
149
|
-
href={href}
|
|
150
|
-
role={buttonLike ? role ?? 'button' : role}
|
|
151
|
-
tabIndex={buttonLike ? tabIndex ?? 0 : tabIndex}
|
|
152
|
-
className={CN("inline-flex items-center gap-1 text-zinc-300 underline underline-offset-4 cursor-pointer hover:text-zinc-100", grow && "flex-1", c)}
|
|
153
|
-
style={dStyle({ gap, p, wd, style })}
|
|
154
|
-
onClick={fire}
|
|
155
|
-
onKeyDown={e => {
|
|
156
|
-
if (buttonLike && (e.key === 'Enter' || e.key === ' ')) {
|
|
157
|
-
e.preventDefault()
|
|
158
|
-
fire(e)
|
|
159
|
-
}
|
|
160
|
-
onKeyDown?.(e)
|
|
161
|
-
}}
|
|
162
|
-
>{children}</a>
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export const Btn = ({ children, grow, gap, p, click, color, ghost, sm, ...rest }: DProps & { click?: () => void, color?: Color, ghost?: boolean, sm?: boolean } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
|
166
|
-
const c = rcn(rest as DProps)
|
|
167
|
-
return <button className={CN("rounded cursor-pointer", sm ? "px-3 py-1 text-xs" : "px-4 py-2 text-sm",
|
|
168
|
-
ghost ? "bg-transparent text-zinc-400 hover:bg-zinc-800" : color ? colorCn(color) : "bg-zinc-800 hover:bg-zinc-700", grow && "flex-1", c)}
|
|
169
|
-
style={dStyle({ gap, p, wd: rest.wd, style: rest.style })} onClick={click} {...rest}>{children}</button>
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export const Tint = (props: DProps & { color: Color }) => {
|
|
173
|
-
const { children, color, grow, gap, p, wd, ht } = props
|
|
174
|
-
return <D cn={CN("rounded text-sm", tintCn(color), rcn(props))} grow={grow} gap={gap} p={p ?? 3} wd={wd} ht={ht}>{children}</D>
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export const Row = (props: DProps & { ratio?: number, align?: 'mid' | 'start' | 'end' }) => {
|
|
178
|
-
const { children, ratio, align, grow, gap, p, wd, ht, style } = props
|
|
179
|
-
return <div className={CN("flex items-center justify-center", align === 'mid' && "justify-between", align == 'end' && "justify-end", align == 'start' && "justify-start", grow && "flex-1", rcn(props))}
|
|
180
|
-
style={{ ...dStyle({ gap: gap ?? 4, p, wd, ht, style }), ...(ratio ? { width: `${ratio * 100}%` } : {}) }}>{children}</div>
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export const Toolbar = (props: DProps) => {
|
|
184
|
-
const { children, gap, p, wd, ht } = props
|
|
185
|
-
return <Row cn={CN("border-b border-zinc-700", rcn(props))} gap={gap} p={p ?? 2} wd={wd} ht={ht} align="mid">{children}</Row>
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export const Modal = (props: DProps & { open: boolean }) => {
|
|
189
|
-
const { children, open, gap, p, wd, ht } = props
|
|
190
|
-
return open ? <div className="fixed inset-0 flex items-center justify-center bg-black/50 z-50">
|
|
191
|
-
<Card cn={CN("max-h-[80vh] overflow-y-auto w-lg", rcn(props))} gap={gap} p={p} wd={wd} ht={ht}>{children}</Card>
|
|
192
|
-
</div> : null
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export const Input = ({ cn, cnIgnoreWrongUsage, grow, ...rest }: DProps & React.InputHTMLAttributes<HTMLInputElement>) => {
|
|
196
|
-
const c = rcn({ cn, cnIgnoreWrongUsage })
|
|
197
|
-
return <input className={CN("rounded bg-zinc-800 border border-zinc-700 px-3 py-2 text-sm outline-none", grow && "flex-1", c)} style={dStyle({ gap: rest.gap, p: rest.p, wd: rest.wd, style: rest.style })} {...rest} />
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export const Textarea = ({ cn, cnIgnoreWrongUsage, ref, grow, ...rest }: DProps & React.TextareaHTMLAttributes<HTMLTextAreaElement> & { ref?: React.RefObject<HTMLTextAreaElement | null> }) => {
|
|
201
|
-
const c = rcn({ cn, cnIgnoreWrongUsage })
|
|
202
|
-
return <textarea ref={ref} className={CN("rounded bg-zinc-800 border border-zinc-700 px-3 py-2 text-sm outline-none w-full", grow && "flex-1", c)} style={dStyle({ gap: rest.gap, p: rest.p, wd: rest.wd, style: rest.style })} {...rest} />
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export const Select = ({ cn, cnIgnoreWrongUsage, grow, ...rest }: DProps & React.SelectHTMLAttributes<HTMLSelectElement>) => {
|
|
206
|
-
const c = rcn({ cn, cnIgnoreWrongUsage })
|
|
207
|
-
return <select className={CN("rounded bg-zinc-800 border border-zinc-700 px-3 py-2 text-sm outline-none cursor-pointer", grow && "flex-1", c)} style={dStyle({ gap: rest.gap, p: rest.p, wd: rest.wd, style: rest.style })} {...rest} />
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export const Grid = (props: DProps & { cols?: number }) => {
|
|
211
|
-
const { children, cols, grow, gap, p, wd, ht } = props
|
|
212
|
-
return <D cn={CN("grid ui-grid", rcn(props))} grow={grow} p={p} wd={wd} ht={ht} style={{ gap: sc(gap ?? 4), '--cols': cols ?? Children.count(children) } as React.CSSProperties}>{children}</D>
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export const Hr = ({ vertical, color = 'gray', grow, gap, p, ...rest }: DProps & { vertical?: boolean, color?: Color | 'gray' }) => {
|
|
216
|
-
const c = rcn(rest as DProps)
|
|
217
|
-
const border = color === 'gray' ? "border-zinc-700" : ({
|
|
218
|
-
red: "border-red-500",
|
|
219
|
-
blue: "border-blue-500",
|
|
220
|
-
orange: "border-orange-500",
|
|
221
|
-
purple: "border-purple-500",
|
|
222
|
-
yellow: "border-yellow-500",
|
|
223
|
-
green: "border-green-500",
|
|
224
|
-
} satisfies Record<Color, string>)[color]
|
|
225
|
-
|
|
226
|
-
return <div
|
|
227
|
-
className={CN(vertical ? "self-stretch border-l" : "w-full border-t", border, grow && "flex-1", c)}
|
|
228
|
-
style={dStyle({ gap, p, wd: rest.wd, style: rest.style })}
|
|
229
|
-
aria-hidden="true"
|
|
230
|
-
/>
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export const Progress = ({ value, color = 'blue', dot, grow, gap, p, ...rest }: DProps & { value: number, color?: Color, dot?: boolean }) => {
|
|
234
|
-
const c = rcn(rest as DProps)
|
|
235
|
-
const v = Math.max(0, Math.min(1, value))
|
|
236
|
-
const fill = color === 'red' ? 'bg-red-500'
|
|
237
|
-
: color === 'orange' ? 'bg-orange-500'
|
|
238
|
-
: color === 'purple' ? 'bg-purple-500'
|
|
239
|
-
: color === 'yellow' ? 'bg-yellow-500'
|
|
240
|
-
: color === 'green' ? 'bg-green-500'
|
|
241
|
-
: 'bg-blue-500'
|
|
242
|
-
|
|
243
|
-
if (dot) return <div
|
|
244
|
-
className={CN("rounded-full border border-zinc-700 bg-zinc-800 overflow-hidden", grow && "flex-1", c)}
|
|
245
|
-
style={dStyle({ gap, p, wd: rest.wd, style: { aspectRatio: '1 / 1', ...rest.style } })}
|
|
246
|
-
>
|
|
247
|
-
<div className={CN("h-full rounded-full", fill)} style={{ opacity: v ? 1 : 0.2 }} />
|
|
248
|
-
</div>
|
|
249
|
-
|
|
250
|
-
return <div
|
|
251
|
-
className={CN("h-2 overflow-hidden rounded-full bg-zinc-800 border border-zinc-700", grow && "flex-1", c)}
|
|
252
|
-
style={dStyle({ gap, p, wd: rest.wd, style: rest.style })}
|
|
253
|
-
>
|
|
254
|
-
<div className={CN("h-full rounded-full", fill)} style={{ width: `${v * 100}%` }} />
|
|
255
|
-
</div>
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
export const Dropzone = ({ children, onFiles, multiple, accept, click, ...rest }: DProps & { onFiles: (files: File[]) => void, multiple?: boolean, accept?: string, click?: () => void }) => {
|
|
259
|
-
const input = useRef<HTMLInputElement>(null)
|
|
260
|
-
const c = rcn(rest)
|
|
261
|
-
const push = (list: FileList | null) => {
|
|
262
|
-
const files = list ? Array.from(list) : []
|
|
263
|
-
if (files.length) onFiles(files)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return <div
|
|
267
|
-
className={CN("contents", c)}
|
|
268
|
-
onClick={e => {
|
|
269
|
-
click?.()
|
|
270
|
-
input.current?.click()
|
|
271
|
-
rest.onClick?.(e as any)
|
|
272
|
-
}}
|
|
273
|
-
onDragOver={e => {
|
|
274
|
-
e.preventDefault()
|
|
275
|
-
rest.onDragOver?.(e)
|
|
276
|
-
}}
|
|
277
|
-
onDrop={e => {
|
|
278
|
-
e.preventDefault()
|
|
279
|
-
push(e.dataTransfer.files)
|
|
280
|
-
rest.onDrop?.(e)
|
|
281
|
-
}}
|
|
282
|
-
>
|
|
283
|
-
<input
|
|
284
|
-
ref={input}
|
|
285
|
-
hidden
|
|
286
|
-
type="file"
|
|
287
|
-
multiple={multiple}
|
|
288
|
-
accept={accept}
|
|
289
|
-
onChange={e => push(e.target.files)}
|
|
290
|
-
/>
|
|
291
|
-
{children}
|
|
292
|
-
</div>
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
export const Code = (props: DProps & { highlight?: string }) => {
|
|
296
|
-
const { children, highlight } = props
|
|
297
|
-
const c = rcn(props)
|
|
298
|
-
const html = useMemo(() => highlight ? hljs.highlight(children as string, { language: highlight }).value : '', [children, highlight])
|
|
299
|
-
if (highlight) return <pre className={CN("rounded bg-zinc-900 border border-zinc-700 p-3 text-sm overflow-x-auto", c)}>
|
|
300
|
-
<code className={`hljs language-${highlight}`} dangerouslySetInnerHTML={{ __html: html }} />
|
|
301
|
-
</pre>
|
|
302
|
-
return <code className={CN("bg-zinc-800 rounded px-1.5 py-0.5 text-sm font-mono", c)}>{children}</code>
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const marked = new Marked(markedHighlight({ langPrefix: "hljs language-", highlight: c => hljs.highlightAuto(c).value }))
|
|
306
|
-
export const Md = ({ children, className }: { children: string, className?: string }) => {
|
|
307
|
-
const html = useMemo(() => marked.parse(children) as string, [children])
|
|
308
|
-
return <div className={CN("ui-markdown", className)} dangerouslySetInnerHTML={{ __html: html }} />
|
|
309
|
-
}
|