@krizaka/ui 3.10.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/avatar.d.ts +51 -2
- package/dist/avatar.js +90 -4
- package/dist/avatar.js.map +1 -1
- package/dist/blocks.css +9 -2
- package/dist/chat.js +1 -1
- package/dist/chunk-R6ZS4FMV.js +83 -0
- package/dist/chunk-R6ZS4FMV.js.map +1 -0
- package/dist/link-card.d.ts +7 -1
- package/dist/link-card.js +8 -5
- package/dist/link-card.js.map +1 -1
- package/dist/match-dialog.js +1 -1
- package/dist/native.d.ts +33 -2
- package/dist/native.js +28 -4
- package/dist/native.js.map +1 -1
- package/dist/showcase.js +1 -1
- package/dist/showcase.js.map +1 -1
- package/dist/site-footer.js +5 -5
- package/dist/site-footer.js.map +1 -1
- package/dist/vertical-video.js +1 -1
- package/package.json +3 -3
- package/registry/avatar.json +122 -3
- package/registry/card-grid.json +10 -1
- package/registry/examples/avatar/default.d.ts +4 -0
- package/registry/examples/avatar/default.js +12 -0
- package/registry/examples/avatar/default.tsx +13 -0
- package/registry/examples/card-grid/aligned.d.ts +4 -0
- package/registry/examples/card-grid/aligned.js +18 -0
- package/registry/examples/card-grid/aligned.tsx +16 -0
- package/registry/index.json +6 -6
- package/registry/link-card.json +17 -2
- package/registry/map.json +1 -1
- package/registry/nav-bar.json +1 -1
- package/registry/showcase.json +1 -1
- package/registry/site-footer.json +2 -2
- package/dist/chunk-CURW36FI.js +0 -47
- package/dist/chunk-CURW36FI.js.map +0 -1
package/dist/avatar.d.ts
CHANGED
|
@@ -2,6 +2,48 @@ import * as tailwind_variants from 'tailwind-variants';
|
|
|
2
2
|
import { VariantProps } from 'tailwind-variants';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
|
|
5
|
+
type DefaultAvatarBrand = "krizaka" | "orazaka" | "orochia" | "yasakia" | "yororis";
|
|
6
|
+
/** `person`: a neutral silhouette. `plain`: the brand's gradient alone, ready for initials. */
|
|
7
|
+
type DefaultAvatarKind = "person" | "plain";
|
|
8
|
+
type DefaultAvatarSize = 256 | 512;
|
|
9
|
+
declare const DEFAULT_AVATAR_BRANDS: readonly DefaultAvatarBrand[];
|
|
10
|
+
declare const DEFAULT_AVATAR_KINDS: readonly DefaultAvatarKind[];
|
|
11
|
+
declare const DEFAULT_AVATAR_SIZES: readonly DefaultAvatarSize[];
|
|
12
|
+
/** How many tones each brand has: a person always gets the same one from their seed, so a list is not one colour. */
|
|
13
|
+
declare const DEFAULT_AVATAR_TONES = 6;
|
|
14
|
+
/** Where the set lives. `v1` is the set's version: a changed picture is a new version folder, never an overwrite. */
|
|
15
|
+
declare const DEFAULT_AVATAR_BASE = "https://krizaka-assets.b-cdn.net/people/defaults/v1";
|
|
16
|
+
interface DefaultAvatar {
|
|
17
|
+
/** The brand of the product showing the avatar: its gradient, its hues. */
|
|
18
|
+
brand: DefaultAvatarBrand;
|
|
19
|
+
/** Any stable text or number of the person (their id): the same seed gives the same tone everywhere. */
|
|
20
|
+
seed?: string | number;
|
|
21
|
+
/** Force a tone, 1 to 6, instead of deriving it from `seed`. */
|
|
22
|
+
tone?: number;
|
|
23
|
+
/**
|
|
24
|
+
* `person` (default): a silhouette. `plain`: the gradient alone.
|
|
25
|
+
* @default "person"
|
|
26
|
+
*/
|
|
27
|
+
kind?: DefaultAvatarKind;
|
|
28
|
+
}
|
|
29
|
+
/** The two stops (hex) of tone `tone` (1 to 6) of a brand. */
|
|
30
|
+
declare function defaultAvatarTone(brand: DefaultAvatarBrand, tone: number): readonly [string, string];
|
|
31
|
+
/** 1 to 6 from a seed (FNV-1a): stable across renders, devices and products. */
|
|
32
|
+
declare function defaultAvatarToneOf(seed: string | number | undefined): number;
|
|
33
|
+
/** The file name of a picture in the set: `person-3` + 256 → `person-3-256.webp`; `svg` has no size. */
|
|
34
|
+
declare function defaultAvatarFile(kind: DefaultAvatarKind, tone: number, size: DefaultAvatarSize | "svg"): string;
|
|
35
|
+
/**
|
|
36
|
+
* The URL of a default avatar: `defaultAvatarUrl({ brand: "orochia", seed: user.id })`.
|
|
37
|
+
* @param size 256 for lists, 512 for a profile; `"svg"` for the vector.
|
|
38
|
+
* @param base Another host or version of the set (a self-hosted copy); the default is the Krizaka CDN.
|
|
39
|
+
*/
|
|
40
|
+
declare function defaultAvatarUrl(avatar: DefaultAvatar, { size, base }?: {
|
|
41
|
+
size?: DefaultAvatarSize | "svg";
|
|
42
|
+
base?: string;
|
|
43
|
+
}): string;
|
|
44
|
+
/** The SVG source of a default avatar, 512 × 512 and square (the Avatar clips it round). */
|
|
45
|
+
declare function defaultAvatarSvg(avatar: DefaultAvatar): string;
|
|
46
|
+
|
|
5
47
|
declare const avatar: tailwind_variants.TVReturnType<{
|
|
6
48
|
size: {
|
|
7
49
|
xs: {
|
|
@@ -80,13 +122,20 @@ type AvatarProps = Omit<React.ComponentProps<"span">, "children"> & {
|
|
|
80
122
|
alt?: string;
|
|
81
123
|
/** Shown while the image loads, when it fails or when there is none: initials, an icon. */
|
|
82
124
|
fallback?: React.ReactNode;
|
|
125
|
+
/** The picture to show instead when `src` is missing or fails to load (an image URL). The `fallback` still covers its own loading. */
|
|
126
|
+
fallbackSrc?: string;
|
|
127
|
+
/**
|
|
128
|
+
* The Krizaka default picture when the person has no photo (or it fails): their brand's gradient and a silhouette,
|
|
129
|
+
* one of six tones from `seed` — `defaultAvatar={{ brand: "orochia", seed: user.id }}`. `fallbackSrc` wins over it.
|
|
130
|
+
*/
|
|
131
|
+
defaultAvatar?: DefaultAvatar;
|
|
83
132
|
/**
|
|
84
133
|
* xs · sm · md · lg · xl.
|
|
85
134
|
* @default "md"
|
|
86
135
|
*/
|
|
87
136
|
size?: AvatarSize;
|
|
88
137
|
};
|
|
89
|
-
declare function AvatarRoot({ src, alt, fallback, size, className, ...props }: AvatarProps): React.JSX.Element;
|
|
138
|
+
declare function AvatarRoot({ src, alt, fallback, fallbackSrc, defaultAvatar, size, className, ...props }: AvatarProps): React.JSX.Element;
|
|
90
139
|
type AvatarGroupProps = React.ComponentProps<"div"> & {
|
|
91
140
|
/** How many avatars are shown; the rest becomes a `+n` avatar. */
|
|
92
141
|
max?: number;
|
|
@@ -100,4 +149,4 @@ declare const Avatar: typeof AvatarRoot & {
|
|
|
100
149
|
Group: typeof AvatarGroup;
|
|
101
150
|
};
|
|
102
151
|
|
|
103
|
-
export { Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, AvatarRoot, type AvatarSize };
|
|
152
|
+
export { Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, AvatarRoot, type AvatarSize, DEFAULT_AVATAR_BASE, DEFAULT_AVATAR_BRANDS, DEFAULT_AVATAR_KINDS, DEFAULT_AVATAR_SIZES, DEFAULT_AVATAR_TONES, type DefaultAvatar, type DefaultAvatarBrand, type DefaultAvatarKind, type DefaultAvatarSize, defaultAvatarFile, defaultAvatarSvg, defaultAvatarTone, defaultAvatarToneOf, defaultAvatarUrl };
|
package/dist/avatar.js
CHANGED
|
@@ -4,6 +4,69 @@
|
|
|
4
4
|
import { Avatar as AvatarPrimitive } from "radix-ui";
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
import { tv } from "tailwind-variants";
|
|
7
|
+
|
|
8
|
+
// src/avatar/default-avatar.ts
|
|
9
|
+
var DEFAULT_AVATAR_BRANDS = ["krizaka", "orazaka", "orochia", "yasakia", "yororis"];
|
|
10
|
+
var DEFAULT_AVATAR_KINDS = ["person", "plain"];
|
|
11
|
+
var DEFAULT_AVATAR_SIZES = [256, 512];
|
|
12
|
+
var DEFAULT_AVATAR_TONES = 6;
|
|
13
|
+
var DEFAULT_AVATAR_BASE = "https://krizaka-assets.b-cdn.net/people/defaults/v1";
|
|
14
|
+
var BRANDS = {
|
|
15
|
+
krizaka: { from: [217, 92, 53], to: [217, 92, 68], spread: 14 },
|
|
16
|
+
orazaka: { from: [26, 90.5, 37.1], to: [26, 92, 45], spread: 7 },
|
|
17
|
+
orochia: { from: [262.1, 83.3, 57.8], to: [328.6, 85.5, 70.2], spread: 12 },
|
|
18
|
+
yasakia: { from: [156, 92, 32], to: [178, 90, 32], spread: 12 },
|
|
19
|
+
yororis: { from: [34, 46, 44], to: [104, 28, 42], spread: 10 }
|
|
20
|
+
};
|
|
21
|
+
var MIN_CONTRAST = 3.2;
|
|
22
|
+
var OFFSETS = [0, -1, 1, -2, 2, 3];
|
|
23
|
+
function hslToHex([h, s, l]) {
|
|
24
|
+
const sat = s / 100;
|
|
25
|
+
const light = l / 100;
|
|
26
|
+
const k = (n) => (n + (h % 360 + 360) % 360 / 30) % 12;
|
|
27
|
+
const a = sat * Math.min(light, 1 - light);
|
|
28
|
+
const channel = (n) => Math.round(255 * (light - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)))));
|
|
29
|
+
return `#${[0, 8, 4].map((n) => channel(n).toString(16).padStart(2, "0")).join("")}`;
|
|
30
|
+
}
|
|
31
|
+
var channelLuminance = (v) => v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
32
|
+
var whiteContrast = (hex) => {
|
|
33
|
+
const [r, g, b] = [1, 3, 5].map((i) => channelLuminance(Number.parseInt(hex.slice(i, i + 2), 16) / 255));
|
|
34
|
+
return 1.05 / (0.2126 * r + 0.7152 * g + 0.0722 * b + 0.05);
|
|
35
|
+
};
|
|
36
|
+
function readable([h, s, l]) {
|
|
37
|
+
let hex = hslToHex([h, s, l]);
|
|
38
|
+
for (let light = l; whiteContrast(hex) < MIN_CONTRAST && light > 10; light -= 1) hex = hslToHex([h, s, light]);
|
|
39
|
+
return hex;
|
|
40
|
+
}
|
|
41
|
+
function defaultAvatarTone(brand, tone) {
|
|
42
|
+
const b = BRANDS[brand];
|
|
43
|
+
const o = OFFSETS[(clampTone(tone) - 1) % OFFSETS.length] * b.spread;
|
|
44
|
+
return [readable([b.from[0] + o, b.from[1], b.from[2]]), readable([b.to[0] + o, b.to[1], b.to[2]])];
|
|
45
|
+
}
|
|
46
|
+
var clampTone = (tone) => Math.min(DEFAULT_AVATAR_TONES, Math.max(1, Math.round(tone)));
|
|
47
|
+
function defaultAvatarToneOf(seed) {
|
|
48
|
+
if (seed === void 0) return 1;
|
|
49
|
+
let h = 2166136261;
|
|
50
|
+
for (const ch of String(seed)) {
|
|
51
|
+
h ^= ch.codePointAt(0) ?? 0;
|
|
52
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
53
|
+
}
|
|
54
|
+
return h % DEFAULT_AVATAR_TONES + 1;
|
|
55
|
+
}
|
|
56
|
+
var resolveTone = (a) => a.tone === void 0 ? defaultAvatarToneOf(a.seed) : clampTone(a.tone);
|
|
57
|
+
function defaultAvatarFile(kind, tone, size) {
|
|
58
|
+
return size === "svg" ? `${kind}-${tone}.svg` : `${kind}-${tone}-${size}.webp`;
|
|
59
|
+
}
|
|
60
|
+
function defaultAvatarUrl(avatar2, { size = 256, base = DEFAULT_AVATAR_BASE } = {}) {
|
|
61
|
+
return `${base.replace(/\/+$/, "")}/${avatar2.brand}/${defaultAvatarFile(avatar2.kind ?? "person", resolveTone(avatar2), size)}`;
|
|
62
|
+
}
|
|
63
|
+
function defaultAvatarSvg(avatar2) {
|
|
64
|
+
const [from, to] = defaultAvatarTone(avatar2.brand, resolveTone(avatar2));
|
|
65
|
+
const person = (avatar2.kind ?? "person") === "person" ? `<circle cx="256" cy="200" r="86" fill="#fff" fill-opacity=".92"/><path d="M84 512c0-108 78-182 172-182s172 74 172 182z" fill="#fff" fill-opacity=".92"/>` : "";
|
|
66
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="${from}"/><stop offset="1" stop-color="${to}"/></linearGradient><radialGradient id="l" cx=".28" cy=".18" r=".85"><stop offset="0" stop-color="#fff" stop-opacity=".24"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></radialGradient></defs><rect width="512" height="512" fill="url(#g)"/><rect width="512" height="512" fill="url(#l)"/>${person}</svg>`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/avatar/avatar.tsx
|
|
7
70
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
71
|
var avatar = tv({
|
|
9
72
|
slots: {
|
|
@@ -23,11 +86,24 @@ var avatar = tv({
|
|
|
23
86
|
},
|
|
24
87
|
defaultVariants: { size: "md" }
|
|
25
88
|
});
|
|
26
|
-
function AvatarRoot({ src, alt = "", fallback, size, className, ...props }) {
|
|
89
|
+
function AvatarRoot({ src, alt = "", fallback, fallbackSrc, defaultAvatar, size, className, ...props }) {
|
|
27
90
|
const s = avatar({ size });
|
|
91
|
+
const [failed, setFailed] = React.useState(null);
|
|
92
|
+
const backup = fallbackSrc ?? (defaultAvatar ? defaultAvatarUrl(defaultAvatar, { size: size === "xl" || size === "lg" ? 512 : 256 }) : void 0);
|
|
93
|
+
const shown = src && failed !== src ? src : backup;
|
|
28
94
|
return /* @__PURE__ */ jsxs(AvatarPrimitive.Root, { "data-size": size ?? "md", className: s.root({ className }), ...props, children: [
|
|
29
|
-
|
|
30
|
-
|
|
95
|
+
shown && /* @__PURE__ */ jsx(
|
|
96
|
+
AvatarPrimitive.Image,
|
|
97
|
+
{
|
|
98
|
+
src: shown,
|
|
99
|
+
alt,
|
|
100
|
+
className: s.image(),
|
|
101
|
+
onLoadingStatusChange: (status) => {
|
|
102
|
+
if (status === "error" && shown === src) setFailed(src);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
),
|
|
106
|
+
/* @__PURE__ */ jsx(AvatarPrimitive.Fallback, { delayMs: shown ? 300 : 0, className: s.fallback(), children: fallback })
|
|
31
107
|
] });
|
|
32
108
|
}
|
|
33
109
|
function AvatarGroup({ max, size, className, children, ...props }) {
|
|
@@ -43,6 +119,16 @@ var Avatar = Object.assign(AvatarRoot, { Group: AvatarGroup });
|
|
|
43
119
|
export {
|
|
44
120
|
Avatar,
|
|
45
121
|
AvatarGroup,
|
|
46
|
-
AvatarRoot
|
|
122
|
+
AvatarRoot,
|
|
123
|
+
DEFAULT_AVATAR_BASE,
|
|
124
|
+
DEFAULT_AVATAR_BRANDS,
|
|
125
|
+
DEFAULT_AVATAR_KINDS,
|
|
126
|
+
DEFAULT_AVATAR_SIZES,
|
|
127
|
+
DEFAULT_AVATAR_TONES,
|
|
128
|
+
defaultAvatarFile,
|
|
129
|
+
defaultAvatarSvg,
|
|
130
|
+
defaultAvatarTone,
|
|
131
|
+
defaultAvatarToneOf,
|
|
132
|
+
defaultAvatarUrl
|
|
47
133
|
};
|
|
48
134
|
//# sourceMappingURL=avatar.js.map
|
package/dist/avatar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/avatar/avatar.tsx"],"sourcesContent":["// Client: Radix tracks the image's loading state and shows the fallback until it has loaded (or when it fails).\nimport { Avatar as AvatarPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nconst avatar = tv({\n slots: {\n root: \"relative inline-flex shrink-0 overflow-hidden rounded-full bg-surface-3 align-middle\",\n image: \"h-full w-full object-cover\",\n fallback: \"flex h-full w-full items-center justify-center font-semibold uppercase text-fg-secondary\",\n group: \"flex items-center -space-x-2 [&>*]:ring-2 [&>*]:ring-surface-0\",\n },\n variants: {\n size: {\n xs: { root: \"h-5 w-5 text-[9px]\" },\n sm: { root: \"h-8 w-8 text-xs\" },\n md: { root: \"h-10 w-10 text-sm\" },\n lg: { root: \"h-14 w-14 text-base\" },\n xl: { root: \"h-20 w-20 text-lg\" },\n },\n },\n defaultVariants: { size: \"md\" },\n});\n\nexport type AvatarSize = NonNullable<VariantProps<typeof avatar>[\"size\"]>;\n\nexport type AvatarProps = Omit<React.ComponentProps<\"span\">, \"children\"> & {\n /** The image's URL; `null` or nothing shows the `fallback`. */\n src?: string | null;\n /** The image's text alternative (the person's name); empty when a visible name sits next to the avatar. */\n alt?: string;\n /** Shown while the image loads, when it fails or when there is none: initials, an icon. */\n fallback?: React.ReactNode;\n /**\n * xs · sm · md · lg · xl.\n * @default \"md\"\n */\n size?: AvatarSize;\n};\n\nexport function AvatarRoot({ src, alt = \"\", fallback, size, className, ...props }: AvatarProps) {\n const s = avatar({ size });\n return (\n <AvatarPrimitive.Root data-size={size ?? \"md\"} className={s.root({ className })} {...props}>\n {src && <AvatarPrimitive.Image src={src} alt={alt} className={s.image()} />}\n <AvatarPrimitive.Fallback delayMs={src ? 300 : 0} className={s.fallback()}>\n {fallback}\n </AvatarPrimitive.Fallback>\n </AvatarPrimitive.Root>\n );\n}\n\nexport type AvatarGroupProps = React.ComponentProps<\"div\"> & {\n /** How many avatars are shown; the rest becomes a `+n` avatar. */\n max?: number;\n /** The size of the `+n` avatar (match the avatars of the group). */\n size?: AvatarSize;\n};\n\n/** Overlapping avatars; past `max`, a `+n` avatar counts the rest. */\nexport function AvatarGroup({ max, size, className, children, ...props }: AvatarGroupProps) {\n const items = React.Children.toArray(children);\n const shown = max === undefined ? items : items.slice(0, max);\n const rest = items.length - shown.length;\n return (\n <div className={avatar().group({ className })} {...props}>\n {shown}\n {rest > 0 && <AvatarRoot size={size} fallback={`+${rest}`} />}\n </div>\n );\n}\n\n/** `Avatar` (an image with a fallback) and `Avatar.Group`. */\nexport const Avatar = Object.assign(AvatarRoot, { Group: AvatarGroup });\n"],"mappings":";;;AACA,SAAS,UAAU,uBAAuB;AAC1C,YAAY,WAAW;AACvB,SAAS,UAA6B;AAwClC,SACU,KADV;AAtCJ,IAAM,SAAS,GAAG;AAAA,EAChB,OAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,IAAI,EAAE,MAAM,qBAAqB;AAAA,MACjC,IAAI,EAAE,MAAM,kBAAkB;AAAA,MAC9B,IAAI,EAAE,MAAM,oBAAoB;AAAA,MAChC,IAAI,EAAE,MAAM,sBAAsB;AAAA,MAClC,IAAI,EAAE,MAAM,oBAAoB;AAAA,IAClC;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CAAC;AAkBM,SAAS,WAAW,EAAE,KAAK,MAAM,IAAI,UAAU,MAAM,WAAW,GAAG,MAAM,GAAgB;AAC9F,QAAM,IAAI,OAAO,EAAE,KAAK,CAAC;AACzB,SACE,qBAAC,gBAAgB,MAAhB,EAAqB,aAAW,QAAQ,MAAM,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,OAClF;AAAA,WAAO,oBAAC,gBAAgB,OAAhB,EAAsB,KAAU,KAAU,WAAW,EAAE,MAAM,GAAG;AAAA,IACzE,oBAAC,gBAAgB,UAAhB,EAAyB,SAAS,MAAM,MAAM,GAAG,WAAW,EAAE,SAAS,GACrE,oBACH;AAAA,KACF;AAEJ;AAUO,SAAS,YAAY,EAAE,KAAK,MAAM,WAAW,UAAU,GAAG,MAAM,GAAqB;AAC1F,QAAM,QAAc,eAAS,QAAQ,QAAQ;AAC7C,QAAM,QAAQ,QAAQ,SAAY,QAAQ,MAAM,MAAM,GAAG,GAAG;AAC5D,QAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SACE,qBAAC,SAAI,WAAW,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,GAAI,GAAG,OAChD;AAAA;AAAA,IACA,OAAO,KAAK,oBAAC,cAAW,MAAY,UAAU,IAAI,IAAI,IAAI;AAAA,KAC7D;AAEJ;AAGO,IAAM,SAAS,OAAO,OAAO,YAAY,EAAE,OAAO,YAAY,CAAC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/avatar/avatar.tsx","../src/avatar/default-avatar.ts"],"sourcesContent":["// Client: Radix tracks the image's loading state and shows the fallback until it has loaded (or when it fails).\nimport { Avatar as AvatarPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { type DefaultAvatar, defaultAvatarUrl } from \"./default-avatar\";\n\nconst avatar = tv({\n slots: {\n root: \"relative inline-flex shrink-0 overflow-hidden rounded-full bg-surface-3 align-middle\",\n image: \"h-full w-full object-cover\",\n fallback: \"flex h-full w-full items-center justify-center font-semibold uppercase text-fg-secondary\",\n group: \"flex items-center -space-x-2 [&>*]:ring-2 [&>*]:ring-surface-0\",\n },\n variants: {\n size: {\n xs: { root: \"h-5 w-5 text-[9px]\" },\n sm: { root: \"h-8 w-8 text-xs\" },\n md: { root: \"h-10 w-10 text-sm\" },\n lg: { root: \"h-14 w-14 text-base\" },\n xl: { root: \"h-20 w-20 text-lg\" },\n },\n },\n defaultVariants: { size: \"md\" },\n});\n\nexport type AvatarSize = NonNullable<VariantProps<typeof avatar>[\"size\"]>;\n\nexport type AvatarProps = Omit<React.ComponentProps<\"span\">, \"children\"> & {\n /** The image's URL; `null` or nothing shows the `fallback`. */\n src?: string | null;\n /** The image's text alternative (the person's name); empty when a visible name sits next to the avatar. */\n alt?: string;\n /** Shown while the image loads, when it fails or when there is none: initials, an icon. */\n fallback?: React.ReactNode;\n /** The picture to show instead when `src` is missing or fails to load (an image URL). The `fallback` still covers its own loading. */\n fallbackSrc?: string;\n /**\n * The Krizaka default picture when the person has no photo (or it fails): their brand's gradient and a silhouette,\n * one of six tones from `seed` — `defaultAvatar={{ brand: \"orochia\", seed: user.id }}`. `fallbackSrc` wins over it.\n */\n defaultAvatar?: DefaultAvatar;\n /**\n * xs · sm · md · lg · xl.\n * @default \"md\"\n */\n size?: AvatarSize;\n};\n\nexport function AvatarRoot({ src, alt = \"\", fallback, fallbackSrc, defaultAvatar, size, className, ...props }: AvatarProps) {\n const s = avatar({ size });\n const [failed, setFailed] = React.useState<string | null>(null);\n // 512 px for the big sizes, 256 for the others: sharp on a 2x screen either way.\n const backup = fallbackSrc ?? (defaultAvatar ? defaultAvatarUrl(defaultAvatar, { size: size === \"xl\" || size === \"lg\" ? 512 : 256 }) : undefined);\n const shown = src && failed !== src ? src : backup;\n return (\n <AvatarPrimitive.Root data-size={size ?? \"md\"} className={s.root({ className })} {...props}>\n {shown && (\n <AvatarPrimitive.Image\n src={shown}\n alt={alt}\n className={s.image()}\n onLoadingStatusChange={(status) => {\n if (status === \"error\" && shown === src) setFailed(src);\n }}\n />\n )}\n <AvatarPrimitive.Fallback delayMs={shown ? 300 : 0} className={s.fallback()}>\n {fallback}\n </AvatarPrimitive.Fallback>\n </AvatarPrimitive.Root>\n );\n}\n\nexport type AvatarGroupProps = React.ComponentProps<\"div\"> & {\n /** How many avatars are shown; the rest becomes a `+n` avatar. */\n max?: number;\n /** The size of the `+n` avatar (match the avatars of the group). */\n size?: AvatarSize;\n};\n\n/** Overlapping avatars; past `max`, a `+n` avatar counts the rest. */\nexport function AvatarGroup({ max, size, className, children, ...props }: AvatarGroupProps) {\n const items = React.Children.toArray(children);\n const shown = max === undefined ? items : items.slice(0, max);\n const rest = items.length - shown.length;\n return (\n <div className={avatar().group({ className })} {...props}>\n {shown}\n {rest > 0 && <AvatarRoot size={size} fallback={`+${rest}`} />}\n </div>\n );\n}\n\n/** `Avatar` (an image with a fallback) and `Avatar.Group`. */\nexport const Avatar = Object.assign(AvatarRoot, { Group: AvatarGroup });\n","// The default profile pictures of the Krizaka family: what a product shows when a person has no photo.\n// They are generated from this file (tools/export-brand.mjs renders them to SVG and WebP and uploads them to Bunny,\n// `people/defaults/v1/<brand>/…`), so the pictures and the URLs that name them cannot drift apart.\n\nexport type DefaultAvatarBrand = \"krizaka\" | \"orazaka\" | \"orochia\" | \"yasakia\" | \"yororis\";\n/** `person`: a neutral silhouette. `plain`: the brand's gradient alone, ready for initials. */\nexport type DefaultAvatarKind = \"person\" | \"plain\";\nexport type DefaultAvatarSize = 256 | 512;\n\nexport const DEFAULT_AVATAR_BRANDS: readonly DefaultAvatarBrand[] = [\"krizaka\", \"orazaka\", \"orochia\", \"yasakia\", \"yororis\"];\nexport const DEFAULT_AVATAR_KINDS: readonly DefaultAvatarKind[] = [\"person\", \"plain\"];\nexport const DEFAULT_AVATAR_SIZES: readonly DefaultAvatarSize[] = [256, 512];\n/** How many tones each brand has: a person always gets the same one from their seed, so a list is not one colour. */\nexport const DEFAULT_AVATAR_TONES = 6;\n\n/** Where the set lives. `v1` is the set's version: a changed picture is a new version folder, never an overwrite. */\nexport const DEFAULT_AVATAR_BASE = \"https://krizaka-assets.b-cdn.net/people/defaults/v1\";\n\nexport interface DefaultAvatar {\n /** The brand of the product showing the avatar: its gradient, its hues. */\n brand: DefaultAvatarBrand;\n /** Any stable text or number of the person (their id): the same seed gives the same tone everywhere. */\n seed?: string | number;\n /** Force a tone, 1 to 6, instead of deriving it from `seed`. */\n tone?: number;\n /**\n * `person` (default): a silhouette. `plain`: the gradient alone.\n * @default \"person\"\n */\n kind?: DefaultAvatarKind;\n}\n\n/** The mark gradient of each brand (BRAND.md, `$extensions[\"com.krizaka\"].mark`) as [hue, saturation %, lightness %] and the hue spread of its tones. */\nconst BRANDS: Record<DefaultAvatarBrand, { from: [number, number, number]; to: [number, number, number]; spread: number }> = {\n krizaka: { from: [217, 92, 53], to: [217, 92, 68], spread: 14 },\n orazaka: { from: [26, 90.5, 37.1], to: [26, 92, 45], spread: 7 },\n orochia: { from: [262.1, 83.3, 57.8], to: [328.6, 85.5, 70.2], spread: 12 },\n yasakia: { from: [156, 92, 32], to: [178, 90, 32], spread: 12 },\n yororis: { from: [34, 46, 44], to: [104, 28, 42], spread: 10 },\n};\n\n/** White on top of a stop must stay visible: a stop is darkened until the contrast reaches this (WCAG 1.4.11, non-text). */\nconst MIN_CONTRAST = 3.2;\nconst OFFSETS = [0, -1, 1, -2, 2, 3] as const;\n\nfunction hslToHex([h, s, l]: [number, number, number]): string {\n const sat = s / 100;\n const light = l / 100;\n const k = (n: number) => (n + (((h % 360) + 360) % 360) / 30) % 12;\n const a = sat * Math.min(light, 1 - light);\n const channel = (n: number) => Math.round(255 * (light - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)))));\n return `#${[0, 8, 4].map((n) => channel(n).toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\nconst channelLuminance = (v: number) => (v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4);\nconst whiteContrast = (hex: string) => {\n const [r, g, b] = [1, 3, 5].map((i) => channelLuminance(Number.parseInt(hex.slice(i, i + 2), 16) / 255));\n return 1.05 / (0.2126 * r! + 0.7152 * g! + 0.0722 * b! + 0.05);\n};\n/** The stop, darkened one point of lightness at a time until white reads on it. */\nfunction readable([h, s, l]: [number, number, number]): string {\n let hex = hslToHex([h, s, l]);\n for (let light = l; whiteContrast(hex) < MIN_CONTRAST && light > 10; light -= 1) hex = hslToHex([h, s, light]);\n return hex;\n}\n\n/** The two stops (hex) of tone `tone` (1 to 6) of a brand. */\nexport function defaultAvatarTone(brand: DefaultAvatarBrand, tone: number): readonly [string, string] {\n const b = BRANDS[brand];\n const o = OFFSETS[(clampTone(tone) - 1) % OFFSETS.length] * b.spread;\n return [readable([b.from[0] + o, b.from[1], b.from[2]]), readable([b.to[0] + o, b.to[1], b.to[2]])];\n}\n\nconst clampTone = (tone: number) => Math.min(DEFAULT_AVATAR_TONES, Math.max(1, Math.round(tone)));\n\n/** 1 to 6 from a seed (FNV-1a): stable across renders, devices and products. */\nexport function defaultAvatarToneOf(seed: string | number | undefined): number {\n if (seed === undefined) return 1;\n let h = 0x811c9dc5;\n for (const ch of String(seed)) {\n h ^= ch.codePointAt(0) ?? 0;\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return (h % DEFAULT_AVATAR_TONES) + 1;\n}\n\nconst resolveTone = (a: DefaultAvatar) => (a.tone === undefined ? defaultAvatarToneOf(a.seed) : clampTone(a.tone));\n\n/** The file name of a picture in the set: `person-3` + 256 → `person-3-256.webp`; `svg` has no size. */\nexport function defaultAvatarFile(kind: DefaultAvatarKind, tone: number, size: DefaultAvatarSize | \"svg\"): string {\n return size === \"svg\" ? `${kind}-${tone}.svg` : `${kind}-${tone}-${size}.webp`;\n}\n\n/**\n * The URL of a default avatar: `defaultAvatarUrl({ brand: \"orochia\", seed: user.id })`.\n * @param size 256 for lists, 512 for a profile; `\"svg\"` for the vector.\n * @param base Another host or version of the set (a self-hosted copy); the default is the Krizaka CDN.\n */\nexport function defaultAvatarUrl(avatar: DefaultAvatar, { size = 256, base = DEFAULT_AVATAR_BASE }: { size?: DefaultAvatarSize | \"svg\"; base?: string } = {}): string {\n return `${base.replace(/\\/+$/, \"\")}/${avatar.brand}/${defaultAvatarFile(avatar.kind ?? \"person\", resolveTone(avatar), size)}`;\n}\n\n/** The SVG source of a default avatar, 512 × 512 and square (the Avatar clips it round). */\nexport function defaultAvatarSvg(avatar: DefaultAvatar): string {\n const [from, to] = defaultAvatarTone(avatar.brand, resolveTone(avatar));\n const person =\n (avatar.kind ?? \"person\") === \"person\"\n ? `<circle cx=\"256\" cy=\"200\" r=\"86\" fill=\"#fff\" fill-opacity=\".92\"/><path d=\"M84 512c0-108 78-182 172-182s172 74 172 182z\" fill=\"#fff\" fill-opacity=\".92\"/>`\n : \"\";\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\" width=\"512\" height=\"512\"><defs><linearGradient id=\"g\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\"><stop offset=\"0\" stop-color=\"${from}\"/><stop offset=\"1\" stop-color=\"${to}\"/></linearGradient><radialGradient id=\"l\" cx=\".28\" cy=\".18\" r=\".85\"><stop offset=\"0\" stop-color=\"#fff\" stop-opacity=\".24\"/><stop offset=\"1\" stop-color=\"#fff\" stop-opacity=\"0\"/></radialGradient></defs><rect width=\"512\" height=\"512\" fill=\"url(#g)\"/><rect width=\"512\" height=\"512\" fill=\"url(#l)\"/>${person}</svg>`;\n}\n"],"mappings":"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","names":["avatar"]}
|
package/dist/blocks.css
CHANGED
|
@@ -247,11 +247,18 @@ a.kz-fact:focus-visible { outline: 2px solid var(--kz-ring, var(--kz-accent)); o
|
|
|
247
247
|
text-decoration: none;
|
|
248
248
|
color: var(--kz-text-primary);
|
|
249
249
|
}
|
|
250
|
-
.kz-link-card[data-layout="stack"] {
|
|
250
|
+
.kz-link-card[data-layout="stack"] { display: flex; flex-direction: column; align-items: stretch; gap: 12px; }
|
|
251
|
+
.kz-link-card[data-layout="stack"] > .kz-block-icon { align-self: flex-start; }
|
|
252
|
+
.kz-link-card[data-layout="stack"] > .kz-link-card-body { flex: 1; }
|
|
251
253
|
.kz-link-card:focus-visible { outline: 2px solid var(--kz-ring, var(--kz-accent)); outline-offset: 2px; }
|
|
252
254
|
.kz-link-card > .kz-block-icon { grid-row: span 3; }
|
|
253
255
|
.kz-link-card[data-layout="stack"] > .kz-block-icon { grid-row: auto; }
|
|
254
|
-
.kz-link-card-body { display:
|
|
256
|
+
.kz-link-card-body { display: flex; flex-direction: column; gap: 5px; min-width: 0; align-self: stretch; }
|
|
257
|
+
.kz-link-card:not([data-layout="stack"]) > .kz-block-icon + .kz-link-card-body { grid-column: 2; grid-row: 1 / span 3; }
|
|
258
|
+
/* The foot (facts + the way in) is pinned to the bottom of a card taller than its text, so a row of cards at one height
|
|
259
|
+
keeps its status and its link on one line. `data-footer="top"` keeps them under the text. */
|
|
260
|
+
.kz-link-card-foot { display: grid; gap: 5px; margin-top: auto; }
|
|
261
|
+
.kz-link-card[data-footer="top"] .kz-link-card-foot { margin-top: 0; }
|
|
255
262
|
.kz-link-card-meta { margin: 0; font-family: var(--kz-font-mono); font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--kz-hue-text, var(--kz-accent-text)); }
|
|
256
263
|
.kz-link-card-title { margin: 0; font-family: var(--kz-font-display); font-size: 16.5px; font-weight: 750; letter-spacing: -0.012em; line-height: 1.3; color: var(--kz-text-primary); }
|
|
257
264
|
.kz-link-card-text { margin: 0; font-size: 14px; line-height: 1.55; color: var(--kz-text-secondary); }
|
package/dist/chat.js
CHANGED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/avatar/avatar.tsx
|
|
4
|
+
import { Avatar as AvatarPrimitive } from "radix-ui";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { tv } from "tailwind-variants";
|
|
7
|
+
|
|
8
|
+
// src/avatar/default-avatar.ts
|
|
9
|
+
var DEFAULT_AVATAR_TONES = 6;
|
|
10
|
+
var DEFAULT_AVATAR_BASE = "https://krizaka-assets.b-cdn.net/people/defaults/v1";
|
|
11
|
+
var clampTone = (tone) => Math.min(DEFAULT_AVATAR_TONES, Math.max(1, Math.round(tone)));
|
|
12
|
+
function defaultAvatarToneOf(seed) {
|
|
13
|
+
if (seed === void 0) return 1;
|
|
14
|
+
let h = 2166136261;
|
|
15
|
+
for (const ch of String(seed)) {
|
|
16
|
+
h ^= ch.codePointAt(0) ?? 0;
|
|
17
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
18
|
+
}
|
|
19
|
+
return h % DEFAULT_AVATAR_TONES + 1;
|
|
20
|
+
}
|
|
21
|
+
var resolveTone = (a) => a.tone === void 0 ? defaultAvatarToneOf(a.seed) : clampTone(a.tone);
|
|
22
|
+
function defaultAvatarFile(kind, tone, size) {
|
|
23
|
+
return size === "svg" ? `${kind}-${tone}.svg` : `${kind}-${tone}-${size}.webp`;
|
|
24
|
+
}
|
|
25
|
+
function defaultAvatarUrl(avatar2, { size = 256, base = DEFAULT_AVATAR_BASE } = {}) {
|
|
26
|
+
return `${base.replace(/\/+$/, "")}/${avatar2.brand}/${defaultAvatarFile(avatar2.kind ?? "person", resolveTone(avatar2), size)}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/avatar/avatar.tsx
|
|
30
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
31
|
+
var avatar = tv({
|
|
32
|
+
slots: {
|
|
33
|
+
root: "relative inline-flex shrink-0 overflow-hidden rounded-full bg-surface-3 align-middle",
|
|
34
|
+
image: "h-full w-full object-cover",
|
|
35
|
+
fallback: "flex h-full w-full items-center justify-center font-semibold uppercase text-fg-secondary",
|
|
36
|
+
group: "flex items-center -space-x-2 [&>*]:ring-2 [&>*]:ring-surface-0"
|
|
37
|
+
},
|
|
38
|
+
variants: {
|
|
39
|
+
size: {
|
|
40
|
+
xs: { root: "h-5 w-5 text-[9px]" },
|
|
41
|
+
sm: { root: "h-8 w-8 text-xs" },
|
|
42
|
+
md: { root: "h-10 w-10 text-sm" },
|
|
43
|
+
lg: { root: "h-14 w-14 text-base" },
|
|
44
|
+
xl: { root: "h-20 w-20 text-lg" }
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
defaultVariants: { size: "md" }
|
|
48
|
+
});
|
|
49
|
+
function AvatarRoot({ src, alt = "", fallback, fallbackSrc, defaultAvatar, size, className, ...props }) {
|
|
50
|
+
const s = avatar({ size });
|
|
51
|
+
const [failed, setFailed] = React.useState(null);
|
|
52
|
+
const backup = fallbackSrc ?? (defaultAvatar ? defaultAvatarUrl(defaultAvatar, { size: size === "xl" || size === "lg" ? 512 : 256 }) : void 0);
|
|
53
|
+
const shown = src && failed !== src ? src : backup;
|
|
54
|
+
return /* @__PURE__ */ jsxs(AvatarPrimitive.Root, { "data-size": size ?? "md", className: s.root({ className }), ...props, children: [
|
|
55
|
+
shown && /* @__PURE__ */ jsx(
|
|
56
|
+
AvatarPrimitive.Image,
|
|
57
|
+
{
|
|
58
|
+
src: shown,
|
|
59
|
+
alt,
|
|
60
|
+
className: s.image(),
|
|
61
|
+
onLoadingStatusChange: (status) => {
|
|
62
|
+
if (status === "error" && shown === src) setFailed(src);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
),
|
|
66
|
+
/* @__PURE__ */ jsx(AvatarPrimitive.Fallback, { delayMs: shown ? 300 : 0, className: s.fallback(), children: fallback })
|
|
67
|
+
] });
|
|
68
|
+
}
|
|
69
|
+
function AvatarGroup({ max, size, className, children, ...props }) {
|
|
70
|
+
const items = React.Children.toArray(children);
|
|
71
|
+
const shown = max === void 0 ? items : items.slice(0, max);
|
|
72
|
+
const rest = items.length - shown.length;
|
|
73
|
+
return /* @__PURE__ */ jsxs("div", { className: avatar().group({ className }), ...props, children: [
|
|
74
|
+
shown,
|
|
75
|
+
rest > 0 && /* @__PURE__ */ jsx(AvatarRoot, { size, fallback: `+${rest}` })
|
|
76
|
+
] });
|
|
77
|
+
}
|
|
78
|
+
var Avatar = Object.assign(AvatarRoot, { Group: AvatarGroup });
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
Avatar
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=chunk-R6ZS4FMV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/avatar/avatar.tsx","../src/avatar/default-avatar.ts"],"sourcesContent":["// Client: Radix tracks the image's loading state and shows the fallback until it has loaded (or when it fails).\nimport { Avatar as AvatarPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { type DefaultAvatar, defaultAvatarUrl } from \"./default-avatar\";\n\nconst avatar = tv({\n slots: {\n root: \"relative inline-flex shrink-0 overflow-hidden rounded-full bg-surface-3 align-middle\",\n image: \"h-full w-full object-cover\",\n fallback: \"flex h-full w-full items-center justify-center font-semibold uppercase text-fg-secondary\",\n group: \"flex items-center -space-x-2 [&>*]:ring-2 [&>*]:ring-surface-0\",\n },\n variants: {\n size: {\n xs: { root: \"h-5 w-5 text-[9px]\" },\n sm: { root: \"h-8 w-8 text-xs\" },\n md: { root: \"h-10 w-10 text-sm\" },\n lg: { root: \"h-14 w-14 text-base\" },\n xl: { root: \"h-20 w-20 text-lg\" },\n },\n },\n defaultVariants: { size: \"md\" },\n});\n\nexport type AvatarSize = NonNullable<VariantProps<typeof avatar>[\"size\"]>;\n\nexport type AvatarProps = Omit<React.ComponentProps<\"span\">, \"children\"> & {\n /** The image's URL; `null` or nothing shows the `fallback`. */\n src?: string | null;\n /** The image's text alternative (the person's name); empty when a visible name sits next to the avatar. */\n alt?: string;\n /** Shown while the image loads, when it fails or when there is none: initials, an icon. */\n fallback?: React.ReactNode;\n /** The picture to show instead when `src` is missing or fails to load (an image URL). The `fallback` still covers its own loading. */\n fallbackSrc?: string;\n /**\n * The Krizaka default picture when the person has no photo (or it fails): their brand's gradient and a silhouette,\n * one of six tones from `seed` — `defaultAvatar={{ brand: \"orochia\", seed: user.id }}`. `fallbackSrc` wins over it.\n */\n defaultAvatar?: DefaultAvatar;\n /**\n * xs · sm · md · lg · xl.\n * @default \"md\"\n */\n size?: AvatarSize;\n};\n\nexport function AvatarRoot({ src, alt = \"\", fallback, fallbackSrc, defaultAvatar, size, className, ...props }: AvatarProps) {\n const s = avatar({ size });\n const [failed, setFailed] = React.useState<string | null>(null);\n // 512 px for the big sizes, 256 for the others: sharp on a 2x screen either way.\n const backup = fallbackSrc ?? (defaultAvatar ? defaultAvatarUrl(defaultAvatar, { size: size === \"xl\" || size === \"lg\" ? 512 : 256 }) : undefined);\n const shown = src && failed !== src ? src : backup;\n return (\n <AvatarPrimitive.Root data-size={size ?? \"md\"} className={s.root({ className })} {...props}>\n {shown && (\n <AvatarPrimitive.Image\n src={shown}\n alt={alt}\n className={s.image()}\n onLoadingStatusChange={(status) => {\n if (status === \"error\" && shown === src) setFailed(src);\n }}\n />\n )}\n <AvatarPrimitive.Fallback delayMs={shown ? 300 : 0} className={s.fallback()}>\n {fallback}\n </AvatarPrimitive.Fallback>\n </AvatarPrimitive.Root>\n );\n}\n\nexport type AvatarGroupProps = React.ComponentProps<\"div\"> & {\n /** How many avatars are shown; the rest becomes a `+n` avatar. */\n max?: number;\n /** The size of the `+n` avatar (match the avatars of the group). */\n size?: AvatarSize;\n};\n\n/** Overlapping avatars; past `max`, a `+n` avatar counts the rest. */\nexport function AvatarGroup({ max, size, className, children, ...props }: AvatarGroupProps) {\n const items = React.Children.toArray(children);\n const shown = max === undefined ? items : items.slice(0, max);\n const rest = items.length - shown.length;\n return (\n <div className={avatar().group({ className })} {...props}>\n {shown}\n {rest > 0 && <AvatarRoot size={size} fallback={`+${rest}`} />}\n </div>\n );\n}\n\n/** `Avatar` (an image with a fallback) and `Avatar.Group`. */\nexport const Avatar = Object.assign(AvatarRoot, { Group: AvatarGroup });\n","// The default profile pictures of the Krizaka family: what a product shows when a person has no photo.\n// They are generated from this file (tools/export-brand.mjs renders them to SVG and WebP and uploads them to Bunny,\n// `people/defaults/v1/<brand>/…`), so the pictures and the URLs that name them cannot drift apart.\n\nexport type DefaultAvatarBrand = \"krizaka\" | \"orazaka\" | \"orochia\" | \"yasakia\" | \"yororis\";\n/** `person`: a neutral silhouette. `plain`: the brand's gradient alone, ready for initials. */\nexport type DefaultAvatarKind = \"person\" | \"plain\";\nexport type DefaultAvatarSize = 256 | 512;\n\nexport const DEFAULT_AVATAR_BRANDS: readonly DefaultAvatarBrand[] = [\"krizaka\", \"orazaka\", \"orochia\", \"yasakia\", \"yororis\"];\nexport const DEFAULT_AVATAR_KINDS: readonly DefaultAvatarKind[] = [\"person\", \"plain\"];\nexport const DEFAULT_AVATAR_SIZES: readonly DefaultAvatarSize[] = [256, 512];\n/** How many tones each brand has: a person always gets the same one from their seed, so a list is not one colour. */\nexport const DEFAULT_AVATAR_TONES = 6;\n\n/** Where the set lives. `v1` is the set's version: a changed picture is a new version folder, never an overwrite. */\nexport const DEFAULT_AVATAR_BASE = \"https://krizaka-assets.b-cdn.net/people/defaults/v1\";\n\nexport interface DefaultAvatar {\n /** The brand of the product showing the avatar: its gradient, its hues. */\n brand: DefaultAvatarBrand;\n /** Any stable text or number of the person (their id): the same seed gives the same tone everywhere. */\n seed?: string | number;\n /** Force a tone, 1 to 6, instead of deriving it from `seed`. */\n tone?: number;\n /**\n * `person` (default): a silhouette. `plain`: the gradient alone.\n * @default \"person\"\n */\n kind?: DefaultAvatarKind;\n}\n\n/** The mark gradient of each brand (BRAND.md, `$extensions[\"com.krizaka\"].mark`) as [hue, saturation %, lightness %] and the hue spread of its tones. */\nconst BRANDS: Record<DefaultAvatarBrand, { from: [number, number, number]; to: [number, number, number]; spread: number }> = {\n krizaka: { from: [217, 92, 53], to: [217, 92, 68], spread: 14 },\n orazaka: { from: [26, 90.5, 37.1], to: [26, 92, 45], spread: 7 },\n orochia: { from: [262.1, 83.3, 57.8], to: [328.6, 85.5, 70.2], spread: 12 },\n yasakia: { from: [156, 92, 32], to: [178, 90, 32], spread: 12 },\n yororis: { from: [34, 46, 44], to: [104, 28, 42], spread: 10 },\n};\n\n/** White on top of a stop must stay visible: a stop is darkened until the contrast reaches this (WCAG 1.4.11, non-text). */\nconst MIN_CONTRAST = 3.2;\nconst OFFSETS = [0, -1, 1, -2, 2, 3] as const;\n\nfunction hslToHex([h, s, l]: [number, number, number]): string {\n const sat = s / 100;\n const light = l / 100;\n const k = (n: number) => (n + (((h % 360) + 360) % 360) / 30) % 12;\n const a = sat * Math.min(light, 1 - light);\n const channel = (n: number) => Math.round(255 * (light - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)))));\n return `#${[0, 8, 4].map((n) => channel(n).toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\nconst channelLuminance = (v: number) => (v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4);\nconst whiteContrast = (hex: string) => {\n const [r, g, b] = [1, 3, 5].map((i) => channelLuminance(Number.parseInt(hex.slice(i, i + 2), 16) / 255));\n return 1.05 / (0.2126 * r! + 0.7152 * g! + 0.0722 * b! + 0.05);\n};\n/** The stop, darkened one point of lightness at a time until white reads on it. */\nfunction readable([h, s, l]: [number, number, number]): string {\n let hex = hslToHex([h, s, l]);\n for (let light = l; whiteContrast(hex) < MIN_CONTRAST && light > 10; light -= 1) hex = hslToHex([h, s, light]);\n return hex;\n}\n\n/** The two stops (hex) of tone `tone` (1 to 6) of a brand. */\nexport function defaultAvatarTone(brand: DefaultAvatarBrand, tone: number): readonly [string, string] {\n const b = BRANDS[brand];\n const o = OFFSETS[(clampTone(tone) - 1) % OFFSETS.length] * b.spread;\n return [readable([b.from[0] + o, b.from[1], b.from[2]]), readable([b.to[0] + o, b.to[1], b.to[2]])];\n}\n\nconst clampTone = (tone: number) => Math.min(DEFAULT_AVATAR_TONES, Math.max(1, Math.round(tone)));\n\n/** 1 to 6 from a seed (FNV-1a): stable across renders, devices and products. */\nexport function defaultAvatarToneOf(seed: string | number | undefined): number {\n if (seed === undefined) return 1;\n let h = 0x811c9dc5;\n for (const ch of String(seed)) {\n h ^= ch.codePointAt(0) ?? 0;\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return (h % DEFAULT_AVATAR_TONES) + 1;\n}\n\nconst resolveTone = (a: DefaultAvatar) => (a.tone === undefined ? defaultAvatarToneOf(a.seed) : clampTone(a.tone));\n\n/** The file name of a picture in the set: `person-3` + 256 → `person-3-256.webp`; `svg` has no size. */\nexport function defaultAvatarFile(kind: DefaultAvatarKind, tone: number, size: DefaultAvatarSize | \"svg\"): string {\n return size === \"svg\" ? `${kind}-${tone}.svg` : `${kind}-${tone}-${size}.webp`;\n}\n\n/**\n * The URL of a default avatar: `defaultAvatarUrl({ brand: \"orochia\", seed: user.id })`.\n * @param size 256 for lists, 512 for a profile; `\"svg\"` for the vector.\n * @param base Another host or version of the set (a self-hosted copy); the default is the Krizaka CDN.\n */\nexport function defaultAvatarUrl(avatar: DefaultAvatar, { size = 256, base = DEFAULT_AVATAR_BASE }: { size?: DefaultAvatarSize | \"svg\"; base?: string } = {}): string {\n return `${base.replace(/\\/+$/, \"\")}/${avatar.brand}/${defaultAvatarFile(avatar.kind ?? \"person\", resolveTone(avatar), size)}`;\n}\n\n/** The SVG source of a default avatar, 512 × 512 and square (the Avatar clips it round). */\nexport function defaultAvatarSvg(avatar: DefaultAvatar): string {\n const [from, to] = defaultAvatarTone(avatar.brand, resolveTone(avatar));\n const person =\n (avatar.kind ?? \"person\") === \"person\"\n ? `<circle cx=\"256\" cy=\"200\" r=\"86\" fill=\"#fff\" fill-opacity=\".92\"/><path d=\"M84 512c0-108 78-182 172-182s172 74 172 182z\" fill=\"#fff\" fill-opacity=\".92\"/>`\n : \"\";\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\" width=\"512\" height=\"512\"><defs><linearGradient id=\"g\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\"><stop offset=\"0\" stop-color=\"${from}\"/><stop offset=\"1\" stop-color=\"${to}\"/></linearGradient><radialGradient id=\"l\" cx=\".28\" cy=\".18\" r=\".85\"><stop offset=\"0\" stop-color=\"#fff\" stop-opacity=\".24\"/><stop offset=\"1\" stop-color=\"#fff\" stop-opacity=\"0\"/></radialGradient></defs><rect width=\"512\" height=\"512\" fill=\"url(#g)\"/><rect width=\"512\" height=\"512\" fill=\"url(#l)\"/>${person}</svg>`;\n}\n"],"mappings":"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","names":["avatar"]}
|
package/dist/link-card.d.ts
CHANGED
|
@@ -26,10 +26,16 @@ type LinkCardProps = Omit<React.ComponentProps<"a">, "title" | "href"> & {
|
|
|
26
26
|
* @default "row"
|
|
27
27
|
*/
|
|
28
28
|
layout?: "row" | "stack";
|
|
29
|
+
/**
|
|
30
|
+
* Where the facts (`extra`) and the way in (`cta`) sit when the card is taller than its text — a row of cards at one
|
|
31
|
+
* height: `bottom` pins them to the card's foot, so they share a line across the row; `top` leaves them under the text.
|
|
32
|
+
* @default "bottom"
|
|
33
|
+
*/
|
|
34
|
+
footerAlign?: "bottom" | "top";
|
|
29
35
|
/** The app's link component for internal addresses (Next's `Link`); an `<a>` otherwise. */
|
|
30
36
|
linkAs?: LinkComponent;
|
|
31
37
|
};
|
|
32
38
|
/** An entry point: the whole card is one link — icon, title, sentence, the way in. Lifts and lights under the pointer. */
|
|
33
|
-
declare function LinkCard({ href, title, description, icon, meta, cta, extra, titleAs: Title, layout, linkAs, className, ...props }: LinkCardProps): React.JSX.Element;
|
|
39
|
+
declare function LinkCard({ href, title, description, icon, meta, cta, extra, titleAs: Title, layout, footerAlign, linkAs, className, ...props }: LinkCardProps): React.JSX.Element;
|
|
34
40
|
|
|
35
41
|
export { LinkCard, type LinkCardProps, LinkComponent };
|
package/dist/link-card.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
|
|
13
13
|
// src/link-card/link-card.tsx
|
|
14
14
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
-
function LinkCard({ href, title, description, icon, meta, cta, extra, titleAs: Title = "h3", layout = "row", linkAs, className, ...props }) {
|
|
15
|
+
function LinkCard({ href, title, description, icon, meta, cta, extra, titleAs: Title = "h3", layout = "row", footerAlign = "bottom", linkAs, className, ...props }) {
|
|
16
16
|
const external = isExternal(href);
|
|
17
17
|
return /* @__PURE__ */ jsxs(
|
|
18
18
|
BlockLink,
|
|
@@ -23,6 +23,7 @@ function LinkCard({ href, title, description, icon, meta, cta, extra, titleAs: T
|
|
|
23
23
|
className: cn("kz-block-card kz-link-card", className),
|
|
24
24
|
"data-kz-light": "",
|
|
25
25
|
"data-layout": layout === "row" ? void 0 : layout,
|
|
26
|
+
"data-footer": footerAlign === "top" ? "top" : void 0,
|
|
26
27
|
...props,
|
|
27
28
|
children: [
|
|
28
29
|
icon && /* @__PURE__ */ jsx("span", { className: "kz-block-icon", "aria-hidden": true, children: icon }),
|
|
@@ -30,10 +31,12 @@ function LinkCard({ href, title, description, icon, meta, cta, extra, titleAs: T
|
|
|
30
31
|
meta && /* @__PURE__ */ jsx("p", { className: "kz-link-card-meta", children: meta }),
|
|
31
32
|
/* @__PURE__ */ jsx(Title, { className: "kz-link-card-title", children: title }),
|
|
32
33
|
description && /* @__PURE__ */ jsx("p", { className: "kz-link-card-text", children: description }),
|
|
33
|
-
extra && /* @__PURE__ */
|
|
34
|
-
|
|
35
|
-
cta,
|
|
36
|
-
|
|
34
|
+
(extra || cta || external) && /* @__PURE__ */ jsxs("div", { className: "kz-link-card-foot", children: [
|
|
35
|
+
extra && /* @__PURE__ */ jsx("div", { className: "kz-link-card-extra", children: extra }),
|
|
36
|
+
(cta || external) && /* @__PURE__ */ jsxs("span", { className: "kz-link-card-cta", children: [
|
|
37
|
+
cta,
|
|
38
|
+
external ? /* @__PURE__ */ jsx(ExternalGlyph, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ArrowGlyph, { className: "h-3.5 w-3.5" })
|
|
39
|
+
] })
|
|
37
40
|
] })
|
|
38
41
|
] })
|
|
39
42
|
]
|
package/dist/link-card.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/link-card/link-card.tsx"],"sourcesContent":["// Server-safe. A card that leads somewhere (blocks.css): the whole card is the link — an icon, a title, one sentence,\n// the way in. Compact, one look for every entry point of a page (a documentation hub, the products, the next step).\nimport type * as React from \"react\";\n\nimport { BlockLink, isExternal, type LinkComponent } from \"../block-link\";\nimport { cn } from \"../cn\";\nimport { ArrowGlyph, ExternalGlyph } from \"../glyphs\";\n\nexport type LinkCardProps = Omit<React.ComponentProps<\"a\">, \"title\" | \"href\"> & {\n /** Where the card leads. An absolute URL opens in a new tab, with the external mark. */\n href: string;\n /** The title: the name of the destination. */\n title: React.ReactNode;\n /** One sentence: what the reader finds there. */\n description?: React.ReactNode;\n /** The icon tile on the left (a product mark, a signature icon): decorative, the title names the link. */\n icon?: React.ReactNode;\n /** A short mono line above the title (a category, a version). */\n meta?: React.ReactNode;\n /** The way in, under the text, with an arrow: `Open`, `Read the guide`. */\n cta?: React.ReactNode;\n /** Under the text, before the way in: facts (`Facts`), badges. Never another link — the card is one. */\n extra?: React.ReactNode;\n /**\n * The title's element: a heading when the cards are the content of a section (a hub), `span` inside a list of links.\n * @default \"h3\"\n */\n titleAs?: \"h2\" | \"h3\" | \"h4\" | \"span\";\n /**\n * `row`: the icon beside the text (compact, the default); `stack`: the icon above it.\n * @default \"row\"\n */\n layout?: \"row\" | \"stack\";\n /** The app's link component for internal addresses (Next's `Link`); an `<a>` otherwise. */\n linkAs?: LinkComponent;\n};\n\n/** An entry point: the whole card is one link — icon, title, sentence, the way in. Lifts and lights under the pointer. */\nexport function LinkCard({ href, title, description, icon, meta, cta, extra, titleAs: Title = \"h3\", layout = \"row\", linkAs, className, ...props }: LinkCardProps) {\n const external = isExternal(href);\n return (\n <BlockLink\n href={href}\n linkAs={linkAs}\n external={external}\n className={cn(\"kz-block-card kz-link-card\", className)}\n data-kz-light=\"\"\n data-layout={layout === \"row\" ? undefined : layout}\n {...props}\n >\n {icon && (\n <span className=\"kz-block-icon\" aria-hidden>\n {icon}\n </span>\n )}\n <div className=\"kz-link-card-body\">\n {meta && <p className=\"kz-link-card-meta\">{meta}</p>}\n <Title className=\"kz-link-card-title\">{title}</Title>\n {description && <p className=\"kz-link-card-text\">{description}</p>}\n {extra && <div className=\"kz-link-card-extra\">{extra}</div>}\n
|
|
1
|
+
{"version":3,"sources":["../src/link-card/link-card.tsx"],"sourcesContent":["// Server-safe. A card that leads somewhere (blocks.css): the whole card is the link — an icon, a title, one sentence,\n// the way in. Compact, one look for every entry point of a page (a documentation hub, the products, the next step).\nimport type * as React from \"react\";\n\nimport { BlockLink, isExternal, type LinkComponent } from \"../block-link\";\nimport { cn } from \"../cn\";\nimport { ArrowGlyph, ExternalGlyph } from \"../glyphs\";\n\nexport type LinkCardProps = Omit<React.ComponentProps<\"a\">, \"title\" | \"href\"> & {\n /** Where the card leads. An absolute URL opens in a new tab, with the external mark. */\n href: string;\n /** The title: the name of the destination. */\n title: React.ReactNode;\n /** One sentence: what the reader finds there. */\n description?: React.ReactNode;\n /** The icon tile on the left (a product mark, a signature icon): decorative, the title names the link. */\n icon?: React.ReactNode;\n /** A short mono line above the title (a category, a version). */\n meta?: React.ReactNode;\n /** The way in, under the text, with an arrow: `Open`, `Read the guide`. */\n cta?: React.ReactNode;\n /** Under the text, before the way in: facts (`Facts`), badges. Never another link — the card is one. */\n extra?: React.ReactNode;\n /**\n * The title's element: a heading when the cards are the content of a section (a hub), `span` inside a list of links.\n * @default \"h3\"\n */\n titleAs?: \"h2\" | \"h3\" | \"h4\" | \"span\";\n /**\n * `row`: the icon beside the text (compact, the default); `stack`: the icon above it.\n * @default \"row\"\n */\n layout?: \"row\" | \"stack\";\n /**\n * Where the facts (`extra`) and the way in (`cta`) sit when the card is taller than its text — a row of cards at one\n * height: `bottom` pins them to the card's foot, so they share a line across the row; `top` leaves them under the text.\n * @default \"bottom\"\n */\n footerAlign?: \"bottom\" | \"top\";\n /** The app's link component for internal addresses (Next's `Link`); an `<a>` otherwise. */\n linkAs?: LinkComponent;\n};\n\n/** An entry point: the whole card is one link — icon, title, sentence, the way in. Lifts and lights under the pointer. */\nexport function LinkCard({ href, title, description, icon, meta, cta, extra, titleAs: Title = \"h3\", layout = \"row\", footerAlign = \"bottom\", linkAs, className, ...props }: LinkCardProps) {\n const external = isExternal(href);\n return (\n <BlockLink\n href={href}\n linkAs={linkAs}\n external={external}\n className={cn(\"kz-block-card kz-link-card\", className)}\n data-kz-light=\"\"\n data-layout={layout === \"row\" ? undefined : layout}\n data-footer={footerAlign === \"top\" ? \"top\" : undefined}\n {...props}\n >\n {icon && (\n <span className=\"kz-block-icon\" aria-hidden>\n {icon}\n </span>\n )}\n <div className=\"kz-link-card-body\">\n {meta && <p className=\"kz-link-card-meta\">{meta}</p>}\n <Title className=\"kz-link-card-title\">{title}</Title>\n {description && <p className=\"kz-link-card-text\">{description}</p>}\n {(extra || cta || external) && (\n <div className=\"kz-link-card-foot\">\n {extra && <div className=\"kz-link-card-extra\">{extra}</div>}\n {(cta || external) && (\n <span className=\"kz-link-card-cta\">\n {cta}\n {external ? <ExternalGlyph className=\"h-3.5 w-3.5\" /> : <ArrowGlyph className=\"h-3.5 w-3.5\" />}\n </span>\n )}\n </div>\n )}\n </div>\n </BlockLink>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA0DQ,cAYM,YAZN;AAdD,SAAS,SAAS,EAAE,MAAM,OAAO,aAAa,MAAM,MAAM,KAAK,OAAO,SAAS,QAAQ,MAAM,SAAS,OAAO,cAAc,UAAU,QAAQ,WAAW,GAAG,MAAM,GAAkB;AACxL,QAAM,WAAW,WAAW,IAAI;AAChC,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,GAAG,8BAA8B,SAAS;AAAA,MACrD,iBAAc;AAAA,MACd,eAAa,WAAW,QAAQ,SAAY;AAAA,MAC5C,eAAa,gBAAgB,QAAQ,QAAQ;AAAA,MAC5C,GAAG;AAAA,MAEH;AAAA,gBACC,oBAAC,UAAK,WAAU,iBAAgB,eAAW,MACxC,gBACH;AAAA,QAEF,qBAAC,SAAI,WAAU,qBACZ;AAAA,kBAAQ,oBAAC,OAAE,WAAU,qBAAqB,gBAAK;AAAA,UAChD,oBAAC,SAAM,WAAU,sBAAsB,iBAAM;AAAA,UAC5C,eAAe,oBAAC,OAAE,WAAU,qBAAqB,uBAAY;AAAA,WAC5D,SAAS,OAAO,aAChB,qBAAC,SAAI,WAAU,qBACZ;AAAA,qBAAS,oBAAC,SAAI,WAAU,sBAAsB,iBAAM;AAAA,aACnD,OAAO,aACP,qBAAC,UAAK,WAAU,oBACb;AAAA;AAAA,cACA,WAAW,oBAAC,iBAAc,WAAU,eAAc,IAAK,oBAAC,cAAW,WAAU,eAAc;AAAA,eAC9F;AAAA,aAEJ;AAAA,WAEJ;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
package/dist/match-dialog.js
CHANGED
package/dist/native.d.ts
CHANGED
|
@@ -2,6 +2,33 @@ import * as React$1 from 'react';
|
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { StyleProp, ViewStyle, ViewProps, PressableProps, TextProps, ImageProps, TextInput, TextInputProps, Animated, NativeSyntheticEvent, NativeScrollEvent, TextStyle, RefreshControlProps, DimensionValue, ActivityIndicatorProps } from 'react-native';
|
|
4
4
|
|
|
5
|
+
type DefaultAvatarBrand = "krizaka" | "orazaka" | "orochia" | "yasakia" | "yororis";
|
|
6
|
+
/** `person`: a neutral silhouette. `plain`: the brand's gradient alone, ready for initials. */
|
|
7
|
+
type DefaultAvatarKind = "person" | "plain";
|
|
8
|
+
type DefaultAvatarSize = 256 | 512;
|
|
9
|
+
interface DefaultAvatar {
|
|
10
|
+
/** The brand of the product showing the avatar: its gradient, its hues. */
|
|
11
|
+
brand: DefaultAvatarBrand;
|
|
12
|
+
/** Any stable text or number of the person (their id): the same seed gives the same tone everywhere. */
|
|
13
|
+
seed?: string | number;
|
|
14
|
+
/** Force a tone, 1 to 6, instead of deriving it from `seed`. */
|
|
15
|
+
tone?: number;
|
|
16
|
+
/**
|
|
17
|
+
* `person` (default): a silhouette. `plain`: the gradient alone.
|
|
18
|
+
* @default "person"
|
|
19
|
+
*/
|
|
20
|
+
kind?: DefaultAvatarKind;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The URL of a default avatar: `defaultAvatarUrl({ brand: "orochia", seed: user.id })`.
|
|
24
|
+
* @param size 256 for lists, 512 for a profile; `"svg"` for the vector.
|
|
25
|
+
* @param base Another host or version of the set (a self-hosted copy); the default is the Krizaka CDN.
|
|
26
|
+
*/
|
|
27
|
+
declare function defaultAvatarUrl(avatar: DefaultAvatar, { size, base }?: {
|
|
28
|
+
size?: DefaultAvatarSize | "svg";
|
|
29
|
+
base?: string;
|
|
30
|
+
}): string;
|
|
31
|
+
|
|
5
32
|
/** Milliseconds left until `target` (never negative), ticking every second. `skewMs` = server clock − this clock. */
|
|
6
33
|
declare function useCountdown(target: Date | string | number, skewMs?: number): number;
|
|
7
34
|
/** Splits a duration into days, hours, minutes and seconds. */
|
|
@@ -155,6 +182,10 @@ type AvatarProps = Omit<ViewProps, "children"> & {
|
|
|
155
182
|
alt?: string;
|
|
156
183
|
/** Shown while there is no image or when it fails: default, the first letter of `alt`. */
|
|
157
184
|
fallback?: React$1.ReactNode;
|
|
185
|
+
/** The picture to show when `src` is missing or fails to load (an image URI); `fallback` still covers the case of no picture at all. */
|
|
186
|
+
fallbackSrc?: string;
|
|
187
|
+
/** The Krizaka default picture (the brand's gradient and a silhouette, one of six tones from `seed`) when there is no photo; `fallbackSrc` wins over it. */
|
|
188
|
+
defaultAvatar?: DefaultAvatar;
|
|
158
189
|
/** A size of the scale, or points. */
|
|
159
190
|
size?: AvatarSize | number;
|
|
160
191
|
/** A dot at the bottom right: `online` (success), `away` (warning), `busy` (danger), `offline` (hollow). */
|
|
@@ -164,7 +195,7 @@ type AvatarProps = Omit<ViewProps, "children"> & {
|
|
|
164
195
|
/** A ring around it: `story` (an unseen story, in the accent gradient), `seen` (a story already watched), `live` (danger). */
|
|
165
196
|
ring?: "story" | "seen" | "live";
|
|
166
197
|
};
|
|
167
|
-
declare function AvatarRoot({ src, svg, alt, fallback, size, presence, presenceLabel, ring, style, ...props }: AvatarProps): React$1.JSX.Element;
|
|
198
|
+
declare function AvatarRoot({ src, svg, alt, fallback, fallbackSrc, defaultAvatar, size, presence, presenceLabel, ring, style, ...props }: AvatarProps): React$1.JSX.Element;
|
|
168
199
|
type AvatarGroupProps = ViewProps & {
|
|
169
200
|
/** How many avatars are shown; the rest becomes a `+n` avatar. */
|
|
170
201
|
max?: number;
|
|
@@ -2349,4 +2380,4 @@ declare const YazakiaMark: typeof YasakiaMark;
|
|
|
2349
2380
|
*/
|
|
2350
2381
|
declare function YororisMark({ size, animated, title, neutral }: NativeMarkProps): React__default.JSX.Element;
|
|
2351
2382
|
|
|
2352
|
-
export { ActionSheet, type ActionSheetAction, type ActionSheetProps, type ActivityEvent, ActivityFeed, type ActivityFeedLabels, type ActivityFeedProps, type ActivityTone, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, AvatarRoot, type AvatarSize, Badge, type BadgeProps, type BadgeTone, type BrandId, BrandedRefresh, type BrandedRefreshProps, type BrandedRefreshScrollProps, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Calendar, type CalendarLabels, type CalendarProps, Card, CardBody, type CardBodyProps, CardDescription, CardFooter, CardImage, type CardImageProps, CardMedia, type CardMediaProps, CardOverlay, type CardOverlayProps, CardRoot, type CardRootProps, CardTitle, Checkbox, type CheckboxProps, Chip, type ChipGroupProps, type ChipProps, type ChipSize, Combobox, type ComboboxLabels, type ComboboxProps, ConversationItem, type ConversationItemProps, Countdown, type CountdownProps, type CountdownUnits, DateField, type DateFieldLabels, type DateFieldProps, type DatePart, DateWheel, type DateWheelProps, DayStrip, type DayStripProps, type DescriptionItem, DescriptionList, type DescriptionListProps, EmptyState, type EmptyStateProps, ErrorState, Fab, type FabProps, GoalGauge, type GoalGaugeProps, type GridPhoto, type HapticKind, Header, type HeaderProps, IconButton, type IconButtonProps, ImageViewer, type ImageViewerProps, type Insets, KrizakaMark, Loader, LoaderGlyph, type LoaderGlyphProps, type LoaderProps, LoadingState, Mascot, type MascotProps, MascotState, type MascotStateProps, MatchDialog, type MatchDialogProps, MessageBubble, type MessageBubbleLabels, type MessageBubbleProps, MessageComposer, type MessageComposerProps, type MessageStatus, Meter, type MeterPart, type MeterProps, type Mode, MonthPicker, type MonthPickerProps, type Motion, type NativeMarkProps, NumberTicker, type NumberTickerProps, Onboarding, type OnboardingProps, type OnboardingSlide, OrazakaMark, OrochiaMark, OtpInput, type OtpInputProps, Pagination, type PaginationLabels, type PaginationProps, PermissionPrompt, type PermissionPromptProps, type PermissionStatus, PersonCard, type PersonCardProps, Phoenix, type PhoenixProps, PhotoGrid, type PhotoGridLabels, type PhotoGridProps, ProductMark, ProfileCard, type ProfileCardProps, type ProfileStat, Progress, type ProgressProps, PullToRefresh, type PullToRefreshProps, RadioGroup, type RadioGroupProps, type RadioOption, type Radius, Screen, type ScreenProps, SearchField, type SearchFieldProps, Segmented, type SegmentedOption, type SegmentedProps, Select, type SelectOption, type SelectProps, Sheet, type SheetProps, Showcase, type ShowcaseLabels, type ShowcaseProps, Skeleton, type SkeletonProps, Slider, type SliderProps, type SliderValue, Spinner, type SpinnerProps, SplashHandoff, type SplashHandoffProps, type Step, StepProgress, type StepProgressProps, type StepStatus, Stepper, type StepperProps, SuccessState, type SwipeAction, SwipeDeck, type SwipeDeckLabels, type SwipeDeckProps, type SwipeDirection, SwipeableRow, type SwipeableRowProps, Switch, type SwitchProps, TabBar, type TabBarItem, type TabBarProps, type TabItem, Tabs, type TabsProps, type TagOption, TagSelector, type TagSelectorProps, TextField, type TextFieldProps, type Theme, type ThemeContextValue, type ThemeFonts, type ThemeName, type ThemeOverrides, ThemeProvider, type ThemeProviderProps, TimePicker, type TimePickerLabels, type TimePickerProps, type TimeValue, type ToastItem, type ToastOptions, type ToastTone, Toaster, type ToasterProps, Txt, type TxtProps, type TxtTone, type TxtVariant, TypingIndicator, type TypingIndicatorProps, VerifiedBadge, type VerifiedBadgeProps, type ViewerImage, WelcomeState, Wheel, WheelDivider, type WheelOption, WheelPicker, type WheelPickerProps, type WheelProps, YasakiaMark, YazakiaMark, YororisMark, addDays, addMonths, ageOn, alpha, calendarLabels, comboboxLabels, composeDate, dateFieldLabels, datePartsOrder, daysInMonth, formatTime, isRealDate, latestBirthDate, monthNames, prefetchNeighbours, showcaseLabels, splitDuration, textStyles, timeSlots, timeToMinutes, toast, todayIso, useCollapsingHeader, useCountdown, useDelayedWait, useReducedMotion, useTheme, uses12Hours };
|
|
2383
|
+
export { ActionSheet, type ActionSheetAction, type ActionSheetProps, type ActivityEvent, ActivityFeed, type ActivityFeedLabels, type ActivityFeedProps, type ActivityTone, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, AvatarRoot, type AvatarSize, Badge, type BadgeProps, type BadgeTone, type BrandId, BrandedRefresh, type BrandedRefreshProps, type BrandedRefreshScrollProps, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Calendar, type CalendarLabels, type CalendarProps, Card, CardBody, type CardBodyProps, CardDescription, CardFooter, CardImage, type CardImageProps, CardMedia, type CardMediaProps, CardOverlay, type CardOverlayProps, CardRoot, type CardRootProps, CardTitle, Checkbox, type CheckboxProps, Chip, type ChipGroupProps, type ChipProps, type ChipSize, Combobox, type ComboboxLabels, type ComboboxProps, ConversationItem, type ConversationItemProps, Countdown, type CountdownProps, type CountdownUnits, DateField, type DateFieldLabels, type DateFieldProps, type DatePart, DateWheel, type DateWheelProps, DayStrip, type DayStripProps, type DefaultAvatar, type DefaultAvatarBrand, type DescriptionItem, DescriptionList, type DescriptionListProps, EmptyState, type EmptyStateProps, ErrorState, Fab, type FabProps, GoalGauge, type GoalGaugeProps, type GridPhoto, type HapticKind, Header, type HeaderProps, IconButton, type IconButtonProps, ImageViewer, type ImageViewerProps, type Insets, KrizakaMark, Loader, LoaderGlyph, type LoaderGlyphProps, type LoaderProps, LoadingState, Mascot, type MascotProps, MascotState, type MascotStateProps, MatchDialog, type MatchDialogProps, MessageBubble, type MessageBubbleLabels, type MessageBubbleProps, MessageComposer, type MessageComposerProps, type MessageStatus, Meter, type MeterPart, type MeterProps, type Mode, MonthPicker, type MonthPickerProps, type Motion, type NativeMarkProps, NumberTicker, type NumberTickerProps, Onboarding, type OnboardingProps, type OnboardingSlide, OrazakaMark, OrochiaMark, OtpInput, type OtpInputProps, Pagination, type PaginationLabels, type PaginationProps, PermissionPrompt, type PermissionPromptProps, type PermissionStatus, PersonCard, type PersonCardProps, Phoenix, type PhoenixProps, PhotoGrid, type PhotoGridLabels, type PhotoGridProps, ProductMark, ProfileCard, type ProfileCardProps, type ProfileStat, Progress, type ProgressProps, PullToRefresh, type PullToRefreshProps, RadioGroup, type RadioGroupProps, type RadioOption, type Radius, Screen, type ScreenProps, SearchField, type SearchFieldProps, Segmented, type SegmentedOption, type SegmentedProps, Select, type SelectOption, type SelectProps, Sheet, type SheetProps, Showcase, type ShowcaseLabels, type ShowcaseProps, Skeleton, type SkeletonProps, Slider, type SliderProps, type SliderValue, Spinner, type SpinnerProps, SplashHandoff, type SplashHandoffProps, type Step, StepProgress, type StepProgressProps, type StepStatus, Stepper, type StepperProps, SuccessState, type SwipeAction, SwipeDeck, type SwipeDeckLabels, type SwipeDeckProps, type SwipeDirection, SwipeableRow, type SwipeableRowProps, Switch, type SwitchProps, TabBar, type TabBarItem, type TabBarProps, type TabItem, Tabs, type TabsProps, type TagOption, TagSelector, type TagSelectorProps, TextField, type TextFieldProps, type Theme, type ThemeContextValue, type ThemeFonts, type ThemeName, type ThemeOverrides, ThemeProvider, type ThemeProviderProps, TimePicker, type TimePickerLabels, type TimePickerProps, type TimeValue, type ToastItem, type ToastOptions, type ToastTone, Toaster, type ToasterProps, Txt, type TxtProps, type TxtTone, type TxtVariant, TypingIndicator, type TypingIndicatorProps, VerifiedBadge, type VerifiedBadgeProps, type ViewerImage, WelcomeState, Wheel, WheelDivider, type WheelOption, WheelPicker, type WheelPickerProps, type WheelProps, YasakiaMark, YazakiaMark, YororisMark, addDays, addMonths, ageOn, alpha, calendarLabels, comboboxLabels, composeDate, dateFieldLabels, datePartsOrder, daysInMonth, defaultAvatarUrl, formatTime, isRealDate, latestBirthDate, monthNames, prefetchNeighbours, showcaseLabels, splitDuration, textStyles, timeSlots, timeToMinutes, toast, todayIso, useCollapsingHeader, useCountdown, useDelayedWait, useReducedMotion, useTheme, uses12Hours };
|
package/dist/native.js
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
// src/avatar/default-avatar.ts
|
|
2
|
+
var DEFAULT_AVATAR_TONES = 6;
|
|
3
|
+
var DEFAULT_AVATAR_BASE = "https://krizaka-assets.b-cdn.net/people/defaults/v1";
|
|
4
|
+
var clampTone = (tone3) => Math.min(DEFAULT_AVATAR_TONES, Math.max(1, Math.round(tone3)));
|
|
5
|
+
function defaultAvatarToneOf(seed) {
|
|
6
|
+
if (seed === void 0) return 1;
|
|
7
|
+
let h = 2166136261;
|
|
8
|
+
for (const ch of String(seed)) {
|
|
9
|
+
h ^= ch.codePointAt(0) ?? 0;
|
|
10
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
11
|
+
}
|
|
12
|
+
return h % DEFAULT_AVATAR_TONES + 1;
|
|
13
|
+
}
|
|
14
|
+
var resolveTone = (a) => a.tone === void 0 ? defaultAvatarToneOf(a.seed) : clampTone(a.tone);
|
|
15
|
+
function defaultAvatarFile(kind, tone3, size) {
|
|
16
|
+
return size === "svg" ? `${kind}-${tone3}.svg` : `${kind}-${tone3}-${size}.webp`;
|
|
17
|
+
}
|
|
18
|
+
function defaultAvatarUrl(avatar, { size = 256, base = DEFAULT_AVATAR_BASE } = {}) {
|
|
19
|
+
return `${base.replace(/\/+$/, "")}/${avatar.brand}/${defaultAvatarFile(avatar.kind ?? "person", resolveTone(avatar), size)}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
1
22
|
// src/countdown/core.ts
|
|
2
23
|
import { useSyncExternalStore } from "react";
|
|
3
24
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -590,12 +611,14 @@ var SIDE = { xs: 20, sm: 32, md: 40, lg: 56, xl: 80 };
|
|
|
590
611
|
function isSvgUri(uri) {
|
|
591
612
|
return /^data:image\/svg\+xml[;,]/i.test(uri) || /\.svg(?:[?#]|$)/i.test(uri);
|
|
592
613
|
}
|
|
593
|
-
function AvatarRoot({ src, svg, alt = "", fallback, size = "md", presence, presenceLabel, ring, style, ...props }) {
|
|
614
|
+
function AvatarRoot({ src, svg, alt = "", fallback, fallbackSrc, defaultAvatar, size = "md", presence, presenceLabel, ring, style, ...props }) {
|
|
594
615
|
const { theme } = useTheme();
|
|
595
616
|
const gradient = useSvgId("kz-ring-");
|
|
596
|
-
const [
|
|
597
|
-
const
|
|
617
|
+
const [failedSrcs, setFailedSrcs] = React4.useState([]);
|
|
618
|
+
const failedSrc = { has: (uri) => Boolean(uri) && failedSrcs.includes(uri) };
|
|
598
619
|
const side = typeof size === "number" ? size : SIDE[size];
|
|
620
|
+
const backup = fallbackSrc ?? (defaultAvatar ? defaultAvatarUrl(defaultAvatar, { size: side > 40 ? 512 : 256 }) : void 0);
|
|
621
|
+
const shown = src && !failedSrc.has(src) ? src : backup && !failedSrc.has(backup) ? backup : null;
|
|
599
622
|
const initial = alt.trim().charAt(0).toUpperCase();
|
|
600
623
|
const content = fallback ?? initial;
|
|
601
624
|
const picture = /* @__PURE__ */ jsx5(
|
|
@@ -606,7 +629,7 @@ function AvatarRoot({ src, svg, alt = "", fallback, size = "md", presence, prese
|
|
|
606
629
|
"aria-label": alt && !presence && !ring ? alt : void 0,
|
|
607
630
|
...presence || ring ? {} : props,
|
|
608
631
|
style: [styles4.root, { width: side, height: side, borderRadius: side / 2, backgroundColor: theme.surface3 }, presence || ring ? null : style],
|
|
609
|
-
children:
|
|
632
|
+
children: shown && (svg && shown === src || isSvgUri(shown)) ? /* @__PURE__ */ jsx5(View3, { style: StyleSheet4.absoluteFill, pointerEvents: "none", children: /* @__PURE__ */ jsx5(SvgUri, { testID: props.testID ? `${props.testID}.svg` : void 0, uri: shown, width: "100%", height: "100%", preserveAspectRatio: "xMidYMid slice", onError: () => setFailedSrcs((all) => [...all, shown]) }) }) : shown ? /* @__PURE__ */ jsx5(Image, { testID: props.testID ? `${props.testID}.image` : void 0, source: { uri: shown }, onError: () => setFailedSrcs((all) => [...all, shown]), resizeMode: "cover", style: StyleSheet4.absoluteFill }) : typeof content === "string" ? /* @__PURE__ */ jsx5(Text2, { style: [styles4.initial, { color: theme.textSecondary, fontSize: Math.max(9, Math.round(side * 0.4)) }], children: content }) : content
|
|
610
633
|
}
|
|
611
634
|
);
|
|
612
635
|
if (!presence && !ring) return picture;
|
|
@@ -5709,6 +5732,7 @@ export {
|
|
|
5709
5732
|
dateFieldLabels,
|
|
5710
5733
|
datePartsOrder,
|
|
5711
5734
|
daysInMonth,
|
|
5735
|
+
defaultAvatarUrl,
|
|
5712
5736
|
formatTime,
|
|
5713
5737
|
isRealDate,
|
|
5714
5738
|
latestBirthDate,
|