b44ui 0.0.11 → 0.0.12

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 (3) hide show
  1. package/index.tsx +113 -12
  2. package/package.json +1 -1
  3. package/readme.md +9 -4
package/index.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { type ReactNode, Children, useMemo, useState } from "react"
1
+ import { type ReactNode, Children, useMemo, useRef, useState } from "react"
2
2
  import { Marked } from "marked"
3
3
  import { markedHighlight } from "marked-highlight"
4
4
  import hljs from "highlight.js"
@@ -6,13 +6,14 @@ import type { ClassValue } from "clsx"
6
6
  import clsx from "clsx"
7
7
  import { twMerge } from "tailwind-merge"
8
8
 
9
- export type Color = 'red' | 'blue' | 'orange' | 'purple' | 'yellow'
9
+ export type Color = 'red' | 'blue' | 'orange' | 'purple' | 'yellow' | 'green'
10
10
  const tintMap: Record<Color, string> = {
11
11
  red: 'bg-red-500/20 border-red-500',
12
12
  blue: 'bg-blue-500/20 border-blue-500',
13
13
  orange: 'bg-orange-500/20 border-orange-500',
14
14
  purple: 'bg-purple-500/20 border-purple-500',
15
15
  yellow: 'bg-yellow-500/20 border-yellow-500',
16
+ green: 'bg-green-500/20 border-green-500',
16
17
  }
17
18
  const colorMap: Record<Color, string> = {
18
19
  red: 'bg-red-600 hover:bg-red-500',
@@ -20,6 +21,7 @@ const colorMap: Record<Color, string> = {
20
21
  orange: 'bg-orange-600 hover:bg-orange-500',
21
22
  purple: 'bg-purple-600 hover:bg-purple-500',
22
23
  yellow: 'bg-yellow-600 hover:bg-yellow-500',
24
+ green: 'bg-green-600 hover:bg-green-500',
23
25
  }
24
26
  const tintCn = (c: Color) => tintMap[c]
25
27
  const colorCn = (c: Color) => colorMap[c]
@@ -39,12 +41,13 @@ export type DProps = {
39
41
  grow?: boolean
40
42
  gap?: number
41
43
  p?: number
44
+ wd?: number
42
45
  }
43
46
 
44
- const dStyle = ({ gap, p, style }: DProps): React.CSSProperties | undefined => {
47
+ const dStyle = ({ gap, p, wd, style }: DProps): React.CSSProperties | undefined => {
45
48
  const g = sc(gap), pd = sc(p)
46
- if (!g && !pd && !style) return undefined
47
- return { ...(g !== undefined && { gap: g }), ...(pd !== undefined && { padding: pd }), ...style }
49
+ if (!g && !pd && wd === undefined && !style) return undefined
50
+ return { ...(g !== undefined && { gap: g }), ...(pd !== undefined && { padding: pd }), ...(wd !== undefined && { width: `${wd * 100}%` }), ...style }
48
51
  }
49
52
 
50
53
  export const D = (props: DProps) =>
@@ -64,6 +67,23 @@ export const Centered = (props: DProps & { width?: number }) => {
64
67
  return <D cn={CN("mx-auto max-w-full px-6", rcn(props))} grow={grow} gap={gap} p={p} style={{ width }}>{children}</D>
65
68
  }
66
69
 
70
+ export const TabList = (props: DProps) => {
71
+ const { children, grow, gap, p } = props
72
+ return <D cn={CN("flex items-end gap-4", rcn(props))} grow={grow} gap={gap} p={p}>{children}</D>
73
+ }
74
+
75
+ export const Tab = ({ title, active, click, grow, gap, p, ...rest }: DProps & { title: ReactNode, active?: boolean, click?: () => void } & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
76
+ const c = rcn(rest as DProps)
77
+ return <button
78
+ 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)}
79
+ style={dStyle({ gap, p, wd: rest.wd, style: rest.style })}
80
+ onClick={click}
81
+ role="tab"
82
+ aria-selected={active}
83
+ {...rest}
84
+ >{title}</button>
85
+ }
86
+
67
87
  export const Block = (props: DProps & { label?: ReactNode, row?: boolean, dashed?: boolean }) => {
68
88
  const { label, children, row, dashed, grow, gap, p } = props
69
89
  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}>
@@ -79,8 +99,9 @@ export const BlockSm = (props: DProps & { dashed?: boolean }) => {
79
99
  }
80
100
 
81
101
  export const Chip = (props: DProps & { click?: () => void }) => {
82
- const { children, click } = props
102
+ const { children, click, grow, gap, p, wd, style } = props
83
103
  return <span className={CN("bg-zinc-800 rounded px-2 py-0.5 text-xs", click && "cursor-pointer hover:bg-zinc-700", rcn(props))}
104
+ style={dStyle({ gap, p, wd, style })}
84
105
  onClick={click}>{children}</span>
85
106
  }
86
107
 
