@birdycom/omniaui-react 0.12.5 → 0.12.6
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 +323 -35
- package/dist/index.js +2642 -1701
- package/manifest.json +90 -6
- package/package.json +1 -1
- package/src/blocks/BlockAppShell.tsx +1 -1
- package/src/blocks/BlockProfileHeader.tsx +20 -3
- package/src/components/Attachment.tsx +1 -1
- package/src/components/AvatarPicker.tsx +68 -0
- package/src/components/Barcode.tsx +42 -0
- package/src/components/Breadcrumb.tsx +8 -4
- package/src/components/Button.tsx +22 -7
- package/src/components/ChatComposer.tsx +1 -1
- package/src/components/DataTable.tsx +182 -33
- package/src/components/DateField.tsx +41 -0
- package/src/components/EmojiPicker.tsx +2 -2
- package/src/components/FormSection.tsx +32 -0
- package/src/components/Gallery.tsx +56 -0
- package/src/components/Glass.tsx +5 -3
- package/src/components/GlassBar.tsx +42 -0
- package/src/components/LazyImage.tsx +45 -0
- package/src/components/Message.tsx +7 -7
- package/src/components/MessageThread.tsx +10 -10
- package/src/components/MeterRow.tsx +38 -0
- package/src/components/NameCard.tsx +59 -0
- package/src/components/Price.tsx +45 -0
- package/src/components/Prose.tsx +3 -3
- package/src/components/Quantity.tsx +55 -0
- package/src/components/Rating.tsx +69 -0
- package/src/components/Ribbon.tsx +30 -0
- package/src/components/SpecList.tsx +36 -0
- package/src/components/Stock.tsx +28 -0
- package/src/index.ts +15 -1
- package/src/utils.ts +1 -1
- package/src/components/ColumnMenu.tsx +0 -66
package/manifest.json
CHANGED
|
@@ -48,6 +48,12 @@
|
|
|
48
48
|
"file": "src/components/Avatar.tsx",
|
|
49
49
|
"snippet": "@birdycom/omniaui/snippets/avatar.html"
|
|
50
50
|
},
|
|
51
|
+
{
|
|
52
|
+
"name": "avatar-picker",
|
|
53
|
+
"kind": "component",
|
|
54
|
+
"file": "src/components/AvatarPicker.tsx",
|
|
55
|
+
"snippet": "@birdycom/omniaui/snippets/avatar-picker.html"
|
|
56
|
+
},
|
|
51
57
|
{
|
|
52
58
|
"name": "back-link",
|
|
53
59
|
"kind": "component",
|
|
@@ -60,6 +66,12 @@
|
|
|
60
66
|
"file": "src/components/Badge.tsx",
|
|
61
67
|
"snippet": "@birdycom/omniaui/snippets/badge.html"
|
|
62
68
|
},
|
|
69
|
+
{
|
|
70
|
+
"name": "barcode",
|
|
71
|
+
"kind": "component",
|
|
72
|
+
"file": "src/components/Barcode.tsx",
|
|
73
|
+
"snippet": "@birdycom/omniaui/snippets/barcode.html"
|
|
74
|
+
},
|
|
63
75
|
{
|
|
64
76
|
"name": "barcode-input",
|
|
65
77
|
"kind": "component",
|
|
@@ -144,12 +156,6 @@
|
|
|
144
156
|
"file": "src/components/ColorSelect.tsx",
|
|
145
157
|
"snippet": "@birdycom/omniaui/snippets/color-select.html"
|
|
146
158
|
},
|
|
147
|
-
{
|
|
148
|
-
"name": "column-menu",
|
|
149
|
-
"kind": "component",
|
|
150
|
-
"file": "src/components/ColumnMenu.tsx",
|
|
151
|
-
"snippet": "@birdycom/omniaui/snippets/column-menu.html"
|
|
152
|
-
},
|
|
153
159
|
{
|
|
154
160
|
"name": "command",
|
|
155
161
|
"kind": "component",
|
|
@@ -186,6 +192,12 @@
|
|
|
186
192
|
"file": "src/components/DataTable.tsx",
|
|
187
193
|
"snippet": "@birdycom/omniaui/snippets/data-table.html"
|
|
188
194
|
},
|
|
195
|
+
{
|
|
196
|
+
"name": "date-field",
|
|
197
|
+
"kind": "component",
|
|
198
|
+
"file": "src/components/DateField.tsx",
|
|
199
|
+
"snippet": "@birdycom/omniaui/snippets/date-field.html"
|
|
200
|
+
},
|
|
189
201
|
{
|
|
190
202
|
"name": "decimal-input",
|
|
191
203
|
"kind": "component",
|
|
@@ -258,12 +270,30 @@
|
|
|
258
270
|
"file": "src/components/FormField.tsx",
|
|
259
271
|
"snippet": "@birdycom/omniaui/snippets/form-field.html"
|
|
260
272
|
},
|
|
273
|
+
{
|
|
274
|
+
"name": "form-section",
|
|
275
|
+
"kind": "component",
|
|
276
|
+
"file": "src/components/FormSection.tsx",
|
|
277
|
+
"snippet": "@birdycom/omniaui/snippets/form-section.html"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"name": "gallery",
|
|
281
|
+
"kind": "component",
|
|
282
|
+
"file": "src/components/Gallery.tsx",
|
|
283
|
+
"snippet": "@birdycom/omniaui/snippets/gallery.html"
|
|
284
|
+
},
|
|
261
285
|
{
|
|
262
286
|
"name": "glass",
|
|
263
287
|
"kind": "component",
|
|
264
288
|
"file": "src/components/Glass.tsx",
|
|
265
289
|
"snippet": "@birdycom/omniaui/snippets/glass.html"
|
|
266
290
|
},
|
|
291
|
+
{
|
|
292
|
+
"name": "glass-bar",
|
|
293
|
+
"kind": "component",
|
|
294
|
+
"file": "src/components/GlassBar.tsx",
|
|
295
|
+
"snippet": null
|
|
296
|
+
},
|
|
267
297
|
{
|
|
268
298
|
"name": "greeting",
|
|
269
299
|
"kind": "component",
|
|
@@ -312,6 +342,12 @@
|
|
|
312
342
|
"file": "src/components/LauncherCard.tsx",
|
|
313
343
|
"snippet": "@birdycom/omniaui/snippets/launcher-card.html"
|
|
314
344
|
},
|
|
345
|
+
{
|
|
346
|
+
"name": "lazy-image",
|
|
347
|
+
"kind": "component",
|
|
348
|
+
"file": "src/components/LazyImage.tsx",
|
|
349
|
+
"snippet": null
|
|
350
|
+
},
|
|
315
351
|
{
|
|
316
352
|
"name": "lightbox",
|
|
317
353
|
"kind": "component",
|
|
@@ -378,12 +414,24 @@
|
|
|
378
414
|
"file": "src/components/MessageThread.tsx",
|
|
379
415
|
"snippet": "@birdycom/omniaui/snippets/message-thread.html"
|
|
380
416
|
},
|
|
417
|
+
{
|
|
418
|
+
"name": "meter-row",
|
|
419
|
+
"kind": "component",
|
|
420
|
+
"file": "src/components/MeterRow.tsx",
|
|
421
|
+
"snippet": "@birdycom/omniaui/snippets/meter-row.html"
|
|
422
|
+
},
|
|
381
423
|
{
|
|
382
424
|
"name": "modal",
|
|
383
425
|
"kind": "component",
|
|
384
426
|
"file": "src/components/Modal.tsx",
|
|
385
427
|
"snippet": "@birdycom/omniaui/snippets/modal.html"
|
|
386
428
|
},
|
|
429
|
+
{
|
|
430
|
+
"name": "name-card",
|
|
431
|
+
"kind": "component",
|
|
432
|
+
"file": "src/components/NameCard.tsx",
|
|
433
|
+
"snippet": "@birdycom/omniaui/snippets/name-card.html"
|
|
434
|
+
},
|
|
387
435
|
{
|
|
388
436
|
"name": "navmenu",
|
|
389
437
|
"kind": "component",
|
|
@@ -444,6 +492,12 @@
|
|
|
444
492
|
"file": "src/components/Popover.tsx",
|
|
445
493
|
"snippet": "@birdycom/omniaui/snippets/popover.html"
|
|
446
494
|
},
|
|
495
|
+
{
|
|
496
|
+
"name": "price",
|
|
497
|
+
"kind": "component",
|
|
498
|
+
"file": "src/components/Price.tsx",
|
|
499
|
+
"snippet": "@birdycom/omniaui/snippets/price.html"
|
|
500
|
+
},
|
|
447
501
|
{
|
|
448
502
|
"name": "price-card",
|
|
449
503
|
"kind": "component",
|
|
@@ -474,12 +528,30 @@
|
|
|
474
528
|
"file": "src/components/Prose.tsx",
|
|
475
529
|
"snippet": "@birdycom/omniaui/snippets/prose.html"
|
|
476
530
|
},
|
|
531
|
+
{
|
|
532
|
+
"name": "quantity",
|
|
533
|
+
"kind": "component",
|
|
534
|
+
"file": "src/components/Quantity.tsx",
|
|
535
|
+
"snippet": "@birdycom/omniaui/snippets/quantity.html"
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
"name": "rating",
|
|
539
|
+
"kind": "component",
|
|
540
|
+
"file": "src/components/Rating.tsx",
|
|
541
|
+
"snippet": "@birdycom/omniaui/snippets/rating.html"
|
|
542
|
+
},
|
|
477
543
|
{
|
|
478
544
|
"name": "recovery-codes",
|
|
479
545
|
"kind": "component",
|
|
480
546
|
"file": "src/components/RecoveryCodes.tsx",
|
|
481
547
|
"snippet": "@birdycom/omniaui/snippets/recovery-codes.html"
|
|
482
548
|
},
|
|
549
|
+
{
|
|
550
|
+
"name": "ribbon",
|
|
551
|
+
"kind": "component",
|
|
552
|
+
"file": "src/components/Ribbon.tsx",
|
|
553
|
+
"snippet": "@birdycom/omniaui/snippets/ribbon.html"
|
|
554
|
+
},
|
|
483
555
|
{
|
|
484
556
|
"name": "segmented",
|
|
485
557
|
"kind": "component",
|
|
@@ -522,6 +594,12 @@
|
|
|
522
594
|
"file": "src/components/SortableRow.tsx",
|
|
523
595
|
"snippet": "@birdycom/omniaui/snippets/sortable-row.html"
|
|
524
596
|
},
|
|
597
|
+
{
|
|
598
|
+
"name": "spec-list",
|
|
599
|
+
"kind": "component",
|
|
600
|
+
"file": "src/components/SpecList.tsx",
|
|
601
|
+
"snippet": "@birdycom/omniaui/snippets/spec-list.html"
|
|
602
|
+
},
|
|
525
603
|
{
|
|
526
604
|
"name": "stack-card",
|
|
527
605
|
"kind": "component",
|
|
@@ -552,6 +630,12 @@
|
|
|
552
630
|
"file": "src/components/Stepper.tsx",
|
|
553
631
|
"snippet": "@birdycom/omniaui/snippets/stepper.html"
|
|
554
632
|
},
|
|
633
|
+
{
|
|
634
|
+
"name": "stock",
|
|
635
|
+
"kind": "component",
|
|
636
|
+
"file": "src/components/Stock.tsx",
|
|
637
|
+
"snippet": "@birdycom/omniaui/snippets/stock.html"
|
|
638
|
+
},
|
|
555
639
|
{
|
|
556
640
|
"name": "switch",
|
|
557
641
|
"kind": "component",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@birdycom/omniaui-react",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.6",
|
|
4
4
|
"description": "OmniaUI for React: typed function components and page blocks that render the exact @birdycom/omniaui and @birdycom/omniaui-blocks markup.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,7 +37,7 @@ export const BlockAppShell = forwardRef<HTMLDivElement, BlockAppShellProps>(func
|
|
|
37
37
|
{breadcrumbs && (
|
|
38
38
|
<div className="oc-block-shell-breadcrumbs">
|
|
39
39
|
<nav aria-label="Breadcrumb">
|
|
40
|
-
<ol className="oc-breadcrumb">
|
|
40
|
+
<ol className="oc-breadcrumb oc-breadcrumb--chevron">
|
|
41
41
|
{breadcrumbs.map((item, i) =>
|
|
42
42
|
item.href && i < breadcrumbs.length - 1 ? (
|
|
43
43
|
<li className="oc-breadcrumb-item" key={i}><a href={item.href}>{item.label}</a></li>
|
|
@@ -5,8 +5,12 @@ import { cx } from "../utils";
|
|
|
5
5
|
|
|
6
6
|
export interface ProfileHeaderTab {
|
|
7
7
|
label: ReactNode;
|
|
8
|
-
|
|
8
|
+
/** A link per tab (a route in your app); leave out and give content for tabs on the page itself. */
|
|
9
|
+
href?: string;
|
|
10
|
+
/** The open tab (aria-current for links, checked for tabs with content). */
|
|
9
11
|
current?: boolean;
|
|
12
|
+
/** Panel content: with content the tabs switch on the page itself (radios + panels, CSS only). */
|
|
13
|
+
content?: ReactNode;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
export interface BlockProfileHeaderProps extends HTMLAttributes<HTMLElement> {
|
|
@@ -25,13 +29,14 @@ export interface BlockProfileHeaderProps extends HTMLAttributes<HTMLElement> {
|
|
|
25
29
|
headingLevel?: 1 | 2;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
/** `.oc-block-profile`: banner, overhanging avatar, name, meta, actions and
|
|
32
|
+
/** `.oc-block-profile`: banner, overhanging avatar, name, meta, actions and tabs (links, or panels on the page). */
|
|
29
33
|
export const BlockProfileHeader = forwardRef<HTMLElement, BlockProfileHeaderProps>(function BlockProfileHeader(
|
|
30
34
|
{ name, meta, bio, avatar, banner, actions, tabs = [], tabsLabel = "Profile sections", compact = false, headingLevel = 1, className, ...rest },
|
|
31
35
|
ref,
|
|
32
36
|
) {
|
|
33
37
|
const nameId = `oc-profile-name-${useId().replace(/[^\w-]/g, "")}`;
|
|
34
38
|
const Heading = headingLevel === 1 ? "h1" : "h2";
|
|
39
|
+
const panels = tabs.some((tab) => tab.content != null);
|
|
35
40
|
const bannerStyle = banner ? ({ "--oc-profile-banner": `url("${banner}")` } as CSSProperties) : undefined;
|
|
36
41
|
return (
|
|
37
42
|
<section ref={ref} className={cx("oc-block-profile", compact && "oc-block-profile--compact", className)} aria-labelledby={nameId} {...rest}>
|
|
@@ -45,7 +50,19 @@ export const BlockProfileHeader = forwardRef<HTMLElement, BlockProfileHeaderProp
|
|
|
45
50
|
</div>
|
|
46
51
|
{actions != null && <div className="oc-block-profile-actions">{actions}</div>}
|
|
47
52
|
</div>
|
|
48
|
-
{tabs.length > 0 && (
|
|
53
|
+
{tabs.length > 0 && panels && (
|
|
54
|
+
<>
|
|
55
|
+
<div className="oc-block-profile-tabs" role="radiogroup" aria-label={tabsLabel}>
|
|
56
|
+
{tabs.map((tab, i) => (
|
|
57
|
+
<label key={i} className="oc-block-profile-tab"><input type="radio" name={`${nameId}-tab`} defaultChecked={tab.current ?? i === 0} />{tab.label}</label>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
<div className="oc-block-profile-panels">
|
|
61
|
+
{tabs.map((tab, i) => <div key={i} className="oc-block-profile-panel">{tab.content}</div>)}
|
|
62
|
+
</div>
|
|
63
|
+
</>
|
|
64
|
+
)}
|
|
65
|
+
{tabs.length > 0 && !panels && (
|
|
49
66
|
<nav className="oc-block-profile-tabs" aria-label={tabsLabel}>
|
|
50
67
|
{tabs.map((tab, i) => (
|
|
51
68
|
<a key={i} className="oc-block-profile-tab" href={tab.href} aria-current={tab.current ? "page" : undefined}>{tab.label}</a>
|
|
@@ -124,7 +124,7 @@ export interface AttachmentListProps extends HTMLAttributes<HTMLElement> {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
const DEMO: AttachmentFile[] = [
|
|
127
|
-
{ name: "Quote kitchen
|
|
127
|
+
{ name: "Quote kitchen Project 1 v2.pdf", size: "1.2 MB", href: "#quote.pdf" },
|
|
128
128
|
{ name: "Measurements ground floor.xlsx", size: "86 KB", href: "#measurements.xlsx" },
|
|
129
129
|
{ name: "Contract addendum.docx", size: "240 KB", href: "#contract.docx" },
|
|
130
130
|
{ name: "Plans architect.zip", size: "14.8 MB", href: "#plans.zip" },
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { forwardRef, useEffect, useId, useRef } from "react";
|
|
4
|
+
import type { HTMLAttributes, InputHTMLAttributes } from "react";
|
|
5
|
+
import { bindAvatarPicker } from "../../../ui/src/form-extras.js";
|
|
6
|
+
import { cx } from "../utils";
|
|
7
|
+
import { Icon } from "./Icon";
|
|
8
|
+
|
|
9
|
+
export interface AvatarPickerProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "onInvalid"> {
|
|
10
|
+
initials: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
hint?: string;
|
|
13
|
+
/** Rounded square and contain-fit (a company logo). */
|
|
14
|
+
logo?: boolean;
|
|
15
|
+
/** Name of the file input (sent with the form). */
|
|
16
|
+
name?: string;
|
|
17
|
+
accept?: string;
|
|
18
|
+
/** Refuse larger files (KB); the reason shows under the buttons. */
|
|
19
|
+
maxKb?: number;
|
|
20
|
+
inputProps?: InputHTMLAttributes<HTMLInputElement>;
|
|
21
|
+
onChange?: (file: File | null) => void;
|
|
22
|
+
/** A refused file (wrong type for accept, or larger than maxKb) and the reason. */
|
|
23
|
+
onInvalid?: (file: File, reason: string) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Avatar or logo picker with a preview, Upload and Remove, and drag and drop (`.oc-avatar-picker`). */
|
|
27
|
+
export const AvatarPicker = forwardRef<HTMLDivElement, AvatarPickerProps>(function AvatarPicker(
|
|
28
|
+
{ initials, label = "Profile picture", hint, logo, name, accept = "image/*", maxKb, inputProps, onChange, onInvalid, className, ...rest },
|
|
29
|
+
ref,
|
|
30
|
+
) {
|
|
31
|
+
const id = useId();
|
|
32
|
+
const own = useRef<HTMLDivElement | null>(null);
|
|
33
|
+
const changeRef = useRef(onChange);
|
|
34
|
+
changeRef.current = onChange;
|
|
35
|
+
const invalidRef = useRef(onInvalid);
|
|
36
|
+
invalidRef.current = onInvalid;
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const el = own.current;
|
|
39
|
+
if (!el) return;
|
|
40
|
+
bindAvatarPicker(el);
|
|
41
|
+
const handler = (event: Event) => changeRef.current?.((event as CustomEvent<{ file: File | null }>).detail.file);
|
|
42
|
+
const refused = (event: Event) => { const { file, reason } = (event as CustomEvent<{ file: File; reason: string }>).detail; invalidRef.current?.(file, reason); };
|
|
43
|
+
el.addEventListener("oc-avatar-change", handler);
|
|
44
|
+
el.addEventListener("oc-avatar-invalid", refused);
|
|
45
|
+
return () => { el.removeEventListener("oc-avatar-change", handler); el.removeEventListener("oc-avatar-invalid", refused); };
|
|
46
|
+
}, []);
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
ref={(node) => { own.current = node; if (typeof ref === "function") ref(node); else if (ref) ref.current = node; }}
|
|
50
|
+
className={cx("oc-avatar-picker", logo && "oc-avatar-picker--square oc-avatar-picker--logo", className)}
|
|
51
|
+
data-oc-max-kb={maxKb}
|
|
52
|
+
{...rest}
|
|
53
|
+
>
|
|
54
|
+
<label className="oc-avatar-picker__preview" htmlFor={id} title="Choose an image">
|
|
55
|
+
<span data-oc-initials="">{initials}</span>
|
|
56
|
+
<input className="oc-avatar-picker__file" id={id} type="file" accept={accept} name={name} {...inputProps} />
|
|
57
|
+
</label>
|
|
58
|
+
<div className="oc-avatar-picker__text">
|
|
59
|
+
<span className="oc-avatar-picker__label">{label}</span>
|
|
60
|
+
{hint && <span className="oc-avatar-picker__hint">{hint}</span>}
|
|
61
|
+
<div className="oc-avatar-picker__actions">
|
|
62
|
+
<button type="button" className="oc-btn oc-btn--outline oc-btn--sm" data-oc-avatar-upload=""><Icon d="M9 16v-6H5l7-7l7 7h-4v6zm-4 4v-2h14v2z" />Upload</button>
|
|
63
|
+
<button type="button" className="oc-btn oc-btn--ghost oc-btn--sm" data-oc-avatar-remove=""><Icon d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z" />Remove</button>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { forwardRef, useMemo } from "react";
|
|
4
|
+
import type { HTMLAttributes } from "react";
|
|
5
|
+
import { barsToSvg, encodeBarcode } from "../../../ui/src/barcode.js";
|
|
6
|
+
import { cx } from "../utils";
|
|
7
|
+
|
|
8
|
+
export interface BarcodeProps extends HTMLAttributes<HTMLElement> {
|
|
9
|
+
value: string;
|
|
10
|
+
format?: "auto" | "EAN13" | "EAN8" | "UPC" | "CODE128";
|
|
11
|
+
/** Digits under the bars. Default true. */
|
|
12
|
+
digits?: boolean;
|
|
13
|
+
size?: "md" | "hero" | "compact";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** EAN-13, EAN-8, UPC-A or Code 128 as SVG (`figure.oc-barcode`); shows the reason when the value cannot be encoded. */
|
|
17
|
+
export const Barcode = forwardRef<HTMLElement, BarcodeProps>(function Barcode(
|
|
18
|
+
{ value, format = "auto", digits = true, size = "md", className, ...rest },
|
|
19
|
+
ref,
|
|
20
|
+
) {
|
|
21
|
+
const result = useMemo(() => {
|
|
22
|
+
try {
|
|
23
|
+
const { bits, text } = encodeBarcode(value, format);
|
|
24
|
+
return { svg: barsToSvg(bits, `Barcode ${text}`), text, error: "" };
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return { svg: "", text: value, error: (err as Error).message };
|
|
27
|
+
}
|
|
28
|
+
}, [value, format]);
|
|
29
|
+
return (
|
|
30
|
+
<figure
|
|
31
|
+
ref={ref}
|
|
32
|
+
className={cx("oc-barcode", size !== "md" && `oc-barcode--${size}`, className)}
|
|
33
|
+
data-error={result.error ? "" : undefined}
|
|
34
|
+
{...rest}
|
|
35
|
+
>
|
|
36
|
+
{result.svg && <span style={{ display: "contents" }} dangerouslySetInnerHTML={{ __html: result.svg }} />}
|
|
37
|
+
{(digits || result.error) && (
|
|
38
|
+
<figcaption className="oc-barcode__digits">{result.error ? `${value}: ${result.error}` : result.text}</figcaption>
|
|
39
|
+
)}
|
|
40
|
+
</figure>
|
|
41
|
+
);
|
|
42
|
+
});
|
|
@@ -6,24 +6,28 @@ export interface BreadcrumbItemData {
|
|
|
6
6
|
label: ReactNode;
|
|
7
7
|
/** Omit (or put last) to mark the current page. */
|
|
8
8
|
href?: string;
|
|
9
|
+
/** Shown before the label: an `<iconify-icon icon="mdi:…" />`, an `<Icon d=… />` or any node. */
|
|
10
|
+
icon?: ReactNode;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export interface BreadcrumbProps extends HTMLAttributes<HTMLElement> {
|
|
12
14
|
items?: BreadcrumbItemData[];
|
|
15
|
+
/** `"slash"` (default) or `"chevron"` (›) between the items. */
|
|
16
|
+
separator?: "slash" | "chevron";
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
/** `<nav class="oc-breadcrumb">` with links and a final `aria-current="page"` item. */
|
|
16
20
|
export const Breadcrumb = forwardRef<HTMLElement, BreadcrumbProps>(function Breadcrumb(
|
|
17
|
-
{ items, children, className, "aria-label": ariaLabel = "Breadcrumb", ...rest },
|
|
21
|
+
{ items, separator = "slash", children, className, "aria-label": ariaLabel = "Breadcrumb", ...rest },
|
|
18
22
|
ref,
|
|
19
23
|
) {
|
|
20
24
|
return (
|
|
21
|
-
<nav ref={ref} className={cx("oc-breadcrumb", className)} aria-label={ariaLabel} {...rest}>
|
|
25
|
+
<nav ref={ref} className={cx("oc-breadcrumb", separator === "chevron" && "oc-breadcrumb--chevron", className)} aria-label={ariaLabel} {...rest}>
|
|
22
26
|
{items?.map((item, i) =>
|
|
23
27
|
item.href && i < items.length - 1 ? (
|
|
24
|
-
<a key={i} className="oc-breadcrumb-item" href={item.href}>{item.label}</a>
|
|
28
|
+
<a key={i} className="oc-breadcrumb-item" href={item.href}>{item.icon}{item.label}</a>
|
|
25
29
|
) : (
|
|
26
|
-
<span key={i} className="oc-breadcrumb-item" aria-current="page">{item.label}</span>
|
|
30
|
+
<span key={i} className="oc-breadcrumb-item" aria-current="page">{item.icon}{item.label}</span>
|
|
27
31
|
),
|
|
28
32
|
)}
|
|
29
33
|
{children}
|
|
@@ -2,7 +2,9 @@ import { forwardRef } from "react";
|
|
|
2
2
|
import type { AnchorHTMLAttributes, ButtonHTMLAttributes } from "react";
|
|
3
3
|
import { cx } from "../utils";
|
|
4
4
|
|
|
5
|
-
export type ButtonVariant =
|
|
5
|
+
export type ButtonVariant =
|
|
6
|
+
| "default" | "secondary" | "outline" | "ghost" | "link" | "destructive" | "success" | "gradient" | "glass"
|
|
7
|
+
| "soft-primary" | "soft-success" | "soft-warning" | "soft-destructive" | "soft-info" | "plain";
|
|
6
8
|
export type ButtonSize = "xs" | "sm" | "md" | "lg";
|
|
7
9
|
|
|
8
10
|
interface ButtonStyleProps {
|
|
@@ -18,6 +20,11 @@ interface ButtonStyleProps {
|
|
|
18
20
|
inline?: boolean;
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
interface LoadingProp {
|
|
24
|
+
/** Busy: a spinner replaces the leading icon and the button cannot be clicked (`aria-busy`). */
|
|
25
|
+
loading?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
/** The class string of a button; reuse it on your own elements (router links etc.). */
|
|
22
29
|
export function buttonClass({ variant = "default", size = "md", icon, pill, inline, className }: ButtonStyleProps & { className?: string }) {
|
|
23
30
|
return cx(
|
|
@@ -31,22 +38,30 @@ export function buttonClass({ variant = "default", size = "md", icon, pill, inli
|
|
|
31
38
|
);
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, ButtonStyleProps {}
|
|
41
|
+
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, ButtonStyleProps, LoadingProp {}
|
|
35
42
|
|
|
36
43
|
/** `<button class="oc-btn oc-btn--{variant} oc-btn--{size}">` */
|
|
37
44
|
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
38
|
-
{ variant, size, icon, pill, inline, className, ...rest },
|
|
45
|
+
{ variant, size, icon, pill, inline, loading, disabled, className, ...rest },
|
|
39
46
|
ref,
|
|
40
47
|
) {
|
|
41
|
-
return
|
|
48
|
+
return (
|
|
49
|
+
<button
|
|
50
|
+
ref={ref}
|
|
51
|
+
className={buttonClass({ variant, size, icon, pill, inline, className })}
|
|
52
|
+
aria-busy={loading || undefined}
|
|
53
|
+
disabled={disabled || loading}
|
|
54
|
+
{...rest}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
42
57
|
});
|
|
43
58
|
|
|
44
|
-
export interface ButtonLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement>, ButtonStyleProps {}
|
|
59
|
+
export interface ButtonLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement>, ButtonStyleProps, LoadingProp {}
|
|
45
60
|
|
|
46
61
|
/** An anchor styled as a button. */
|
|
47
62
|
export const ButtonLink = forwardRef<HTMLAnchorElement, ButtonLinkProps>(function ButtonLink(
|
|
48
|
-
{ variant, size, icon, pill, inline, className, ...rest },
|
|
63
|
+
{ variant, size, icon, pill, inline, loading, className, ...rest },
|
|
49
64
|
ref,
|
|
50
65
|
) {
|
|
51
|
-
return <a ref={ref} className={buttonClass({ variant, size, icon, pill, inline, className })} {...rest} />;
|
|
66
|
+
return <a ref={ref} className={buttonClass({ variant, size, icon, pill, inline, className })} aria-busy={loading || undefined} {...rest} />;
|
|
52
67
|
});
|
|
@@ -66,7 +66,7 @@ export const ChatComposer = forwardRef<HTMLFormElement, ChatComposerProps>(funct
|
|
|
66
66
|
<div className="oc-chat-composer__bar">
|
|
67
67
|
{tools != null && <div className="oc-chat-composer__tools">{tools}</div>}
|
|
68
68
|
{hint != null && <span className="oc-chat-composer__hint">{hint}</span>}
|
|
69
|
-
<button type="submit" className="oc-chat-composer__send" aria-label="Send" disabled={disabled}
|
|
69
|
+
<button type="submit" className="oc-chat-composer__send" aria-label="Send" disabled={disabled}><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M2 21l21-9L2 3v7l15 2l-15 2z"/></svg></button>
|
|
70
70
|
</div>
|
|
71
71
|
</form>
|
|
72
72
|
);
|