@birdycom/omniaui-react 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +21 -21
- package/package.json +71 -71
- package/src/blocks/BlockAboutTimeline.tsx +46 -46
- package/src/blocks/BlockAppHeader.tsx +52 -52
- package/src/blocks/BlockAppShell.tsx +56 -56
- package/src/blocks/BlockAuth.tsx +53 -53
- package/src/blocks/BlockAuthSplit.tsx +84 -84
- package/src/blocks/BlockContactGrid.tsx +71 -71
- package/src/blocks/BlockCta.tsx +23 -23
- package/src/blocks/BlockDataTable.tsx +57 -57
- package/src/blocks/BlockFaq.tsx +55 -55
- package/src/blocks/BlockFeatures.tsx +38 -38
- package/src/blocks/BlockFooter.tsx +50 -50
- package/src/blocks/BlockFormPage.tsx +26 -26
- package/src/blocks/BlockHero.tsx +27 -27
- package/src/blocks/BlockHeroCode.tsx +63 -63
- package/src/blocks/BlockHeroOrbit.tsx +73 -73
- package/src/blocks/BlockHeroSplit.tsx +28 -28
- package/src/blocks/BlockModuleHeader.tsx +34 -34
- package/src/blocks/BlockPageHero.tsx +25 -25
- package/src/blocks/BlockPortfolioGrid.tsx +50 -50
- package/src/blocks/BlockPricing.tsx +62 -62
- package/src/blocks/BlockProcess.tsx +50 -50
- package/src/blocks/BlockProductGrid.tsx +54 -54
- package/src/blocks/BlockProfileHeader.tsx +57 -57
- package/src/blocks/BlockShowcase.tsx +58 -58
- package/src/blocks/BlockSiteFooter.tsx +60 -60
- package/src/blocks/BlockSiteHeader.tsx +42 -42
- package/src/blocks/BlockSpotlight.tsx +58 -58
- package/src/blocks/BlockStats.tsx +26 -26
- package/src/blocks/BlockStatsCounter.tsx +31 -31
- package/src/blocks/BlockTeamList.tsx +48 -48
- package/src/blocks/BlockTwoFactor.tsx +57 -57
- package/src/blocks/BlockWizardDialog.tsx +101 -101
- package/src/components/Accordion.tsx +31 -31
- package/src/components/AccordionAccent.tsx +39 -39
- package/src/components/AffixInput.tsx +47 -47
- package/src/components/Alert.tsx +24 -24
- package/src/components/AlertDialog.tsx +60 -60
- package/src/components/AlphaTabs.tsx +63 -63
- package/src/components/Attachment.tsx +140 -140
- package/src/components/Avatar.tsx +32 -32
- package/src/components/BackLink.tsx +11 -11
- package/src/components/Badge.tsx +14 -14
- package/src/components/BarcodeInput.tsx +64 -64
- package/src/components/Breadcrumb.tsx +32 -32
- package/src/components/BrowserFrame.tsx +48 -48
- package/src/components/BusyOverlay.tsx +52 -52
- package/src/components/Button.tsx +49 -49
- package/src/components/Card.tsx +27 -27
- package/src/components/ChartLine.tsx +119 -119
- package/src/components/ChatComposer.tsx +73 -73
- package/src/components/Checkbox.tsx +14 -14
- package/src/components/Checklist.tsx +45 -45
- package/src/components/ChipFilter.tsx +60 -60
- package/src/components/Collapsible.tsx +43 -43
- package/src/components/ColorField.tsx +48 -48
- package/src/components/ColorSelect.tsx +49 -49
- package/src/components/ColumnMenu.tsx +66 -66
- package/src/components/Command.tsx +113 -113
- package/src/components/ConfirmDialog.tsx +95 -95
- package/src/components/CookieConsent.tsx +137 -137
- package/src/components/CopyField.tsx +44 -44
- package/src/components/Counter.tsx +21 -21
- package/src/components/DataTable.tsx +201 -201
- package/src/components/DecimalInput.tsx +87 -87
- package/src/components/DefinitionList.tsx +38 -38
- package/src/components/DocViewer.tsx +110 -110
- package/src/components/Dropdown.tsx +52 -52
- package/src/components/Dropzone.tsx +46 -46
- package/src/components/EmojiPicker.tsx +72 -72
- package/src/components/EmptyState.tsx +27 -27
- package/src/components/EntityCard.tsx +56 -56
- package/src/components/FactList.tsx +43 -43
- package/src/components/FilterCounts.tsx +50 -50
- package/src/components/FlipCard.tsx +55 -55
- package/src/components/FormField.tsx +54 -54
- package/src/components/Greeting.tsx +63 -63
- package/src/components/Heading.tsx +27 -27
- package/src/components/Icon.tsx +25 -25
- package/src/components/ImageZoom.tsx +51 -51
- package/src/components/Input.tsx +21 -21
- package/src/components/InputGroup.tsx +24 -24
- package/src/components/LanguageSelect.tsx +64 -64
- package/src/components/LauncherCard.tsx +50 -50
- package/src/components/Lightbox.tsx +83 -83
- package/src/components/LinkArrow.tsx +27 -27
- package/src/components/ListToolbar.tsx +67 -67
- package/src/components/Marquee.tsx +38 -38
- package/src/components/MediaCard.tsx +47 -47
- package/src/components/MediaRow.tsx +53 -53
- package/src/components/MediaThumb.tsx +49 -49
- package/src/components/Message.tsx +171 -171
- package/src/components/MessageThread.tsx +177 -177
- package/src/components/Modal.tsx +81 -81
- package/src/components/Navmenu.tsx +62 -62
- package/src/components/NextTeaser.tsx +29 -29
- package/src/components/NumberInput.tsx +64 -64
- package/src/components/OtpInput.tsx +48 -48
- package/src/components/PageHeader.tsx +43 -43
- package/src/components/Pagination.tsx +42 -42
- package/src/components/PasswordInput.tsx +36 -36
- package/src/components/PasswordRequirements.tsx +46 -46
- package/src/components/PhoneInput.tsx +93 -93
- package/src/components/Popover.tsx +34 -34
- package/src/components/PriceCard.tsx +83 -83
- package/src/components/ProductMark.tsx +35 -35
- package/src/components/Progress.tsx +32 -32
- package/src/components/ProjectCard.tsx +46 -46
- package/src/components/Prose.tsx +79 -79
- package/src/components/RecoveryCodes.tsx +65 -65
- package/src/components/Segmented.tsx +47 -47
- package/src/components/Select.tsx +27 -27
- package/src/components/SeverityCard.tsx +53 -53
- package/src/components/Sheet.tsx +62 -62
- package/src/components/Skeleton.tsx +12 -12
- package/src/components/SortableRow.tsx +46 -46
- package/src/components/StackCard.tsx +40 -40
- package/src/components/StackList.tsx +42 -42
- package/src/components/StatCard.tsx +49 -49
- package/src/components/Stepper.tsx +54 -54
- package/src/components/Switch.tsx +18 -18
- package/src/components/Table.tsx +75 -75
- package/src/components/TableActions.tsx +54 -54
- package/src/components/TableCompact.tsx +61 -61
- package/src/components/Tabs.tsx +45 -45
- package/src/components/TagInput.tsx +73 -73
- package/src/components/TerminalLog.tsx +63 -63
- package/src/components/Textarea.tsx +19 -19
- package/src/components/ThemeSwitch.tsx +45 -45
- package/src/components/TimeInput.tsx +76 -76
- package/src/components/Timeline.tsx +45 -45
- package/src/components/Toast.tsx +138 -138
- package/src/components/ToggleField.tsx +39 -39
- package/src/components/Tooltip.tsx +14 -14
- package/src/components/TopProgress.tsx +54 -54
- package/src/components/Tree.tsx +97 -97
- package/src/components/UpgradeBanner.tsx +62 -62
- package/src/components/User.tsx +31 -31
- package/src/components/VersionBanner.tsx +41 -41
- package/src/hooks.tsx +141 -141
- package/src/utils.ts +25 -25
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
3
|
-
import { cx } from "../utils";
|
|
4
|
-
|
|
5
|
-
export interface DefinitionItem {
|
|
6
|
-
term: ReactNode;
|
|
7
|
-
detail: ReactNode;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface DefinitionListProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
11
|
-
title?: ReactNode;
|
|
12
|
-
description?: ReactNode;
|
|
13
|
-
items: DefinitionItem[];
|
|
14
|
-
/** Optional footer (actions). */
|
|
15
|
-
footer?: ReactNode;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** `<div class="oc-dl-panel" role="region">` with a `dl` of label / value rows. */
|
|
19
|
-
export const DefinitionList = forwardRef<HTMLDivElement, DefinitionListProps>(function DefinitionList(
|
|
20
|
-
{ title, description, items, footer, className, ...rest },
|
|
21
|
-
ref,
|
|
22
|
-
) {
|
|
23
|
-
return (
|
|
24
|
-
<div ref={ref} className={cx("oc-dl-panel", className)} role="region" {...rest}>
|
|
25
|
-
{title != null && <h3 className="oc-dl-title">{title}</h3>}
|
|
26
|
-
{description != null && <p className="oc-dl-description">{description}</p>}
|
|
27
|
-
<dl className="oc-dl">
|
|
28
|
-
{items.map((item, i) => (
|
|
29
|
-
<div className="oc-dl-row" key={i}>
|
|
30
|
-
<dt className="oc-dl-term">{item.term}</dt>
|
|
31
|
-
<dd className="oc-dl-detail">{item.detail}</dd>
|
|
32
|
-
</div>
|
|
33
|
-
))}
|
|
34
|
-
</dl>
|
|
35
|
-
{footer != null && <div className="oc-dl-footer">{footer}</div>}
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
});
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
3
|
+
import { cx } from "../utils";
|
|
4
|
+
|
|
5
|
+
export interface DefinitionItem {
|
|
6
|
+
term: ReactNode;
|
|
7
|
+
detail: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface DefinitionListProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
11
|
+
title?: ReactNode;
|
|
12
|
+
description?: ReactNode;
|
|
13
|
+
items: DefinitionItem[];
|
|
14
|
+
/** Optional footer (actions). */
|
|
15
|
+
footer?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** `<div class="oc-dl-panel" role="region">` with a `dl` of label / value rows. */
|
|
19
|
+
export const DefinitionList = forwardRef<HTMLDivElement, DefinitionListProps>(function DefinitionList(
|
|
20
|
+
{ title, description, items, footer, className, ...rest },
|
|
21
|
+
ref,
|
|
22
|
+
) {
|
|
23
|
+
return (
|
|
24
|
+
<div ref={ref} className={cx("oc-dl-panel", className)} role="region" {...rest}>
|
|
25
|
+
{title != null && <h3 className="oc-dl-title">{title}</h3>}
|
|
26
|
+
{description != null && <p className="oc-dl-description">{description}</p>}
|
|
27
|
+
<dl className="oc-dl">
|
|
28
|
+
{items.map((item, i) => (
|
|
29
|
+
<div className="oc-dl-row" key={i}>
|
|
30
|
+
<dt className="oc-dl-term">{item.term}</dt>
|
|
31
|
+
<dd className="oc-dl-detail">{item.detail}</dd>
|
|
32
|
+
</div>
|
|
33
|
+
))}
|
|
34
|
+
</dl>
|
|
35
|
+
{footer != null && <div className="oc-dl-footer">{footer}</div>}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
});
|
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
// File: packages/react/src/components/DocViewer.tsx
|
|
2
|
-
// Map: types + demo items -> media (iframe / video / img / placeholder art) -> DocViewer (grid of cards -> one <dialog> per card)
|
|
3
|
-
// Same markup as packages/ui/snippets/doc-viewer.html. No JavaScript: the buttons open the full-screen dialog with
|
|
4
|
-
// invoker commands (command="show-modal" commandfor), closedby="any" closes it on Esc or a click on the backdrop.
|
|
5
|
-
import { forwardRef, useId } from "react";
|
|
6
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
7
|
-
import { attrs, cx } from "../utils";
|
|
8
|
-
|
|
9
|
-
export interface DocViewerItem {
|
|
10
|
-
/** Name in the bar and the dialog title; also used in the aria-labels. */
|
|
11
|
-
label: string;
|
|
12
|
-
/** Small text next to the label, e.g. "PDF · 2.4 MB" or "1:42". */
|
|
13
|
-
meta?: ReactNode;
|
|
14
|
-
/** File URL. Leave empty to show placeholder art (`.oc-media-art`). */
|
|
15
|
-
src?: string;
|
|
16
|
-
/** Default "pdf" (A4 frame). "video" = 16/9 dark frame, "image" = natural height. */
|
|
17
|
-
type?: "pdf" | "video" | "image";
|
|
18
|
-
/** Dialog id (generated when empty). */
|
|
19
|
-
id?: string;
|
|
20
|
-
/** Text for the inline preview (aria-label / alt / iframe title). Defaults to the label. */
|
|
21
|
-
alt?: string;
|
|
22
|
-
/** Placeholder art variant (`oc-media-art--N`) when there is no src. */
|
|
23
|
-
art?: number;
|
|
24
|
-
/** Download link in the dialog bar. */
|
|
25
|
-
download?: string;
|
|
26
|
-
/** Where the preview opens: "bar" (expand button) or "frame" (the whole preview). Default: "frame" for video. */
|
|
27
|
-
open?: "bar" | "frame";
|
|
28
|
-
/** Set false for a plain card without full-screen dialog. Default true. */
|
|
29
|
-
fullscreen?: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface DocViewerProps extends HTMLAttributes<HTMLDivElement> {
|
|
33
|
-
items?: DocViewerItem[];
|
|
34
|
-
/** Text of the frame button (default "Click for full screen"). */
|
|
35
|
-
openText?: ReactNode;
|
|
36
|
-
downloadText?: ReactNode;
|
|
37
|
-
/** Prefix for generated dialog ids. */
|
|
38
|
-
idPrefix?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const DEMO: DocViewerItem[] = [
|
|
42
|
-
{ label: "Brochure 2026", meta: "PDF · 2.4 MB", alt: "First page of Brochure 2026", art: 2, download: "#brochure.pdf" },
|
|
43
|
-
{ label: "Site visit", meta: "1:42", type: "video", alt: "Video still: site visit", art: 5 },
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
const media = (doc: DocViewerItem, alt: string) => {
|
|
47
|
-
const type = doc.type ?? "pdf";
|
|
48
|
-
if (!doc.src) return <div className={cx("oc-media-art", doc.art != null && `oc-media-art--${doc.art}`)} role="img" aria-label={alt} />;
|
|
49
|
-
if (type === "video") return <video src={doc.src} controls preload="metadata" aria-label={alt} />;
|
|
50
|
-
if (type === "image") return <img src={doc.src} alt={alt} />;
|
|
51
|
-
return <iframe src={`${doc.src}#toolbar=0&view=FitH`} title={alt} loading="lazy" />;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/** `.oc-doc-viewer-grid` of `figure.oc-doc-viewer` cards, each with its `dialog.oc-doc-viewer-dialog`. */
|
|
55
|
-
export const DocViewer = forwardRef<HTMLDivElement, DocViewerProps>(function DocViewer(
|
|
56
|
-
{ items = DEMO, openText = "Click for full screen", downloadText = "Download", idPrefix, className, ...rest },
|
|
57
|
-
ref,
|
|
58
|
-
) {
|
|
59
|
-
const auto = useId().replace(/[^\w-]/g, "");
|
|
60
|
-
const base = idPrefix ?? `oc-doc-viewer-${auto}`;
|
|
61
|
-
const idOf = (doc: DocViewerItem, i: number) => doc.id ?? `${base}-${i + 1}`;
|
|
62
|
-
|
|
63
|
-
const cards = items.map((doc, i) => {
|
|
64
|
-
const id = idOf(doc, i);
|
|
65
|
-
const full = doc.fullscreen !== false;
|
|
66
|
-
const frame = full && (doc.open ?? (doc.type === "video" ? "frame" : "bar")) === "frame";
|
|
67
|
-
return (
|
|
68
|
-
<figure key={i} className={cx("oc-doc-viewer", doc.type === "video" && "oc-doc-viewer--video", doc.type === "image" && "oc-doc-viewer--image")}>
|
|
69
|
-
<figcaption className="oc-doc-viewer__bar">
|
|
70
|
-
<span className="oc-doc-viewer__label">{doc.label}</span>
|
|
71
|
-
{doc.meta != null && <span className="oc-doc-viewer__meta">{doc.meta}</span>}
|
|
72
|
-
{full && !frame && (
|
|
73
|
-
<button type="button" className="oc-doc-viewer__expand" {...attrs({ command: "show-modal", commandfor: id })} aria-label={`Open ${doc.label} full screen`}>⤢</button>
|
|
74
|
-
)}
|
|
75
|
-
</figcaption>
|
|
76
|
-
<div className="oc-doc-viewer__frame">
|
|
77
|
-
{media(doc, doc.alt ?? doc.label)}
|
|
78
|
-
{frame && <button type="button" className="oc-doc-viewer__open" {...attrs({ command: "show-modal", commandfor: id })}>{openText}</button>}
|
|
79
|
-
</div>
|
|
80
|
-
</figure>
|
|
81
|
-
);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
const dialogs = items.map((doc, i) => {
|
|
85
|
-
if (doc.fullscreen === false) return null;
|
|
86
|
-
const id = idOf(doc, i);
|
|
87
|
-
const close = <button type="button" className="oc-doc-viewer-dialog__close" {...attrs({ command: "close", commandfor: id })} aria-label="Close">✕</button>;
|
|
88
|
-
return (
|
|
89
|
-
<dialog key={i} className={cx("oc-doc-viewer-dialog", doc.type === "video" && "oc-doc-viewer-dialog--video")} id={id} {...attrs({ closedby: "any" })} aria-labelledby={`${id}-title`}>
|
|
90
|
-
<div className="oc-doc-viewer-dialog__bar">
|
|
91
|
-
<h2 className="oc-doc-viewer-dialog__title" id={`${id}-title`}>{doc.label}</h2>
|
|
92
|
-
{doc.download ? (
|
|
93
|
-
<div className="oc-doc-viewer-dialog__actions">
|
|
94
|
-
<a className="oc-btn oc-btn--outline oc-btn--sm" href={doc.download} download>{downloadText}</a>
|
|
95
|
-
{close}
|
|
96
|
-
</div>
|
|
97
|
-
) : close}
|
|
98
|
-
</div>
|
|
99
|
-
<div className="oc-doc-viewer-dialog__body">{media(doc, doc.type === "video" ? `${doc.label} video` : doc.label)}</div>
|
|
100
|
-
</dialog>
|
|
101
|
-
);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<>
|
|
106
|
-
<div ref={ref} className={cx("oc-doc-viewer-grid", className)} {...rest}>{cards}</div>
|
|
107
|
-
{dialogs}
|
|
108
|
-
</>
|
|
109
|
-
);
|
|
110
|
-
});
|
|
1
|
+
// File: packages/react/src/components/DocViewer.tsx
|
|
2
|
+
// Map: types + demo items -> media (iframe / video / img / placeholder art) -> DocViewer (grid of cards -> one <dialog> per card)
|
|
3
|
+
// Same markup as packages/ui/snippets/doc-viewer.html. No JavaScript: the buttons open the full-screen dialog with
|
|
4
|
+
// invoker commands (command="show-modal" commandfor), closedby="any" closes it on Esc or a click on the backdrop.
|
|
5
|
+
import { forwardRef, useId } from "react";
|
|
6
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
7
|
+
import { attrs, cx } from "../utils";
|
|
8
|
+
|
|
9
|
+
export interface DocViewerItem {
|
|
10
|
+
/** Name in the bar and the dialog title; also used in the aria-labels. */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Small text next to the label, e.g. "PDF · 2.4 MB" or "1:42". */
|
|
13
|
+
meta?: ReactNode;
|
|
14
|
+
/** File URL. Leave empty to show placeholder art (`.oc-media-art`). */
|
|
15
|
+
src?: string;
|
|
16
|
+
/** Default "pdf" (A4 frame). "video" = 16/9 dark frame, "image" = natural height. */
|
|
17
|
+
type?: "pdf" | "video" | "image";
|
|
18
|
+
/** Dialog id (generated when empty). */
|
|
19
|
+
id?: string;
|
|
20
|
+
/** Text for the inline preview (aria-label / alt / iframe title). Defaults to the label. */
|
|
21
|
+
alt?: string;
|
|
22
|
+
/** Placeholder art variant (`oc-media-art--N`) when there is no src. */
|
|
23
|
+
art?: number;
|
|
24
|
+
/** Download link in the dialog bar. */
|
|
25
|
+
download?: string;
|
|
26
|
+
/** Where the preview opens: "bar" (expand button) or "frame" (the whole preview). Default: "frame" for video. */
|
|
27
|
+
open?: "bar" | "frame";
|
|
28
|
+
/** Set false for a plain card without full-screen dialog. Default true. */
|
|
29
|
+
fullscreen?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface DocViewerProps extends HTMLAttributes<HTMLDivElement> {
|
|
33
|
+
items?: DocViewerItem[];
|
|
34
|
+
/** Text of the frame button (default "Click for full screen"). */
|
|
35
|
+
openText?: ReactNode;
|
|
36
|
+
downloadText?: ReactNode;
|
|
37
|
+
/** Prefix for generated dialog ids. */
|
|
38
|
+
idPrefix?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const DEMO: DocViewerItem[] = [
|
|
42
|
+
{ label: "Brochure 2026", meta: "PDF · 2.4 MB", alt: "First page of Brochure 2026", art: 2, download: "#brochure.pdf" },
|
|
43
|
+
{ label: "Site visit", meta: "1:42", type: "video", alt: "Video still: site visit", art: 5 },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const media = (doc: DocViewerItem, alt: string) => {
|
|
47
|
+
const type = doc.type ?? "pdf";
|
|
48
|
+
if (!doc.src) return <div className={cx("oc-media-art", doc.art != null && `oc-media-art--${doc.art}`)} role="img" aria-label={alt} />;
|
|
49
|
+
if (type === "video") return <video src={doc.src} controls preload="metadata" aria-label={alt} />;
|
|
50
|
+
if (type === "image") return <img src={doc.src} alt={alt} />;
|
|
51
|
+
return <iframe src={`${doc.src}#toolbar=0&view=FitH`} title={alt} loading="lazy" />;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** `.oc-doc-viewer-grid` of `figure.oc-doc-viewer` cards, each with its `dialog.oc-doc-viewer-dialog`. */
|
|
55
|
+
export const DocViewer = forwardRef<HTMLDivElement, DocViewerProps>(function DocViewer(
|
|
56
|
+
{ items = DEMO, openText = "Click for full screen", downloadText = "Download", idPrefix, className, ...rest },
|
|
57
|
+
ref,
|
|
58
|
+
) {
|
|
59
|
+
const auto = useId().replace(/[^\w-]/g, "");
|
|
60
|
+
const base = idPrefix ?? `oc-doc-viewer-${auto}`;
|
|
61
|
+
const idOf = (doc: DocViewerItem, i: number) => doc.id ?? `${base}-${i + 1}`;
|
|
62
|
+
|
|
63
|
+
const cards = items.map((doc, i) => {
|
|
64
|
+
const id = idOf(doc, i);
|
|
65
|
+
const full = doc.fullscreen !== false;
|
|
66
|
+
const frame = full && (doc.open ?? (doc.type === "video" ? "frame" : "bar")) === "frame";
|
|
67
|
+
return (
|
|
68
|
+
<figure key={i} className={cx("oc-doc-viewer", doc.type === "video" && "oc-doc-viewer--video", doc.type === "image" && "oc-doc-viewer--image")}>
|
|
69
|
+
<figcaption className="oc-doc-viewer__bar">
|
|
70
|
+
<span className="oc-doc-viewer__label">{doc.label}</span>
|
|
71
|
+
{doc.meta != null && <span className="oc-doc-viewer__meta">{doc.meta}</span>}
|
|
72
|
+
{full && !frame && (
|
|
73
|
+
<button type="button" className="oc-doc-viewer__expand" {...attrs({ command: "show-modal", commandfor: id })} aria-label={`Open ${doc.label} full screen`}>⤢</button>
|
|
74
|
+
)}
|
|
75
|
+
</figcaption>
|
|
76
|
+
<div className="oc-doc-viewer__frame">
|
|
77
|
+
{media(doc, doc.alt ?? doc.label)}
|
|
78
|
+
{frame && <button type="button" className="oc-doc-viewer__open" {...attrs({ command: "show-modal", commandfor: id })}>{openText}</button>}
|
|
79
|
+
</div>
|
|
80
|
+
</figure>
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const dialogs = items.map((doc, i) => {
|
|
85
|
+
if (doc.fullscreen === false) return null;
|
|
86
|
+
const id = idOf(doc, i);
|
|
87
|
+
const close = <button type="button" className="oc-doc-viewer-dialog__close" {...attrs({ command: "close", commandfor: id })} aria-label="Close">✕</button>;
|
|
88
|
+
return (
|
|
89
|
+
<dialog key={i} className={cx("oc-doc-viewer-dialog", doc.type === "video" && "oc-doc-viewer-dialog--video")} id={id} {...attrs({ closedby: "any" })} aria-labelledby={`${id}-title`}>
|
|
90
|
+
<div className="oc-doc-viewer-dialog__bar">
|
|
91
|
+
<h2 className="oc-doc-viewer-dialog__title" id={`${id}-title`}>{doc.label}</h2>
|
|
92
|
+
{doc.download ? (
|
|
93
|
+
<div className="oc-doc-viewer-dialog__actions">
|
|
94
|
+
<a className="oc-btn oc-btn--outline oc-btn--sm" href={doc.download} download>{downloadText}</a>
|
|
95
|
+
{close}
|
|
96
|
+
</div>
|
|
97
|
+
) : close}
|
|
98
|
+
</div>
|
|
99
|
+
<div className="oc-doc-viewer-dialog__body">{media(doc, doc.type === "video" ? `${doc.label} video` : doc.label)}</div>
|
|
100
|
+
</dialog>
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<>
|
|
106
|
+
<div ref={ref} className={cx("oc-doc-viewer-grid", className)} {...rest}>{cards}</div>
|
|
107
|
+
{dialogs}
|
|
108
|
+
</>
|
|
109
|
+
);
|
|
110
|
+
});
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import { forwardRef, useId } from "react";
|
|
2
|
-
import type { AnchorHTMLAttributes, ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react";
|
|
3
|
-
import { attrs, cx } from "../utils";
|
|
4
|
-
import { Button } from "./Button";
|
|
5
|
-
import type { ButtonProps } from "./Button";
|
|
6
|
-
|
|
7
|
-
export interface DropdownProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
-
/** Content of the trigger button. */
|
|
9
|
-
trigger: ReactNode;
|
|
10
|
-
/** Props for the trigger button (default variant `outline`). */
|
|
11
|
-
triggerProps?: ButtonProps;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Trigger button + `<div class="oc-dropdown-menu" popover>`, wired with `popovertarget`
|
|
16
|
-
* (native popover and anchor positioning, no state needed).
|
|
17
|
-
*/
|
|
18
|
-
export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(function Dropdown(
|
|
19
|
-
{ trigger, triggerProps, className, id, children, ...rest },
|
|
20
|
-
ref,
|
|
21
|
-
) {
|
|
22
|
-
const auto = useId();
|
|
23
|
-
const menuId = id ?? `oc-menu-${auto.replace(/:/g, "")}`;
|
|
24
|
-
return (
|
|
25
|
-
<>
|
|
26
|
-
<Button variant="outline" {...triggerProps} {...attrs({ popovertarget: menuId })}>{trigger}</Button>
|
|
27
|
-
<div ref={ref} className={cx("oc-dropdown-menu", className)} id={menuId} {...attrs({ popover: "" })} {...rest}>
|
|
28
|
-
{children}
|
|
29
|
-
</div>
|
|
30
|
-
</>
|
|
31
|
-
);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
/** `.oc-dropdown-label` */
|
|
35
|
-
export function DropdownLabel({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {
|
|
36
|
-
return <div className={cx("oc-dropdown-label", className)} {...rest} />;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** `.oc-dropdown-separator` */
|
|
40
|
-
export function DropdownSeparator({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {
|
|
41
|
-
return <div className={cx("oc-dropdown-separator", className)} {...rest} />;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
type ItemProps = (AnchorHTMLAttributes<HTMLAnchorElement> & { href: string }) | (ButtonHTMLAttributes<HTMLButtonElement> & { href?: undefined });
|
|
45
|
-
|
|
46
|
-
/** `.oc-dropdown-item`: an anchor when `href` is set, otherwise a button. */
|
|
47
|
-
export function DropdownItem({ className, ...props }: ItemProps) {
|
|
48
|
-
if (props.href !== undefined) {
|
|
49
|
-
return <a className={cx("oc-dropdown-item", className)} {...(props as AnchorHTMLAttributes<HTMLAnchorElement>)} />;
|
|
50
|
-
}
|
|
51
|
-
return <button type="button" className={cx("oc-dropdown-item", className)} {...(props as ButtonHTMLAttributes<HTMLButtonElement>)} />;
|
|
52
|
-
}
|
|
1
|
+
import { forwardRef, useId } from "react";
|
|
2
|
+
import type { AnchorHTMLAttributes, ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react";
|
|
3
|
+
import { attrs, cx } from "../utils";
|
|
4
|
+
import { Button } from "./Button";
|
|
5
|
+
import type { ButtonProps } from "./Button";
|
|
6
|
+
|
|
7
|
+
export interface DropdownProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
/** Content of the trigger button. */
|
|
9
|
+
trigger: ReactNode;
|
|
10
|
+
/** Props for the trigger button (default variant `outline`). */
|
|
11
|
+
triggerProps?: ButtonProps;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Trigger button + `<div class="oc-dropdown-menu" popover>`, wired with `popovertarget`
|
|
16
|
+
* (native popover and anchor positioning, no state needed).
|
|
17
|
+
*/
|
|
18
|
+
export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(function Dropdown(
|
|
19
|
+
{ trigger, triggerProps, className, id, children, ...rest },
|
|
20
|
+
ref,
|
|
21
|
+
) {
|
|
22
|
+
const auto = useId();
|
|
23
|
+
const menuId = id ?? `oc-menu-${auto.replace(/:/g, "")}`;
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<Button variant="outline" {...triggerProps} {...attrs({ popovertarget: menuId })}>{trigger}</Button>
|
|
27
|
+
<div ref={ref} className={cx("oc-dropdown-menu", className)} id={menuId} {...attrs({ popover: "" })} {...rest}>
|
|
28
|
+
{children}
|
|
29
|
+
</div>
|
|
30
|
+
</>
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** `.oc-dropdown-label` */
|
|
35
|
+
export function DropdownLabel({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {
|
|
36
|
+
return <div className={cx("oc-dropdown-label", className)} {...rest} />;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** `.oc-dropdown-separator` */
|
|
40
|
+
export function DropdownSeparator({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {
|
|
41
|
+
return <div className={cx("oc-dropdown-separator", className)} {...rest} />;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type ItemProps = (AnchorHTMLAttributes<HTMLAnchorElement> & { href: string }) | (ButtonHTMLAttributes<HTMLButtonElement> & { href?: undefined });
|
|
45
|
+
|
|
46
|
+
/** `.oc-dropdown-item`: an anchor when `href` is set, otherwise a button. */
|
|
47
|
+
export function DropdownItem({ className, ...props }: ItemProps) {
|
|
48
|
+
if (props.href !== undefined) {
|
|
49
|
+
return <a className={cx("oc-dropdown-item", className)} {...(props as AnchorHTMLAttributes<HTMLAnchorElement>)} />;
|
|
50
|
+
}
|
|
51
|
+
return <button type="button" className={cx("oc-dropdown-item", className)} {...(props as ButtonHTMLAttributes<HTMLButtonElement>)} />;
|
|
52
|
+
}
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { forwardRef, useState } from "react";
|
|
4
|
-
import type { DragEvent, InputHTMLAttributes, ReactNode } from "react";
|
|
5
|
-
import { cx } from "../utils";
|
|
6
|
-
|
|
7
|
-
export interface DropzoneProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "onChange" | "children"> {
|
|
8
|
-
/** Icon in `oc-dropzone-icon` (inline SVG or glyph). */
|
|
9
|
-
icon?: ReactNode;
|
|
10
|
-
/** Main text, e.g. `<strong>Drop invoices here</strong> or click to choose`. */
|
|
11
|
-
children?: ReactNode;
|
|
12
|
-
/** Small hint (`oc-dropzone-hint`), e.g. accepted types and size. */
|
|
13
|
-
hint?: ReactNode;
|
|
14
|
-
/** Called with the chosen or dropped files. */
|
|
15
|
-
onFiles?: (files: File[]) => void;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** `<label class="oc-dropzone">` around a real file input; `is-dragover` while dragging. */
|
|
19
|
-
export const Dropzone = forwardRef<HTMLInputElement, DropzoneProps>(function Dropzone(
|
|
20
|
-
{ icon, children, hint, onFiles, className, ...rest },
|
|
21
|
-
ref,
|
|
22
|
-
) {
|
|
23
|
-
const [over, setOver] = useState(false);
|
|
24
|
-
const stop = (e: DragEvent) => {
|
|
25
|
-
e.preventDefault();
|
|
26
|
-
setOver(e.type === "dragover" || e.type === "dragenter");
|
|
27
|
-
};
|
|
28
|
-
return (
|
|
29
|
-
<label
|
|
30
|
-
className={cx("oc-dropzone", over && "is-dragover", className)}
|
|
31
|
-
onDragEnter={stop}
|
|
32
|
-
onDragOver={stop}
|
|
33
|
-
onDragLeave={stop}
|
|
34
|
-
onDrop={(e) => {
|
|
35
|
-
stop(e);
|
|
36
|
-
const files = Array.from(e.dataTransfer?.files ?? []);
|
|
37
|
-
if (files.length && !rest.disabled) onFiles?.(files);
|
|
38
|
-
}}
|
|
39
|
-
>
|
|
40
|
-
<input ref={ref} type="file" onChange={(e) => onFiles?.(Array.from(e.target.files ?? []))} {...rest} />
|
|
41
|
-
{icon != null && <span className="oc-dropzone-icon" aria-hidden="true">{icon}</span>}
|
|
42
|
-
<span>{children}</span>
|
|
43
|
-
{hint != null && <span className="oc-dropzone-hint">{hint}</span>}
|
|
44
|
-
</label>
|
|
45
|
-
);
|
|
46
|
-
});
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { forwardRef, useState } from "react";
|
|
4
|
+
import type { DragEvent, InputHTMLAttributes, ReactNode } from "react";
|
|
5
|
+
import { cx } from "../utils";
|
|
6
|
+
|
|
7
|
+
export interface DropzoneProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "onChange" | "children"> {
|
|
8
|
+
/** Icon in `oc-dropzone-icon` (inline SVG or glyph). */
|
|
9
|
+
icon?: ReactNode;
|
|
10
|
+
/** Main text, e.g. `<strong>Drop invoices here</strong> or click to choose`. */
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
/** Small hint (`oc-dropzone-hint`), e.g. accepted types and size. */
|
|
13
|
+
hint?: ReactNode;
|
|
14
|
+
/** Called with the chosen or dropped files. */
|
|
15
|
+
onFiles?: (files: File[]) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** `<label class="oc-dropzone">` around a real file input; `is-dragover` while dragging. */
|
|
19
|
+
export const Dropzone = forwardRef<HTMLInputElement, DropzoneProps>(function Dropzone(
|
|
20
|
+
{ icon, children, hint, onFiles, className, ...rest },
|
|
21
|
+
ref,
|
|
22
|
+
) {
|
|
23
|
+
const [over, setOver] = useState(false);
|
|
24
|
+
const stop = (e: DragEvent) => {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
setOver(e.type === "dragover" || e.type === "dragenter");
|
|
27
|
+
};
|
|
28
|
+
return (
|
|
29
|
+
<label
|
|
30
|
+
className={cx("oc-dropzone", over && "is-dragover", className)}
|
|
31
|
+
onDragEnter={stop}
|
|
32
|
+
onDragOver={stop}
|
|
33
|
+
onDragLeave={stop}
|
|
34
|
+
onDrop={(e) => {
|
|
35
|
+
stop(e);
|
|
36
|
+
const files = Array.from(e.dataTransfer?.files ?? []);
|
|
37
|
+
if (files.length && !rest.disabled) onFiles?.(files);
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
<input ref={ref} type="file" onChange={(e) => onFiles?.(Array.from(e.target.files ?? []))} {...rest} />
|
|
41
|
+
{icon != null && <span className="oc-dropzone-icon" aria-hidden="true">{icon}</span>}
|
|
42
|
+
<span>{children}</span>
|
|
43
|
+
{hint != null && <span className="oc-dropzone-hint">{hint}</span>}
|
|
44
|
+
</label>
|
|
45
|
+
);
|
|
46
|
+
});
|
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
// File: packages/react/src/components/EmojiPicker.tsx
|
|
2
|
-
// Map: default groups -> props -> EmojiPicker (trigger button -> popover panel -> groups: label + grid of buttons)
|
|
3
|
-
// Same markup as packages/ui/snippets/emoji-picker.html. React calls onSelect and closes the popover.
|
|
4
|
-
"use client";
|
|
5
|
-
|
|
6
|
-
import { forwardRef, useId } from "react";
|
|
7
|
-
import type { HTMLAttributes, MouseEvent, ReactNode } from "react";
|
|
8
|
-
import { attrs, cx } from "../utils";
|
|
9
|
-
|
|
10
|
-
export interface EmojiGroup {
|
|
11
|
-
label?: string;
|
|
12
|
-
emojis: string[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const defaultGroups: EmojiGroup[] = [
|
|
16
|
-
{ label: "Smileys", emojis: ["😀", "😃", "😄", "😁", "😂", "🙂", "😉", "😍", "🤔", "😎", "😢", "😴", "😯", "🥳", "😤", "🤗"] },
|
|
17
|
-
{ label: "Gestures", emojis: ["👍", "👎", "👏", "🙌", "👋", "🙏", "✌️", "💪"] },
|
|
18
|
-
{ label: "Objects", emojis: ["❤️", "⭐", "🔥", "🎉", "✅", "🚀", "💡", "☕"] },
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
export interface EmojiPickerProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> {
|
|
22
|
-
groups?: EmojiGroup[];
|
|
23
|
-
/** Called with the picked emoji. */
|
|
24
|
-
onSelect?: (emoji: string) => void;
|
|
25
|
-
/** Always-visible panel without trigger / popover. */
|
|
26
|
-
inline?: boolean;
|
|
27
|
-
/** Trigger content (default 🙂) and its accessible name. */
|
|
28
|
-
trigger?: ReactNode;
|
|
29
|
-
triggerLabel?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** `.oc-emoji-picker`: popover grid of emoji buttons, anchored to its trigger. */
|
|
33
|
-
export const EmojiPicker = forwardRef<HTMLDivElement, EmojiPickerProps>(function EmojiPicker(
|
|
34
|
-
{ groups = defaultGroups, onSelect, inline, trigger = "🙂", triggerLabel = "Insert emoji", className, id, "aria-label": label = "Emoji", ...rest },
|
|
35
|
-
ref,
|
|
36
|
-
) {
|
|
37
|
-
const auto = useId();
|
|
38
|
-
const panelId = id ?? `oc-emoji-${auto.replace(/:/g, "")}`;
|
|
39
|
-
const pick = (e: MouseEvent<HTMLButtonElement>, emoji: string) => {
|
|
40
|
-
onSelect?.(emoji);
|
|
41
|
-
(e.currentTarget.closest("[popover]") as (HTMLElement & { hidePopover?: () => void }) | null)?.hidePopover?.();
|
|
42
|
-
};
|
|
43
|
-
const panel = (
|
|
44
|
-
<div
|
|
45
|
-
ref={ref}
|
|
46
|
-
id={panelId}
|
|
47
|
-
className={cx("oc-emoji-picker", inline && "oc-emoji-picker--inline", className)}
|
|
48
|
-
role={inline ? "group" : undefined}
|
|
49
|
-
aria-label={label}
|
|
50
|
-
{...(inline ? {} : attrs({ popover: "" }))}
|
|
51
|
-
{...rest}
|
|
52
|
-
>
|
|
53
|
-
{groups.map((g, gi) => (
|
|
54
|
-
<div key={gi}>
|
|
55
|
-
{g.label && <p className="oc-emoji-picker__label">{g.label}</p>}
|
|
56
|
-
<div className="oc-emoji-picker__grid">
|
|
57
|
-
{g.emojis.map((emoji) => (
|
|
58
|
-
<button key={emoji} type="button" className="oc-emoji-picker__item" onClick={(e) => pick(e, emoji)}>{emoji}</button>
|
|
59
|
-
))}
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
))}
|
|
63
|
-
</div>
|
|
64
|
-
);
|
|
65
|
-
if (inline) return panel;
|
|
66
|
-
return (
|
|
67
|
-
<>
|
|
68
|
-
<button type="button" className="oc-emoji-picker__trigger" aria-label={triggerLabel} {...attrs({ popovertarget: panelId })}>{trigger}</button>
|
|
69
|
-
{panel}
|
|
70
|
-
</>
|
|
71
|
-
);
|
|
72
|
-
});
|
|
1
|
+
// File: packages/react/src/components/EmojiPicker.tsx
|
|
2
|
+
// Map: default groups -> props -> EmojiPicker (trigger button -> popover panel -> groups: label + grid of buttons)
|
|
3
|
+
// Same markup as packages/ui/snippets/emoji-picker.html. React calls onSelect and closes the popover.
|
|
4
|
+
"use client";
|
|
5
|
+
|
|
6
|
+
import { forwardRef, useId } from "react";
|
|
7
|
+
import type { HTMLAttributes, MouseEvent, ReactNode } from "react";
|
|
8
|
+
import { attrs, cx } from "../utils";
|
|
9
|
+
|
|
10
|
+
export interface EmojiGroup {
|
|
11
|
+
label?: string;
|
|
12
|
+
emojis: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const defaultGroups: EmojiGroup[] = [
|
|
16
|
+
{ label: "Smileys", emojis: ["😀", "😃", "😄", "😁", "😂", "🙂", "😉", "😍", "🤔", "😎", "😢", "😴", "😯", "🥳", "😤", "🤗"] },
|
|
17
|
+
{ label: "Gestures", emojis: ["👍", "👎", "👏", "🙌", "👋", "🙏", "✌️", "💪"] },
|
|
18
|
+
{ label: "Objects", emojis: ["❤️", "⭐", "🔥", "🎉", "✅", "🚀", "💡", "☕"] },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export interface EmojiPickerProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> {
|
|
22
|
+
groups?: EmojiGroup[];
|
|
23
|
+
/** Called with the picked emoji. */
|
|
24
|
+
onSelect?: (emoji: string) => void;
|
|
25
|
+
/** Always-visible panel without trigger / popover. */
|
|
26
|
+
inline?: boolean;
|
|
27
|
+
/** Trigger content (default 🙂) and its accessible name. */
|
|
28
|
+
trigger?: ReactNode;
|
|
29
|
+
triggerLabel?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** `.oc-emoji-picker`: popover grid of emoji buttons, anchored to its trigger. */
|
|
33
|
+
export const EmojiPicker = forwardRef<HTMLDivElement, EmojiPickerProps>(function EmojiPicker(
|
|
34
|
+
{ groups = defaultGroups, onSelect, inline, trigger = "🙂", triggerLabel = "Insert emoji", className, id, "aria-label": label = "Emoji", ...rest },
|
|
35
|
+
ref,
|
|
36
|
+
) {
|
|
37
|
+
const auto = useId();
|
|
38
|
+
const panelId = id ?? `oc-emoji-${auto.replace(/:/g, "")}`;
|
|
39
|
+
const pick = (e: MouseEvent<HTMLButtonElement>, emoji: string) => {
|
|
40
|
+
onSelect?.(emoji);
|
|
41
|
+
(e.currentTarget.closest("[popover]") as (HTMLElement & { hidePopover?: () => void }) | null)?.hidePopover?.();
|
|
42
|
+
};
|
|
43
|
+
const panel = (
|
|
44
|
+
<div
|
|
45
|
+
ref={ref}
|
|
46
|
+
id={panelId}
|
|
47
|
+
className={cx("oc-emoji-picker", inline && "oc-emoji-picker--inline", className)}
|
|
48
|
+
role={inline ? "group" : undefined}
|
|
49
|
+
aria-label={label}
|
|
50
|
+
{...(inline ? {} : attrs({ popover: "" }))}
|
|
51
|
+
{...rest}
|
|
52
|
+
>
|
|
53
|
+
{groups.map((g, gi) => (
|
|
54
|
+
<div key={gi}>
|
|
55
|
+
{g.label && <p className="oc-emoji-picker__label">{g.label}</p>}
|
|
56
|
+
<div className="oc-emoji-picker__grid">
|
|
57
|
+
{g.emojis.map((emoji) => (
|
|
58
|
+
<button key={emoji} type="button" className="oc-emoji-picker__item" onClick={(e) => pick(e, emoji)}>{emoji}</button>
|
|
59
|
+
))}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
if (inline) return panel;
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
<button type="button" className="oc-emoji-picker__trigger" aria-label={triggerLabel} {...attrs({ popovertarget: panelId })}>{trigger}</button>
|
|
69
|
+
{panel}
|
|
70
|
+
</>
|
|
71
|
+
);
|
|
72
|
+
});
|