@@ -144,7 +165,7 @@ export const Btn = ({ children, grow, gap, p, click, color, ghost, sm, ...rest }
144
165
  const c = rcn(rest as DProps)
145
166
  return <button className={CN("rounded cursor-pointer", sm ? "px-3 py-1 text-xs" : "px-4 py-2 text-sm",
146
167
  ghost ? "bg-transparent text-zinc-400 hover:bg-zinc-800" : color ? colorCn(color) : "bg-zinc-800 hover:bg-zinc-700", grow && "flex-1", c)}
147
- style={dStyle({ gap, p })} onClick={click} {...rest}>{children}</button>
168
+ style={dStyle({ gap, p, wd: rest.wd, style: rest.style })} onClick={click} {...rest}>{children}</button>
148
169
  }
149
170
 
150
171
  export const Tint = (props: DProps & { color: Color }) => {
@@ -153,9 +174,9 @@ export const Tint = (props: DProps & { color: Color }) => {
153
174
  }
154
175
 
155
176
  export const Row = (props: DProps & { ratio?: number, align?: 'mid' | 'start' | 'end' }) => {
156
- const { children, ratio, align, grow, gap, p } = props
177
+ const { children, ratio, align, grow, gap, p, wd, style } = props
157
178
  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))}
158
- style={{ gap: sc(gap ?? 4), ...(p !== undefined && { padding: sc(p) }), ...(ratio ? { width: `${ratio * 100}%` } : {}) }}>{children}</div>
179
+ style={{ ...dStyle({ gap: gap ?? 4, p, wd, style }), ...(ratio ? { width: `${ratio * 100}%` } : {}) }}>{children}</div>
159
180
  }
160
181
 
161
182
  export const Toolbar = (props: DProps) => {
@@ -172,17 +193,17 @@ export const Modal = (props: DProps & { open: boolean }) => {
172
193
 
173
194
  export const Input = ({ cn, cnIgnoreWrongUsage, grow, ...rest }: DProps & React.InputHTMLAttributes<HTMLInputElement>) => {
174
195
  const c = rcn({ cn, cnIgnoreWrongUsage })
175
- return <input className={CN("rounded bg-zinc-800 border border-zinc-700 px-3 py-2 text-sm outline-none", grow && "flex-1", c)} {...rest} />
196
+ 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} />
176
197
  }
177
198
 
178
199
  export const Textarea = ({ cn, cnIgnoreWrongUsage, grow, ...rest }: DProps & React.TextareaHTMLAttributes<HTMLTextAreaElement>) => {
179
200
  const c = rcn({ cn, cnIgnoreWrongUsage })
180
- return <textarea className={CN("rounded bg-zinc-800 border border-zinc-700 px-3 py-2 text-sm outline-none w-full", grow && "flex-1", c)} {...rest} />
201
+ return <textarea 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} />
181
202
  }
182
203
 
183
204
  export const Select = ({ cn, cnIgnoreWrongUsage, grow, ...rest }: DProps & React.SelectHTMLAttributes<HTMLSelectElement>) => {
184
205
  const c = rcn({ cn, cnIgnoreWrongUsage })
185
- 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)} {...rest} />
206
+ 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} />
186
207
  }
187
208
 
