@birdycom/omniaui-react 0.12.4 → 0.12.5
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 +6 -4
- package/dist/index.js +13 -30
- package/package.json +1 -1
- package/src/blocks/BlockAppHeader.tsx +1 -1
- package/src/components/Icon.tsx +9 -14
- package/src/components/LanguageSelect.tsx +1 -4
- package/src/components/Message.tsx +5 -4
- package/src/components/MessageThread.tsx +1 -1
- package/src/components/User.tsx +3 -3
- package/src/utils.ts +7 -0
package/dist/index.d.ts
CHANGED
|
@@ -920,11 +920,13 @@ interface HeadingProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
|
920
920
|
declare const Heading: react.ForwardRefExoticComponent<HeadingProps & react.RefAttributes<HTMLDivElement>>;
|
|
921
921
|
|
|
922
922
|
interface IconProps extends Omit<SVGProps<SVGSVGElement>, "d"> {
|
|
923
|
-
/** SVG path data
|
|
923
|
+
/** SVG path data on a 24x24 viewBox: a webicon path (MDI, MDI Light or Material Symbols), e.g. the path of mdi:menu. Use `children` for several shapes. */
|
|
924
924
|
d?: string;
|
|
925
|
+
/** Draw `d` as a 2px line instead of a filled shape (older line icons). */
|
|
926
|
+
line?: boolean;
|
|
925
927
|
}
|
|
926
|
-
/** Inline 24x24
|
|
927
|
-
declare function Icon({ d, children, className, ...rest }: IconProps): react.JSX.Element;
|
|
928
|
+
/** Inline 24x24 icon in `currentColor`: a filled webicon path (the snippets use MDI), drawn without iconify-icon. */
|
|
929
|
+
declare function Icon({ d, line, children, className, ...rest }: IconProps): react.JSX.Element;
|
|
928
930
|
|
|
929
931
|
interface ImageZoomProps extends HTMLAttributes<HTMLDivElement> {
|
|
930
932
|
src?: string;
|
|
@@ -2167,7 +2169,7 @@ declare const UpgradeBanner: react.ForwardRefExoticComponent<UpgradeBannerProps
|
|
|
2167
2169
|
interface UserProps extends HTMLAttributes<HTMLDivElement> {
|
|
2168
2170
|
name: ReactNode;
|
|
2169
2171
|
email?: ReactNode;
|
|
2170
|
-
/** Initials shown in the avatar. */
|
|
2172
|
+
/** Initials shown in the avatar (default: from `name` when it is a string). */
|
|
2171
2173
|
initials?: string;
|
|
2172
2174
|
/** Avatar image URL (wins over initials). */
|
|
2173
2175
|
avatarSrc?: string;
|
package/dist/index.js
CHANGED
|
@@ -115,6 +115,9 @@ function mod(base, ...modifiers) {
|
|
|
115
115
|
function attrs(record) {
|
|
116
116
|
return record;
|
|
117
117
|
}
|
|
118
|
+
function initialsOf(name) {
|
|
119
|
+
return typeof name === "string" ? name.trim().split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("") : "";
|
|
120
|
+
}
|
|
118
121
|
function callAll(...fns) {
|
|
119
122
|
return (e) => fns.forEach((fn) => fn?.(e));
|
|
120
123
|
}
|
|
@@ -2094,22 +2097,9 @@ var Heading = forwardRef46(function Heading2({ title, description, level = 2, si
|
|
|
2094
2097
|
|
|
2095
2098
|
// src/components/Icon.tsx
|
|
2096
2099
|
import { jsx as jsx48 } from "react/jsx-runtime";
|
|
2097
|
-
function Icon({ d, children, className, ...rest }) {
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
{
|
|
2101
|
-
className,
|
|
2102
|
-
viewBox: "0 0 24 24",
|
|
2103
|
-
fill: "none",
|
|
2104
|
-
stroke: "currentColor",
|
|
2105
|
-
strokeWidth: 2,
|
|
2106
|
-
strokeLinecap: "round",
|
|
2107
|
-
strokeLinejoin: "round",
|
|
2108
|
-
"aria-hidden": "true",
|
|
2109
|
-
...rest,
|
|
2110
|
-
children: d ? /* @__PURE__ */ jsx48("path", { d }) : children
|
|
2111
|
-
}
|
|
2112
|
-
);
|
|
2100
|
+
function Icon({ d, line = false, children, className, ...rest }) {
|
|
2101
|
+
const paint = line ? { fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" } : { fill: "currentColor" };
|
|
2102
|
+
return /* @__PURE__ */ jsx48("svg", { className, viewBox: "0 0 24 24", "aria-hidden": "true", ...paint, ...rest, children: d ? /* @__PURE__ */ jsx48("path", { d }) : children });
|
|
2113
2103
|
}
|
|
2114
2104
|
|
|
2115
2105
|
// src/components/ImageZoom.tsx
|
|
@@ -2163,10 +2153,7 @@ var InputGroup = forwardRef49(function InputGroup2({ prefix, suffix, className,
|
|
|
2163
2153
|
// src/components/LanguageSelect.tsx
|
|
2164
2154
|
import { forwardRef as forwardRef50 } from "react";
|
|
2165
2155
|
import { jsx as jsx52, jsxs as jsxs42 } from "react/jsx-runtime";
|
|
2166
|
-
var Globe = () => /* @__PURE__ */
|
|
2167
|
-
/* @__PURE__ */ jsx52("circle", { cx: "12", cy: "12", r: "9" }),
|
|
2168
|
-
/* @__PURE__ */ jsx52("path", { d: "M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" })
|
|
2169
|
-
] });
|
|
2156
|
+
var Globe = () => /* @__PURE__ */ jsx52("svg", { "data-icon": "mdi:web", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx52("path", { d: "M16.36 14c.08-.66.14-1.32.14-2s-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2m-5.15 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56M14.34 14H9.66c-.1-.66-.16-1.32-.16-2s.06-1.35.16-2h4.68c.09.65.16 1.32.16 2s-.07 1.34-.16 2M12 19.96c-.83-1.2-1.5-2.53-1.91-3.96h3.82c-.41 1.43-1.08 2.76-1.91 3.96M8 8H5.08A7.92 7.92 0 0 1 9.4 4.44C8.8 5.55 8.35 6.75 8 8m-2.92 8H8c.35 1.25.8 2.45 1.4 3.56A8 8 0 0 1 5.08 16m-.82-2C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2s.06 1.34.14 2M12 4.03c.83 1.2 1.5 2.54 1.91 3.97h-3.82c.41-1.43 1.08-2.77 1.91-3.97M18.92 8h-2.95a15.7 15.7 0 0 0-1.38-3.56c1.84.63 3.37 1.9 4.33 3.56M12 2C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2" }) });
|
|
2170
2157
|
var LanguageSelect = forwardRef50(function LanguageSelect2({ options, value, defaultValue, onChange, currency, ghost, icon, labelClassName, className, ...rest }, ref) {
|
|
2171
2158
|
const [current, setCurrent] = useControllable(value, defaultValue ?? options[0]?.value ?? "", onChange);
|
|
2172
2159
|
return /* @__PURE__ */ jsxs42("label", { className: cx("oc-language-select", ghost && "oc-language-select--ghost", currency && "oc-language-select--currency", labelClassName), children: [
|
|
@@ -2426,8 +2413,8 @@ var MediaRow = forwardRef60(function MediaRow2({ title, href, meta, thumb, curre
|
|
|
2426
2413
|
// src/components/Message.tsx
|
|
2427
2414
|
import { forwardRef as forwardRef61 } from "react";
|
|
2428
2415
|
import { Fragment as Fragment14, jsx as jsx63, jsxs as jsxs50 } from "react/jsx-runtime";
|
|
2429
|
-
var svg = (d
|
|
2430
|
-
var ICONS = { reply: "
|
|
2416
|
+
var svg = (d) => /* @__PURE__ */ jsx63("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx63("path", { d }) });
|
|
2417
|
+
var ICONS = { reply: "M8 9.8v.9l1.7.3c2.6.4 4.5 1.4 5.9 2.7c-1.7-.5-3.5-.8-5.6-.8H8v1.3L5.8 12zM10 5l-7 7l7 7v-4.1c5 0 8.5 1.6 11 5.1c-1-5-4-10-11-11", delete: "M9 3v1H4v2h1v13a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6h1V4h-5V3zM7 6h10v13H7zm2 2v9h2V8zm4 0v9h2V8z" };
|
|
2431
2418
|
var STATUS = { sent: "Sent", read: "Read", failed: "Failed" };
|
|
2432
2419
|
function MessageAttachments({ items }) {
|
|
2433
2420
|
const thumbs = items.every((a) => a.kind === "thumb");
|
|
@@ -2461,7 +2448,7 @@ var Message = forwardRef61(function Message2({ direction = "in", sender, avatar,
|
|
|
2461
2448
|
(time != null || status) && /* @__PURE__ */ jsxs50("p", { className: "oc-message__meta", children: [
|
|
2462
2449
|
time != null && /* @__PURE__ */ jsx63("time", { dateTime: datetime, children: time }),
|
|
2463
2450
|
status && /* @__PURE__ */ jsxs50("span", { className: cx("oc-message__status", status !== "sent" && `oc-message__status--${status}`), children: [
|
|
2464
|
-
statusIcon && svg("
|
|
2451
|
+
statusIcon && svg("M.41 13.41L6 19l1.41-1.42L1.83 12m20.41-6.42L11.66 16.17L7.5 12l-1.43 1.41L11.66 19l12-12M18 7l-1.41-1.42l-6.35 6.35l1.42 1.41z"),
|
|
2465
2452
|
statusLabel ?? STATUS[status]
|
|
2466
2453
|
] })
|
|
2467
2454
|
] })
|
|
@@ -2646,11 +2633,7 @@ var MessageThread = forwardRef62(function MessageThread2({
|
|
|
2646
2633
|
(status || menu) && /* @__PURE__ */ jsxs51("div", { className: "oc-message-thread__actions", children: [
|
|
2647
2634
|
status && /* @__PURE__ */ jsx64("span", { className: cx("oc-badge", status.variant && `oc-badge--${status.variant}`), children: status.label }),
|
|
2648
2635
|
menu && /* @__PURE__ */ jsxs51(Fragment15, { children: [
|
|
2649
|
-
/* @__PURE__ */ jsx64("button", { type: "button", className: "oc-message-thread__icon-btn", popoverTarget: `${base}-menu`, "aria-label": "Conversation actions", children: /* @__PURE__ */
|
|
2650
|
-
/* @__PURE__ */ jsx64("circle", { cx: "12", cy: "5", r: "1.8" }),
|
|
2651
|
-
/* @__PURE__ */ jsx64("circle", { cx: "12", cy: "12", r: "1.8" }),
|
|
2652
|
-
/* @__PURE__ */ jsx64("circle", { cx: "12", cy: "19", r: "1.8" })
|
|
2653
|
-
] }) }),
|
|
2636
|
+
/* @__PURE__ */ jsx64("button", { type: "button", className: "oc-message-thread__icon-btn", popoverTarget: `${base}-menu`, "aria-label": "Conversation actions", children: /* @__PURE__ */ jsx64("svg", { "data-icon": "mdi:dots-vertical", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx64("path", { d: "M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2" }) }) }),
|
|
2654
2637
|
/* @__PURE__ */ jsx64("div", { className: "oc-dropdown-menu", id: `${base}-menu`, ...attrs({ popover: "" }), role: "menu", children: menu.map((m, i) => "separator" in m ? /* @__PURE__ */ jsx64("div", { className: "oc-dropdown-separator", role: "separator" }, i) : /* @__PURE__ */ jsx64("button", { type: "button", className: cx("oc-dropdown-item", m.destructive && "oc-dropdown-item--destructive"), role: "menuitem", onClick: () => onMenu?.(m.name), children: m.label }, i)) })
|
|
2655
2638
|
] })
|
|
2656
2639
|
] })
|
|
@@ -3974,7 +3957,7 @@ import { forwardRef as forwardRef109 } from "react";
|
|
|
3974
3957
|
import { jsx as jsx111, jsxs as jsxs91 } from "react/jsx-runtime";
|
|
3975
3958
|
var User = forwardRef109(function User2({ name, email, initials, avatarSrc, avatarSize = "md", className, ...rest }, ref) {
|
|
3976
3959
|
return /* @__PURE__ */ jsxs91("div", { ref, className: cx("oc-user", className), ...rest, children: [
|
|
3977
|
-
/* @__PURE__ */ jsx111(Avatar, { size: avatarSize, src: avatarSrc, children: initials }),
|
|
3960
|
+
/* @__PURE__ */ jsx111(Avatar, { size: avatarSize, src: avatarSrc, children: initials ?? initialsOf(name) }),
|
|
3978
3961
|
/* @__PURE__ */ jsxs91("span", { className: "oc-user-text", children: [
|
|
3979
3962
|
/* @__PURE__ */ jsx111("span", { className: "oc-user-name", children: name }),
|
|
3980
3963
|
email != null && /* @__PURE__ */ jsx111("span", { className: "oc-user-email", children: email })
|
|
@@ -4030,7 +4013,7 @@ import { jsx as jsx115, jsxs as jsxs94 } from "react/jsx-runtime";
|
|
|
4030
4013
|
var BlockAppHeader = forwardRef113(function BlockAppHeader2({ toggleFor, toggleLabel = "Toggle sidebar", nav, navLabel = "Module navigation", actions, title, className, ...rest }, ref) {
|
|
4031
4014
|
return /* @__PURE__ */ jsxs94("header", { ref, className: cx("oc-block-appbar", className), ...rest, children: [
|
|
4032
4015
|
toggleFor && /* @__PURE__ */ jsxs94("label", { className: "oc-btn oc-btn--ghost oc-btn--icon", htmlFor: toggleFor, title: toggleLabel, children: [
|
|
4033
|
-
/* @__PURE__ */ jsx115(Icon, { d: "
|
|
4016
|
+
/* @__PURE__ */ jsx115(Icon, { d: "M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z" }),
|
|
4034
4017
|
/* @__PURE__ */ jsx115("span", { className: "oc-visually-hidden", children: toggleLabel })
|
|
4035
4018
|
] }),
|
|
4036
4019
|
title != null && /* @__PURE__ */ jsx115("span", { className: "oc-block-appbar-title", children: title }),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@birdycom/omniaui-react",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.5",
|
|
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",
|
|
@@ -32,7 +32,7 @@ export const BlockAppHeader = forwardRef<HTMLElement, BlockAppHeaderProps>(funct
|
|
|
32
32
|
<header ref={ref} className={cx("oc-block-appbar", className)} {...rest}>
|
|
33
33
|
{toggleFor && (
|
|
34
34
|
<label className="oc-btn oc-btn--ghost oc-btn--icon" htmlFor={toggleFor} title={toggleLabel}>
|
|
35
|
-
<Icon d="
|
|
35
|
+
<Icon d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z" />
|
|
36
36
|
<span className="oc-visually-hidden">{toggleLabel}</span>
|
|
37
37
|
</label>
|
|
38
38
|
)}
|
package/src/components/Icon.tsx
CHANGED
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
import type { SVGProps } from "react";
|
|
2
2
|
|
|
3
3
|
export interface IconProps extends Omit<SVGProps<SVGSVGElement>, "d"> {
|
|
4
|
-
/** SVG path data
|
|
4
|
+
/** SVG path data on a 24x24 viewBox: a webicon path (MDI, MDI Light or Material Symbols), e.g. the path of mdi:menu. Use `children` for several shapes. */
|
|
5
5
|
d?: string;
|
|
6
|
+
/** Draw `d` as a 2px line instead of a filled shape (older line icons). */
|
|
7
|
+
line?: boolean;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
|
-
/** Inline 24x24
|
|
9
|
-
export function Icon({ d, children, className, ...rest }: IconProps) {
|
|
10
|
+
/** Inline 24x24 icon in `currentColor`: a filled webicon path (the snippets use MDI), drawn without iconify-icon. */
|
|
11
|
+
export function Icon({ d, line = false, children, className, ...rest }: IconProps) {
|
|
12
|
+
const paint = line
|
|
13
|
+
? { fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round" as const, strokeLinejoin: "round" as const }
|
|
14
|
+
: { fill: "currentColor" };
|
|
10
15
|
return (
|
|
11
|
-
<svg
|
|
12
|
-
className={className}
|
|
13
|
-
viewBox="0 0 24 24"
|
|
14
|
-
fill="none"
|
|
15
|
-
stroke="currentColor"
|
|
16
|
-
strokeWidth={2}
|
|
17
|
-
strokeLinecap="round"
|
|
18
|
-
strokeLinejoin="round"
|
|
19
|
-
aria-hidden="true"
|
|
20
|
-
{...rest}
|
|
21
|
-
>
|
|
16
|
+
<svg className={className} viewBox="0 0 24 24" aria-hidden="true" {...paint} {...rest}>
|
|
22
17
|
{d ? <path d={d} /> : children}
|
|
23
18
|
</svg>
|
|
24
19
|
);
|
|
@@ -30,10 +30,7 @@ export interface LanguageSelectProps extends Omit<SelectHTMLAttributes<HTMLSelec
|
|
|
30
30
|
|
|
31
31
|
/** Small globe drawn with currentColor. */
|
|
32
32
|
const Globe = () => (
|
|
33
|
-
<svg viewBox="0 0 24 24" fill="
|
|
34
|
-
<circle cx="12" cy="12" r="9" />
|
|
35
|
-
<path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" />
|
|
36
|
-
</svg>
|
|
33
|
+
<svg data-icon="mdi:web" viewBox="0 0 24 24" fill="currentColor"><path d="M16.36 14c.08-.66.14-1.32.14-2s-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2m-5.15 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56M14.34 14H9.66c-.1-.66-.16-1.32-.16-2s.06-1.35.16-2h4.68c.09.65.16 1.32.16 2s-.07 1.34-.16 2M12 19.96c-.83-1.2-1.5-2.53-1.91-3.96h3.82c-.41 1.43-1.08 2.76-1.91 3.96M8 8H5.08A7.92 7.92 0 0 1 9.4 4.44C8.8 5.55 8.35 6.75 8 8m-2.92 8H8c.35 1.25.8 2.45 1.4 3.56A8 8 0 0 1 5.08 16m-.82-2C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2s.06 1.34.14 2M12 4.03c.83 1.2 1.5 2.54 1.91 3.97h-3.82c.41-1.43 1.08-2.77 1.91-3.97M18.92 8h-2.95a15.7 15.7 0 0 0-1.38-3.56c1.84.63 3.37 1.9 4.33 3.56M12 2C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg>
|
|
37
34
|
);
|
|
38
35
|
|
|
39
36
|
/** `.oc-language-select`: native select in a pill with a leading icon. */
|
|
@@ -48,10 +48,11 @@ export interface MessageData {
|
|
|
48
48
|
id?: string;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
/** A webicon (MDI path data, 24x24). */
|
|
52
|
+
const svg = (d: string) => (
|
|
53
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d={d} /></svg>
|
|
53
54
|
);
|
|
54
|
-
const ICONS: Record<string, string> = { reply: "
|
|
55
|
+
const ICONS: Record<string, string> = { reply: "M8 9.8v.9l1.7.3c2.6.4 4.5 1.4 5.9 2.7c-1.7-.5-3.5-.8-5.6-.8H8v1.3L5.8 12zM10 5l-7 7l7 7v-4.1c5 0 8.5 1.6 11 5.1c-1-5-4-10-11-11", delete: "M9 3v1H4v2h1v13a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6h1V4h-5V3zM7 6h10v13H7zm2 2v9h2V8zm4 0v9h2V8z" };
|
|
55
56
|
const STATUS = { sent: "Sent", read: "Read", failed: "Failed" };
|
|
56
57
|
|
|
57
58
|
/** `.oc-message__attachments`: thumbnails share one `.oc-attachment-list`, files and images stand alone. */
|
|
@@ -93,7 +94,7 @@ export const Message = forwardRef<HTMLElement, MessageProps>(function Message(
|
|
|
93
94
|
{time != null && <time dateTime={datetime}>{time}</time>}
|
|
94
95
|
{status && (
|
|
95
96
|
<span className={cx("oc-message__status", status !== "sent" && `oc-message__status--${status}`)}>
|
|
96
|
-
{statusIcon && svg("
|
|
97
|
+
{statusIcon && svg("M.41 13.41L6 19l1.41-1.42L1.83 12m20.41-6.42L11.66 16.17L7.5 12l-1.43 1.41L11.66 19l12-12M18 7l-1.41-1.42l-6.35 6.35l1.42 1.41z")}{statusLabel ?? STATUS[status]}
|
|
97
98
|
</span>
|
|
98
99
|
)}
|
|
99
100
|
</p>
|
|
@@ -132,7 +132,7 @@ export const MessageThread = forwardRef<HTMLElement, MessageThreadProps>(functio
|
|
|
132
132
|
{menu && (
|
|
133
133
|
<>
|
|
134
134
|
<button type="button" className="oc-message-thread__icon-btn" popoverTarget={`${base}-menu`} aria-label="Conversation actions">
|
|
135
|
-
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><
|
|
135
|
+
<svg data-icon="mdi:dots-vertical" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2"/></svg>
|
|
136
136
|
</button>
|
|
137
137
|
<div className="oc-dropdown-menu" id={`${base}-menu`} {...attrs({ popover: "" })} role="menu">
|
|
138
138
|
{menu.map((m, i) => "separator" in m
|
package/src/components/User.tsx
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { forwardRef } from "react";
|
|
2
2
|
import type { HTMLAttributes, ReactNode } from "react";
|
|
3
|
-
import { cx } from "../utils";
|
|
3
|
+
import { cx, initialsOf } from "../utils";
|
|
4
4
|
import { Avatar } from "./Avatar";
|
|
5
5
|
import type { AvatarSize } from "./Avatar";
|
|
6
6
|
|
|
7
7
|
export interface UserProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
8
|
name: ReactNode;
|
|
9
9
|
email?: ReactNode;
|
|
10
|
-
/** Initials shown in the avatar. */
|
|
10
|
+
/** Initials shown in the avatar (default: from `name` when it is a string). */
|
|
11
11
|
initials?: string;
|
|
12
12
|
/** Avatar image URL (wins over initials). */
|
|
13
13
|
avatarSrc?: string;
|
|
@@ -21,7 +21,7 @@ export const User = forwardRef<HTMLDivElement, UserProps>(function User(
|
|
|
21
21
|
) {
|
|
22
22
|
return (
|
|
23
23
|
<div ref={ref} className={cx("oc-user", className)} {...rest}>
|
|
24
|
-
<Avatar size={avatarSize} src={avatarSrc}>{initials}</Avatar>
|
|
24
|
+
<Avatar size={avatarSize} src={avatarSrc}>{initials ?? initialsOf(name)}</Avatar>
|
|
25
25
|
<span className="oc-user-text">
|
|
26
26
|
<span className="oc-user-name">{name}</span>
|
|
27
27
|
{email != null && <span className="oc-user-email">{email}</span>}
|
package/src/utils.ts
CHANGED
|
@@ -19,6 +19,13 @@ export function attrs(record: Record<string, string | undefined>): object {
|
|
|
19
19
|
|
|
20
20
|
export type Slot = ReactNode;
|
|
21
21
|
|
|
22
|
+
/** "Ada Lovelace" -> "AL": the first letters of the first two words (as the Blade components do). */
|
|
23
|
+
export function initialsOf(name: unknown): string {
|
|
24
|
+
return typeof name === "string"
|
|
25
|
+
? name.trim().split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("")
|
|
26
|
+
: "";
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
/** Call a handler, then call another one. */
|
|
23
30
|
export function callAll<E>(...fns: Array<((e: E) => void) | undefined>) {
|
|
24
31
|
return (e: E) => fns.forEach((fn) => fn?.(e));
|