188
209
  export const Grid = (props: DProps & { cols?: number }) => {
@@ -190,6 +211,86 @@ export const Grid = (props: DProps & { cols?: number }) => {
190
211
  return <D cn={CN("grid ui-grid", rcn(props))} grow={grow} p={p} style={{ gap: sc(gap ?? 4), '--cols': cols ?? Children.count(children) } as React.CSSProperties}>{children}</D>
191
212
  }
192
213
 
214
+ export const Hr = ({ vertical, color = 'gray', grow, gap, p, ...rest }: DProps & { vertical?: boolean, color?: Color | 'gray' }) => {
215
+ const c = rcn(rest as DProps)
216
+ const border = color === 'gray' ? "border-zinc-700" : ({
217
+ red: "border-red-500",
218
+ blue: "border-blue-500",
219
+ orange: "border-orange-500",
220
+ purple: "border-purple-500",
221
+ yellow: "border-yellow-500",
222
+ green: "border-green-500",
223
+ } satisfies Record<Color, string>)[color]
224
+
225
+ return <div
226
+ className={CN(vertical ? "self-stretch border-l" : "w-full border-t", border, grow && "flex-1", c)}
227
+ style={dStyle({ gap, p, wd: rest.wd, style: rest.style })}
228
+ aria-hidden="true"
229
+ />
230
+ }
231
+
232
+ export const Progress = ({ value, color = 'blue', dot, grow, gap, p, ...rest }: DProps & { value: number, color?: Color, dot?: boolean }) => {
233
+ const c = rcn(rest as DProps)
234
+ const v = Math.max(0, Math.min(1, value))
235
+ const fill = color === 'red' ? 'bg-red-500'
236
+ : color === 'orange' ? 'bg-orange-500'
237
+ : color === 'purple' ? 'bg-purple-500'
238
+ : color === 'yellow' ? 'bg-yellow-500'
239
+ : color === 'green' ? 'bg-green-500'
240
+ : 'bg-blue-500'
241
+
242
+ if (dot) return <div
243
+ className={CN("rounded-full border border-zinc-700 bg-zinc-800 overflow-hidden", grow && "flex-1", c)}
244
+ style={dStyle({ gap, p, wd: rest.wd, style: { aspectRatio: '1 / 1', ...rest.style } })}
245
+ >
246
+ <div className={CN("h-full rounded-full", fill)} style={{ opacity: v ? 1 : 0.2 }} />
247
+ </div>
248
+
249
+ return <div
250
+ className={CN("h-2 overflow-hidden rounded-full bg-zinc-800 border border-zinc-700", grow && "flex-1", c)}
251
+ style={dStyle({ gap, p, wd: rest.wd, style: rest.style })}
252
+ >
253
+ <div className={CN("h-full rounded-full", fill)} style={{ width: `${v * 100}%` }} />
254
+ </div>
255
+ }
256
+
257
+ export const Dropzone = ({ children, onFiles, multiple, accept, click, ...rest }: DProps & { onFiles: (files: File[]) => void, multiple?: boolean, accept?: string, click?: () => void }) => {
258
+ const input = useRef<HTMLInputElement>(null)
259
+ const c = rcn(rest)
260
+ const push = (list: FileList | null) => {
261
+ const files = list ? Array.from(list) : []
262
+ if (files.length) onFiles(files)
263
+ }
264
+
265
+ return <div
266
+ className={CN("contents", c)}
267
+ onClick={e => {
268
+ click?.()
269
+ input.current?.click()
270
+ rest.onClick?.(e as any)
271
+ }}
272
+ onDragOver={e => {
273
+ e.preventDefault()
274
+ rest.onDragOver?.(e)
275
+ }}
276
+ onDrop={e => {
277
+ e.preventDefault()
278
+ push(e.dataTransfer.files)
279
+ rest.onDrop?.(e)
280
+ }}
281
+ >
282
+ <input
283
+ ref={input}
284
+ hidden
285
+ type="file"
286
+ multiple={multiple}
287
+ accept={accept}
288
+ onChange={e => push(e.target.files)}
289
+ />
290
+ {children}
291
+ </div>
292
+ }
293
+
193
294
  export const Code = (props: DProps & { highlight?: string }) => {
194
295
  const { children, highlight } = props
195
296
  const c = rcn(props)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "b44ui",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "index.tsx",
package/readme.md CHANGED
@@ -28,14 +28,19 @@ export default defineConfig({
28
28
  |-----------|-------|-------------|
29
29
  | `App` | `center`, `width`, `cn` | Root layout, dark background, wraps strings in `Md` |
30
30
  | `Centered` | `width`, `cn`, `grow` | Centered max-width container |
31
- | `D` | `cn`, `style`, `grow` | Plain div with `cn` |
32
- | `Row` | `align`, `ratio`, `cn`, `grow` | Flex row, `align`: `start \| mid \| end` |
33
- | `Col` | `cn`, `grow` | Flex column |
31
+ | `D` | `cn`, `style`, `grow`, `wd` | Plain div with `cn` |
32
+ | `Row` | `align`, `ratio`, `cn`, `grow`, `wd` | Flex row, `align`: `start \| mid \| end` |
33
+ | `Col` | `cn`, `grow`, `wd` | Flex column |
34
34
  | `Code` | `highlight` | Code block |
35
35
  | `Grid` | `cols`, `cn`, `grow` | CSS grid, defaults to one column per child |
36
36
  | `Card` | `cn`, `grow` | Bordered zinc-900 card |
37
37
  | `Block` | `label`, `row`, `dashed`, `cn`, `grow` | Padded container with optional label |
38
38
  | `BlockSm` | `dashed`, `cn`, `grow` | Smaller padded container |
39
+ | `TabList` | `gap`, `p`, `wd` | Simple tab row wrapper |
40
+ | `Tab` | `title`, `active`, `click`, `wd` | String-first tab primitive |
41
+ | `Hr` | `vertical`, `color`, `wd` | Horizontal or vertical divider |
42
+ | `Progress` | `value`, `color`, `dot`, `wd` | Progress bar or dot |
43
+ | `Dropzone` | `onFiles`, `multiple`, `accept` | Hidden file input wrapper for click/drop |
39
44
  | `Btn` | `click`, `color`, `ghost`, `cn`, `grow` | Button, supports `Color` and ghost style |
40
45
  | `A` | `href`, `click`, `cn`, `grow` | Link-styled anchor, works with `onClick` or `href` |
41
46
  | `Chip` | `click`, `cn` | Small inline badge, clickable if `click` provided |
@@ -48,6 +53,6 @@ export default defineConfig({
48
53
  | `Popover` | `text`, `color`, `cn` | Hover popover |
49
54
  | `Md` | `className` | Markdown renderer with syntax highlighting |
50
55
 
51
- `Color`: `red \| blue \| orange \| purple \| yellow`
56
+ `Color`: `red \| blue \| orange \| purple \| yellow \| green`
52
57
 
53
58
  All components accept `cn` for additional Tailwind classes (merged via `tailwind-merge`).