@krizaka/ui 3.23.0 → 3.25.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/README.md +9 -1
- package/dist/account-menu.d.ts +163 -0
- package/dist/account-menu.js +211 -0
- package/dist/account-menu.js.map +1 -0
- package/dist/app-shell.js +4 -4
- package/dist/changelog-page.d.ts +13 -1
- package/dist/changelog-page.js +20 -0
- package/dist/changelog-page.js.map +1 -1
- package/dist/{chunk-CEJDH6SA.js → chunk-2PNLHZLT.js} +23 -3
- package/dist/chunk-2PNLHZLT.js.map +1 -0
- package/dist/{chunk-YCIBJ6OQ.js → chunk-4DH2HW3D.js} +3 -1
- package/dist/chunk-C7LTF5CC.js +21 -0
- package/dist/chunk-C7LTF5CC.js.map +1 -0
- package/dist/{chunk-2L67YTNV.js → chunk-FTX66XZY.js} +2 -2
- package/dist/{chunk-F7FVPPBB.js → chunk-LYOPPBMC.js} +2 -2
- package/dist/{chunk-AFY35UCB.js → chunk-P7GZR5CU.js} +3 -1
- package/dist/{chunk-AFY35UCB.js.map → chunk-P7GZR5CU.js.map} +1 -1
- package/dist/code-block.js +1 -1
- package/dist/cover-hero.d.ts +125 -0
- package/dist/cover-hero.js +82 -0
- package/dist/cover-hero.js.map +1 -0
- package/dist/data-table.js +2 -2
- package/dist/docs-nav.js +3 -16
- package/dist/docs-nav.js.map +1 -1
- package/dist/dropdown-menu.d.ts +12 -1
- package/dist/dropdown-menu.js +25 -2
- package/dist/dropdown-menu.js.map +1 -1
- package/dist/filter-bar.js +1 -1
- package/dist/genui.js +2 -2
- package/dist/index.d.ts +15 -1
- package/dist/index.js +62 -2
- package/dist/index.js.map +1 -1
- package/dist/media-gallery.d.ts +73 -0
- package/dist/media-gallery.js +42 -0
- package/dist/media-gallery.js.map +1 -0
- package/dist/nav-bar.js +2 -2
- package/dist/opening-hours.d.ts +124 -0
- package/dist/opening-hours.js +48 -0
- package/dist/opening-hours.js.map +1 -0
- package/dist/rail-nav.js +1 -1
- package/dist/service-card.d.ts +168 -0
- package/dist/service-card.js +112 -0
- package/dist/service-card.js.map +1 -0
- package/dist/site-footer.js +2 -2
- package/dist/slot-picker.d.ts +119 -0
- package/dist/slot-picker.js +46 -0
- package/dist/slot-picker.js.map +1 -0
- package/dist/sticky-action.d.ts +12 -0
- package/dist/sticky-action.js +14 -0
- package/dist/sticky-action.js.map +1 -0
- package/package.json +106 -6
- package/registry/account-menu.json +280 -0
- package/registry/achievement.json +1 -1
- package/registry/action-bar.json +1 -1
- package/registry/activity-feed.json +3 -3
- package/registry/alert.json +1 -1
- package/registry/app-header.json +1 -1
- package/registry/app-shell.json +1 -1
- package/registry/argument-card.json +1 -1
- package/registry/audio-player.json +1 -1
- package/registry/auth-layout.json +1 -1
- package/registry/avatar.json +1 -1
- package/registry/badge.json +1 -1
- package/registry/button.json +1 -1
- package/registry/captcha.json +1 -1
- package/registry/card-grid.json +1 -1
- package/registry/card.json +3 -3
- package/registry/catalogue-page.json +1 -1
- package/registry/changelog-page.json +3 -3
- package/registry/chat.json +1 -1
- package/registry/checkbox.json +1 -1
- package/registry/chip-link.json +1 -1
- package/registry/chip.json +1 -1
- package/registry/choice-grid.json +1 -1
- package/registry/code-block.json +1 -1
- package/registry/combobox.json +1 -1
- package/registry/command.json +1 -1
- package/registry/comparison-table.json +1 -1
- package/registry/component-page.json +1 -1
- package/registry/confirm-button.json +1 -1
- package/registry/contact-page.json +1 -1
- package/registry/continue-prompt.json +1 -1
- package/registry/countdown.json +1 -1
- package/registry/cover-hero.json +246 -0
- package/registry/cta-panel.json +1 -1
- package/registry/dashboard-layout.json +1 -1
- package/registry/data-table.json +1 -1
- package/registry/date-picker.json +1 -1
- package/registry/decision-list.json +1 -1
- package/registry/description-list.json +1 -1
- package/registry/detail-layout.json +1 -1
- package/registry/dialog.json +1 -1
- package/registry/docs-article-page.json +1 -1
- package/registry/docs-hub-page.json +1 -1
- package/registry/docs-layout.json +1 -1
- package/registry/docs-nav.json +1 -1
- package/registry/document-viewer.json +1 -1
- package/registry/dropdown-menu.json +33 -3
- package/registry/empty-state.json +1 -1
- package/registry/error-page.json +1 -1
- package/registry/examples/account-menu/default.d.ts +4 -0
- package/registry/examples/account-menu/default.js +28 -0
- package/registry/examples/account-menu/default.tsx +22 -0
- package/registry/examples/cover-hero/plain.d.ts +4 -0
- package/registry/examples/cover-hero/plain.js +12 -0
- package/registry/examples/cover-hero/plain.tsx +7 -0
- package/registry/examples/cover-hero/shop.d.ts +4 -0
- package/registry/examples/cover-hero/shop.js +27 -0
- package/registry/examples/cover-hero/shop.tsx +20 -0
- package/registry/examples/media-gallery/shop.d.ts +4 -0
- package/registry/examples/media-gallery/shop.js +22 -0
- package/registry/examples/media-gallery/shop.tsx +18 -0
- package/registry/examples/opening-hours/week.d.ts +4 -0
- package/registry/examples/opening-hours/week.js +28 -0
- package/registry/examples/opening-hours/week.tsx +22 -0
- package/registry/examples/service-card/services.d.ts +4 -0
- package/registry/examples/service-card/services.js +15 -0
- package/registry/examples/service-card/services.tsx +12 -0
- package/registry/examples/slot-picker/times.d.ts +4 -0
- package/registry/examples/slot-picker/times.js +27 -0
- package/registry/examples/slot-picker/times.tsx +23 -0
- package/registry/examples/sticky-action/phone.d.ts +4 -0
- package/registry/examples/sticky-action/phone.js +15 -0
- package/registry/examples/sticky-action/phone.tsx +14 -0
- package/registry/field.json +1 -1
- package/registry/file-upload.json +1 -1
- package/registry/filter-bar.json +1 -1
- package/registry/force-update-screen.json +1 -1
- package/registry/form.json +3 -3
- package/registry/gallery-page.json +1 -1
- package/registry/genui.json +1 -1
- package/registry/glow-card.json +1 -1
- package/registry/goal-gauge.json +1 -1
- package/registry/how-it-works-page.json +1 -1
- package/registry/image-cropper.json +1 -1
- package/registry/index.json +226 -5
- package/registry/iso.json +1 -1
- package/registry/kanban.json +1 -1
- package/registry/kbd.json +1 -1
- package/registry/landing-page.json +1 -1
- package/registry/landscape.json +1 -1
- package/registry/legal-page.json +1 -1
- package/registry/link-card.json +3 -3
- package/registry/list.json +1 -1
- package/registry/loader.json +1 -1
- package/registry/longform-page.json +1 -1
- package/registry/maintenance-screen.json +1 -1
- package/registry/map.json +2 -2
- package/registry/markdown.json +1 -1
- package/registry/marketing-layout.json +1 -1
- package/registry/marquee.json +1 -1
- package/registry/mascot-quote.json +1 -1
- package/registry/mascot-state.json +1 -1
- package/registry/mascots.json +1 -1
- package/registry/match-dialog.json +1 -1
- package/registry/media-capture.json +3 -3
- package/registry/media-gallery.json +144 -0
- package/registry/media-grid.json +3 -3
- package/registry/meta-row.json +1 -1
- package/registry/meter.json +1 -1
- package/registry/nav-bar.json +2 -2
- package/registry/number-ticker.json +1 -1
- package/registry/opening-hours.json +178 -0
- package/registry/page-header.json +1 -1
- package/registry/page-hero.json +3 -3
- package/registry/page.json +7 -7
- package/registry/pagination.json +1 -1
- package/registry/photo-grid.json +1 -1
- package/registry/pointer-light.json +1 -1
- package/registry/popover.json +1 -1
- package/registry/pricing-page.json +1 -1
- package/registry/product-page.json +1 -1
- package/registry/profile-card.json +1 -1
- package/registry/progress.json +1 -1
- package/registry/promise-card.json +1 -1
- package/registry/radio-group.json +1 -1
- package/registry/rail-nav.json +2 -2
- package/registry/recipes.json +1 -1
- package/registry/scroll-stage.json +1 -1
- package/registry/search-field.json +1 -1
- package/registry/section-backdrop.json +1 -1
- package/registry/separator.json +1 -1
- package/registry/service-card.json +358 -0
- package/registry/settings-layout.json +1 -1
- package/registry/showcase.json +1 -1
- package/registry/site-footer.json +2 -2
- package/registry/sites.json +1 -1
- package/registry/skeleton.json +3 -3
- package/registry/skip-link.json +1 -1
- package/registry/slider.json +1 -1
- package/registry/slot-picker.json +222 -0
- package/registry/slot.json +1 -1
- package/registry/spinner.json +1 -1
- package/registry/split-view.json +1 -1
- package/registry/stage-banner.json +1 -1
- package/registry/stat.json +1 -1
- package/registry/status-page.json +1 -1
- package/registry/step-progress.json +1 -1
- package/registry/step-showcase.json +1 -1
- package/registry/stepper.json +1 -1
- package/registry/sticky-action.json +108 -0
- package/registry/swipe-deck.json +1 -1
- package/registry/switch.json +1 -1
- package/registry/tabs.json +1 -1
- package/registry/tag-input.json +1 -1
- package/registry/tag-selector.json +1 -1
- package/registry/terminal-replay.json +1 -1
- package/registry/theme.json +1 -1
- package/registry/toast.json +1 -1
- package/registry/toc.json +1 -1
- package/registry/tooltip.json +1 -1
- package/registry/tree-view.json +1 -1
- package/registry/update-prompt.json +1 -1
- package/registry/verified-badge.json +1 -1
- package/registry/vertical-video.json +5 -5
- package/registry/video-player.json +3 -3
- package/registry/video-trimmer.json +1 -1
- package/registry/virtual.json +1 -1
- package/registry/waveform.json +1 -1
- package/dist/chunk-CEJDH6SA.js.map +0 -1
- /package/dist/{chunk-YCIBJ6OQ.js.map → chunk-4DH2HW3D.js.map} +0 -0
- /package/dist/{chunk-2L67YTNV.js.map → chunk-FTX66XZY.js.map} +0 -0
- /package/dist/{chunk-F7FVPPBB.js.map → chunk-LYOPPBMC.js.map} +0 -0
package/README.md
CHANGED
|
@@ -212,8 +212,15 @@ light or a product's — is a set of tokens.
|
|
|
212
212
|
| `@krizaka/ui/app-shell` | `AppShell.Root` (`defaultCollapsed`, `collapsed` + `onCollapsedChange`) `/Sidebar` (`label`, `brand`, `footer`, `collapseLabels`, `closeLabel`; a drawer on a phone) `/Group/Item` (`icon`, `label`, `current`, `badge`, `shortcut`, `asChild`) `/Body/Header` (`title`, `menuLabel`, `actions`) `/Search` (`label`, `onOpen`, `shortcut` ⌘K — opens the app's `CommandDialog`) `/Main`; `collapseShortcut` (⌘B), `useShortcuts` — see [Layouts and templates](#layouts-and-templates) | client |
|
|
213
213
|
| `@krizaka/ui/rail-nav` | `RailNav.Shell/Root/Item/Body`: `Root` (`label`, `mark`, `accent`, `footer`, `expandable` + `expanded`/`onExpandedChange`, `expandLabels`) is a vertical rail of icons on a desktop — the mark on top, a tooltip per icon, an accent bar on the current item — and a bottom tab bar on a phone; `Item` (`icon`, `label`, `current`, `badge`, `badgeLabel`, `asChild`); arrow keys, Home and End move between items | client |
|
|
214
214
|
| `@krizaka/ui/app-header` | `AppHeader` (`name`, `mark`, `href` or `homeLink`, `chips`, `actions`): the bar beside a `RailNav` — the name as text, the mark below `md` only (the rail carries it above), so one mark shows at every width | server |
|
|
215
|
+
| `@krizaka/ui/service-card` | `ServiceCard` (`name`, `price`, `priceFrom`, `duration`, `capacity`, `description`, `included`, `tag`, `icon`, `media`, `selected`, `actionLabel`, `href` + `linkAs` or `onSelect`): a service a shop sells as a card with one way in | server |
|
|
216
|
+
| `@krizaka/ui/slot-picker` | `SlotPicker` (`days` of `{ id, label, sub, slots }`, `label`, `emptyLabel`, `value`, `onValueChange`, `linkAs`): the next free times as chips by day — links (`slot.href`) or toggles | client |
|
|
217
|
+
| `@krizaka/ui/opening-hours` | `OpeningHours` (`days`, `today`, `status`, `label`): the weekly hours in one row (open now · closes at…) that unfolds into the week, today marked | server |
|
|
218
|
+
| `@krizaka/ui/cover-hero` | `CoverHero` (`cover` `{ src, alt, video }`, `eyebrow`, `title`, `lead`, `facts`, `actions`, `back`, `linkAs`): a shop's first screen over its photo or muted loop (a still under reduced motion), brand colours without a cover | client |
|
|
219
|
+
| `@krizaka/ui/sticky-action` | `StickyAction`: the page's one action held at the bottom edge of a phone (pure CSS, last child of `main`, not drawn from `md`) | server |
|
|
220
|
+
| `@krizaka/ui/media-gallery` | `MediaGallery` (`items` of `{ id, src, alt, video, caption, shape }`, `label`): a shop's photos and loops in tiles, the first featured, loops play while seen | client |
|
|
215
221
|
| `@krizaka/ui/dashboard-layout` | `DashboardLayout` (`title`, `eyebrow`, `description`, `actions`, `toolbar`, `headingAs`), `Widget` (`title`, `description`, `action`, `span` 1·2·3·full, `level`) | server |
|
|
216
222
|
| `@krizaka/ui/settings-layout` | `SettingsLayout` (`title`, `description`, `nav`, `navLabel`, `linkAs`), `SettingsSection` (`title`, `description`, `footer`) | server |
|
|
223
|
+
| `@krizaka/ui/account-menu` | `AccountMenu` (`label`, `name`, `email`, `avatar`, `links`, `language`, `signOut`) | client |
|
|
217
224
|
| `@krizaka/ui/auth-layout` | `AuthLayout` (`brand`, `title`, `description`, `footer`, `legal`, `promise`, `aside`, `asideFooter`) | server |
|
|
218
225
|
| `@krizaka/ui/detail-layout` | `DetailLayout` (`back`, `media`, `eyebrow`, `title`, `status`, `meta`, `actions`, `tabs`, `aside`) | server |
|
|
219
226
|
| `@krizaka/ui/split-view` | `SplitView` (`listTitle`, `listActions`, `list`, `detailHead`, `detail`, `empty`, `showDetail`, `back`) | server |
|
|
@@ -445,7 +452,7 @@ Testing Library (`src/native/*.test.tsx`); `scripts/native-dist.mjs` checks that
|
|
|
445
452
|
## The marks
|
|
446
453
|
|
|
447
454
|
```tsx
|
|
448
|
-
import { AkinazaLogo, KachizakaLogo, KrizakaLogo, OrazakaLogo, OrochiaLogo, ProductLogo, YasakiaLogo, YomizakaLogo, YororisLogo } from "@krizaka/ui";
|
|
455
|
+
import { AkinazaLogo, KachizakaLogo, KrizakaLogo, OrazakaLogo, OrochiaLogo, ProductLogo, WuSamaMark, YasakiaLogo, YomizakaLogo, YororisLogo } from "@krizaka/ui";
|
|
449
456
|
|
|
450
457
|
<OrochiaLogo size={36} /> // decorative (aria-hidden)
|
|
451
458
|
<KrizakaLogo size={48} title="Krizaka" /> // an image with an accessible name
|
|
@@ -455,6 +462,7 @@ import { AkinazaLogo, KachizakaLogo, KrizakaLogo, OrazakaLogo, OrochiaLogo, Prod
|
|
|
455
462
|
<YomizakaLogo size={32} /> // Yomizaka: ink → indigo, the reading
|
|
456
463
|
<KachizakaLogo size={32} /> // Kachizaka: cobalt → gold, the sealed cup
|
|
457
464
|
<AkinazaLogo size={32} /> // Akinaza: forest → copper, the open shop
|
|
465
|
+
<WuSamaMark size={32} title="Wu-sama" /> // the founder's byline mark (a person, not a product): a gold W in the Krizaka hexagon
|
|
458
466
|
```
|
|
459
467
|
|
|
460
468
|
Neutral strokes follow the host's `--kz-*` tokens when they exist (`--kz-border-strong`, `--kz-text-muted`…) and the
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as tailwind_variants from 'tailwind-variants';
|
|
3
|
+
import { VariantProps } from 'tailwind-variants';
|
|
4
|
+
|
|
5
|
+
type DefaultAvatarBrand = "krizaka" | "orazaka" | "orochia" | "yasakia" | "yororis" | "yomizaka" | "kachizaka" | "akinaza";
|
|
6
|
+
/** `person`: a neutral silhouette. `plain`: the brand's gradient alone, ready for initials. */
|
|
7
|
+
type DefaultAvatarKind = "person" | "plain";
|
|
8
|
+
interface DefaultAvatar {
|
|
9
|
+
/** The brand of the product showing the avatar: its gradient, its hues. */
|
|
10
|
+
brand: DefaultAvatarBrand;
|
|
11
|
+
/** Any stable text or number of the person (their id): the same seed gives the same tone everywhere. */
|
|
12
|
+
seed?: string | number;
|
|
13
|
+
/** Force a tone, 1 to 6, instead of deriving it from `seed`. */
|
|
14
|
+
tone?: number;
|
|
15
|
+
/**
|
|
16
|
+
* `person` (default): a silhouette. `plain`: the gradient alone.
|
|
17
|
+
* @default "person"
|
|
18
|
+
*/
|
|
19
|
+
kind?: DefaultAvatarKind;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare const avatar: tailwind_variants.TVReturnType<{
|
|
23
|
+
size: {
|
|
24
|
+
xs: {
|
|
25
|
+
root: string;
|
|
26
|
+
};
|
|
27
|
+
sm: {
|
|
28
|
+
root: string;
|
|
29
|
+
};
|
|
30
|
+
md: {
|
|
31
|
+
root: string;
|
|
32
|
+
};
|
|
33
|
+
lg: {
|
|
34
|
+
root: string;
|
|
35
|
+
};
|
|
36
|
+
xl: {
|
|
37
|
+
root: string;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
}, {
|
|
41
|
+
root: string;
|
|
42
|
+
image: string;
|
|
43
|
+
fallback: string;
|
|
44
|
+
group: string;
|
|
45
|
+
}, undefined, {
|
|
46
|
+
size: {
|
|
47
|
+
xs: {
|
|
48
|
+
root: string;
|
|
49
|
+
};
|
|
50
|
+
sm: {
|
|
51
|
+
root: string;
|
|
52
|
+
};
|
|
53
|
+
md: {
|
|
54
|
+
root: string;
|
|
55
|
+
};
|
|
56
|
+
lg: {
|
|
57
|
+
root: string;
|
|
58
|
+
};
|
|
59
|
+
xl: {
|
|
60
|
+
root: string;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
}, {
|
|
64
|
+
root: string;
|
|
65
|
+
image: string;
|
|
66
|
+
fallback: string;
|
|
67
|
+
group: string;
|
|
68
|
+
}, tailwind_variants.TVReturnTypeLike<{
|
|
69
|
+
size: {
|
|
70
|
+
xs: {
|
|
71
|
+
root: string;
|
|
72
|
+
};
|
|
73
|
+
sm: {
|
|
74
|
+
root: string;
|
|
75
|
+
};
|
|
76
|
+
md: {
|
|
77
|
+
root: string;
|
|
78
|
+
};
|
|
79
|
+
lg: {
|
|
80
|
+
root: string;
|
|
81
|
+
};
|
|
82
|
+
xl: {
|
|
83
|
+
root: string;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
}, {
|
|
87
|
+
root: string;
|
|
88
|
+
image: string;
|
|
89
|
+
fallback: string;
|
|
90
|
+
group: string;
|
|
91
|
+
}>>;
|
|
92
|
+
type AvatarSize = NonNullable<VariantProps<typeof avatar>["size"]>;
|
|
93
|
+
type AvatarProps = Omit<React.ComponentProps<"span">, "children"> & {
|
|
94
|
+
/** The image's URL; `null` or nothing shows the `fallback`. */
|
|
95
|
+
src?: string | null;
|
|
96
|
+
/** The image's text alternative (the person's name); empty when a visible name sits next to the avatar. */
|
|
97
|
+
alt?: string;
|
|
98
|
+
/** Shown while the image loads, when it fails or when there is none: initials, an icon. */
|
|
99
|
+
fallback?: React.ReactNode;
|
|
100
|
+
/** The picture to show instead when `src` is missing or fails to load (an image URL). The `fallback` still covers its own loading. */
|
|
101
|
+
fallbackSrc?: string;
|
|
102
|
+
/**
|
|
103
|
+
* The Krizaka default picture when the person has no photo (or it fails): their brand's gradient and a silhouette,
|
|
104
|
+
* one of six tones from `seed` — `defaultAvatar={{ brand: "orochia", seed: user.id }}`. `fallbackSrc` wins over it.
|
|
105
|
+
*/
|
|
106
|
+
defaultAvatar?: DefaultAvatar;
|
|
107
|
+
/**
|
|
108
|
+
* xs · sm · md · lg · xl.
|
|
109
|
+
* @default "md"
|
|
110
|
+
*/
|
|
111
|
+
size?: AvatarSize;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
interface AccountMenuLink {
|
|
115
|
+
id: string;
|
|
116
|
+
label: string;
|
|
117
|
+
href: string;
|
|
118
|
+
/** A signature icon. */
|
|
119
|
+
icon?: React.ReactNode;
|
|
120
|
+
}
|
|
121
|
+
interface AccountMenuLanguage {
|
|
122
|
+
/** The row's words (`Language`). */
|
|
123
|
+
label: string;
|
|
124
|
+
/** The current value (`en`). */
|
|
125
|
+
value: string;
|
|
126
|
+
options: readonly {
|
|
127
|
+
value: string;
|
|
128
|
+
label: string;
|
|
129
|
+
}[];
|
|
130
|
+
onChange: (value: string) => void;
|
|
131
|
+
}
|
|
132
|
+
interface AccountMenuProps {
|
|
133
|
+
/** The trigger's accessible name (`Account`). */
|
|
134
|
+
label: string;
|
|
135
|
+
/** Their name; falls back to the e-mail. */
|
|
136
|
+
name?: string;
|
|
137
|
+
/** Their e-mail, shown under the name. */
|
|
138
|
+
email?: string;
|
|
139
|
+
/** The picture: `src`, or `defaultAvatar` (`{ brand, seed }`) for the product's default person. */
|
|
140
|
+
avatar?: Omit<AvatarProps, "size">;
|
|
141
|
+
/** Show the name beside the avatar in the trigger from the `sm` breakpoint. */
|
|
142
|
+
showName?: boolean;
|
|
143
|
+
/** Where to go: profile, settings… */
|
|
144
|
+
links?: readonly AccountMenuLink[];
|
|
145
|
+
/** The app's link component (Next's `Link`); an `<a>` otherwise. */
|
|
146
|
+
linkAs?: React.ElementType;
|
|
147
|
+
/** The language, as a submenu that shows the current one. */
|
|
148
|
+
language?: AccountMenuLanguage;
|
|
149
|
+
/** Extra rows between the language and Sign out (a theme switch…). */
|
|
150
|
+
children?: React.ReactNode;
|
|
151
|
+
/** The last row: its words, what it does, an optional icon. */
|
|
152
|
+
signOut: {
|
|
153
|
+
label: string;
|
|
154
|
+
onSelect: () => void;
|
|
155
|
+
icon?: React.ReactNode;
|
|
156
|
+
};
|
|
157
|
+
/** Classes for the trigger. */
|
|
158
|
+
className?: string;
|
|
159
|
+
}
|
|
160
|
+
/** The account menu: avatar trigger → identity header, links, language submenu, Sign out. */
|
|
161
|
+
declare function AccountMenu({ label, name, email, avatar, showName, links, linkAs: Link, language, children, signOut, className }: AccountMenuProps): React.JSX.Element;
|
|
162
|
+
|
|
163
|
+
export { AccountMenu, type AccountMenuLanguage, type AccountMenuLink, type AccountMenuProps };
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-KH3JOEPY.js";
|
|
4
|
+
|
|
5
|
+
// src/avatar/avatar.tsx
|
|
6
|
+
import { Avatar as AvatarPrimitive } from "radix-ui";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { tv } from "tailwind-variants";
|
|
9
|
+
|
|
10
|
+
// src/avatar/default-avatar.ts
|
|
11
|
+
var DEFAULT_AVATAR_TONES = 6;
|
|
12
|
+
var DEFAULT_AVATAR_BASE = "https://krizaka-assets.b-cdn.net/people/defaults/v1";
|
|
13
|
+
var clampTone = (tone) => Math.min(DEFAULT_AVATAR_TONES, Math.max(1, Math.round(tone)));
|
|
14
|
+
function defaultAvatarToneOf(seed) {
|
|
15
|
+
if (seed === void 0) return 1;
|
|
16
|
+
let h = 2166136261;
|
|
17
|
+
for (const ch of String(seed)) {
|
|
18
|
+
h ^= ch.codePointAt(0) ?? 0;
|
|
19
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
20
|
+
}
|
|
21
|
+
return h % DEFAULT_AVATAR_TONES + 1;
|
|
22
|
+
}
|
|
23
|
+
var resolveTone = (a) => a.tone === void 0 ? defaultAvatarToneOf(a.seed) : clampTone(a.tone);
|
|
24
|
+
function defaultAvatarFile(kind, tone, size) {
|
|
25
|
+
return size === "svg" ? `${kind}-${tone}.svg` : `${kind}-${tone}-${size}.webp`;
|
|
26
|
+
}
|
|
27
|
+
function trimTrailingSlashes(value) {
|
|
28
|
+
let end = value.length;
|
|
29
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
|
|
30
|
+
return value.slice(0, end);
|
|
31
|
+
}
|
|
32
|
+
function defaultAvatarUrl(avatar2, { size = 256, base = DEFAULT_AVATAR_BASE } = {}) {
|
|
33
|
+
return `${trimTrailingSlashes(base)}/${avatar2.brand}/${defaultAvatarFile(avatar2.kind ?? "person", resolveTone(avatar2), size)}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/avatar/avatar.tsx
|
|
37
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
38
|
+
var avatar = tv({
|
|
39
|
+
slots: {
|
|
40
|
+
root: "relative inline-flex shrink-0 overflow-hidden rounded-full bg-surface-3 align-middle",
|
|
41
|
+
image: "h-full w-full object-cover",
|
|
42
|
+
fallback: "flex h-full w-full items-center justify-center font-semibold uppercase text-fg-secondary",
|
|
43
|
+
group: "flex items-center -space-x-2 [&>*]:ring-2 [&>*]:ring-surface-0"
|
|
44
|
+
},
|
|
45
|
+
variants: {
|
|
46
|
+
size: {
|
|
47
|
+
xs: { root: "h-5 w-5 text-[9px]" },
|
|
48
|
+
sm: { root: "h-8 w-8 text-xs" },
|
|
49
|
+
md: { root: "h-10 w-10 text-sm" },
|
|
50
|
+
lg: { root: "h-14 w-14 text-base" },
|
|
51
|
+
xl: { root: "h-20 w-20 text-lg" }
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
defaultVariants: { size: "md" }
|
|
55
|
+
});
|
|
56
|
+
function AvatarRoot({ src, alt = "", fallback, fallbackSrc, defaultAvatar, size, className, ...props }) {
|
|
57
|
+
const s2 = avatar({ size });
|
|
58
|
+
const [failed, setFailed] = React.useState(null);
|
|
59
|
+
const backup = fallbackSrc ?? (defaultAvatar ? defaultAvatarUrl(defaultAvatar, { size: size === "xl" || size === "lg" ? 512 : 256 }) : void 0);
|
|
60
|
+
const shown = src && failed !== src ? src : backup;
|
|
61
|
+
return /* @__PURE__ */ jsxs(AvatarPrimitive.Root, { "data-size": size ?? "md", className: s2.root({ className }), ...props, children: [
|
|
62
|
+
shown && /* @__PURE__ */ jsx(
|
|
63
|
+
AvatarPrimitive.Image,
|
|
64
|
+
{
|
|
65
|
+
src: shown,
|
|
66
|
+
alt,
|
|
67
|
+
className: s2.image(),
|
|
68
|
+
onLoadingStatusChange: (status) => {
|
|
69
|
+
if (status === "error" && shown === src) setFailed(src);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
),
|
|
73
|
+
/* @__PURE__ */ jsx(AvatarPrimitive.Fallback, { delayMs: shown ? 300 : 0, className: s2.fallback(), children: fallback })
|
|
74
|
+
] });
|
|
75
|
+
}
|
|
76
|
+
function AvatarGroup({ max, size, className, children, ...props }) {
|
|
77
|
+
const items = React.Children.toArray(children);
|
|
78
|
+
const shown = max === void 0 ? items : items.slice(0, max);
|
|
79
|
+
const rest = items.length - shown.length;
|
|
80
|
+
return /* @__PURE__ */ jsxs("div", { className: avatar().group({ className }), ...props, children: [
|
|
81
|
+
shown,
|
|
82
|
+
rest > 0 && /* @__PURE__ */ jsx(AvatarRoot, { size, fallback: `+${rest}` })
|
|
83
|
+
] });
|
|
84
|
+
}
|
|
85
|
+
var Avatar = Object.assign(AvatarRoot, { Group: AvatarGroup });
|
|
86
|
+
|
|
87
|
+
// src/dropdown-menu/dropdown-menu.tsx
|
|
88
|
+
import { DropdownMenu as MenuPrimitive } from "radix-ui";
|
|
89
|
+
import { tv as tv2 } from "tailwind-variants";
|
|
90
|
+
|
|
91
|
+
// src/floating.ts
|
|
92
|
+
var floating = "kz-pop z-50 rounded-lg border border-border-default bg-surface-2 text-fg shadow-lg outline-hidden";
|
|
93
|
+
|
|
94
|
+
// src/dropdown-menu/dropdown-menu.tsx
|
|
95
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
96
|
+
var menu = tv2({
|
|
97
|
+
slots: {
|
|
98
|
+
content: cn(floating, "min-w-48 max-h-(--radix-dropdown-menu-content-available-height) overflow-y-auto overflow-x-hidden overscroll-contain p-1 origin-(--radix-dropdown-menu-content-transform-origin)"),
|
|
99
|
+
item: "relative flex cursor-pointer select-none items-center gap-2 rounded-md px-2.5 py-2 text-sm text-fg outline-hidden transition-colors data-highlighted:bg-surface-3 data-disabled:pointer-events-none data-disabled:opacity-40 [&>svg]:h-4 [&>svg]:w-4 [&>svg]:shrink-0 [&>svg]:text-fg-secondary",
|
|
100
|
+
indicator: "ml-auto flex h-4 w-4 items-center justify-center text-fg-accent",
|
|
101
|
+
label: "px-2.5 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary",
|
|
102
|
+
separator: "-mx-1 my-1 h-px bg-border-subtle"
|
|
103
|
+
},
|
|
104
|
+
variants: {
|
|
105
|
+
// The label reads in the danger text role (≥ 4.5:1 on the menu surface, both themes); the icon and the highlight
|
|
106
|
+
// carry the danger fill.
|
|
107
|
+
tone: { default: {}, danger: { item: "text-fg-danger data-highlighted:bg-danger/15 [&>svg]:text-danger" } }
|
|
108
|
+
},
|
|
109
|
+
defaultVariants: { tone: "default" }
|
|
110
|
+
});
|
|
111
|
+
var s = menu();
|
|
112
|
+
function DropdownMenuContent({ sideOffset = 6, align = "end", collisionPadding = 8, container, className, ...props }) {
|
|
113
|
+
return /* @__PURE__ */ jsx2(MenuPrimitive.Portal, { container, children: /* @__PURE__ */ jsx2(MenuPrimitive.Content, { sideOffset, align, collisionPadding, className: s.content({ className }), ...props }) });
|
|
114
|
+
}
|
|
115
|
+
function DropdownMenuItem({ tone, className, ...props }) {
|
|
116
|
+
return /* @__PURE__ */ jsx2(MenuPrimitive.Item, { "data-tone": tone ?? "default", className: menu({ tone }).item({ className }), ...props });
|
|
117
|
+
}
|
|
118
|
+
function DropdownMenuCheckboxItem({ className, children, ...props }) {
|
|
119
|
+
return /* @__PURE__ */ jsxs2(MenuPrimitive.CheckboxItem, { className: s.item({ className }), ...props, children: [
|
|
120
|
+
children,
|
|
121
|
+
/* @__PURE__ */ jsx2(MenuPrimitive.ItemIndicator, { className: s.indicator(), children: /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx2("path", { d: "M20 6 9 17l-5-5" }) }) })
|
|
122
|
+
] });
|
|
123
|
+
}
|
|
124
|
+
function DropdownMenuSubTrigger({ className, children, ...props }) {
|
|
125
|
+
return /* @__PURE__ */ jsxs2(MenuPrimitive.SubTrigger, { className: s.item({ className: cn("data-[state=open]:bg-surface-3", className) }), ...props, children: [
|
|
126
|
+
children,
|
|
127
|
+
/* @__PURE__ */ jsx2("svg", { className: "ml-auto !h-4 !w-4 text-fg-secondary", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx2("path", { d: "m9 18 6-6-6-6" }) })
|
|
128
|
+
] });
|
|
129
|
+
}
|
|
130
|
+
function DropdownMenuSubContent({ sideOffset = 4, collisionPadding = 8, className, ...props }) {
|
|
131
|
+
return /* @__PURE__ */ jsx2(MenuPrimitive.Portal, { children: /* @__PURE__ */ jsx2(MenuPrimitive.SubContent, { sideOffset, collisionPadding, className: s.content({ className: cn("max-h-(--radix-dropdown-menu-content-available-height)", className) }), ...props }) });
|
|
132
|
+
}
|
|
133
|
+
function DropdownMenuRadioItem({ className, children, ...props }) {
|
|
134
|
+
return /* @__PURE__ */ jsxs2(MenuPrimitive.RadioItem, { className: s.item({ className }), ...props, children: [
|
|
135
|
+
children,
|
|
136
|
+
/* @__PURE__ */ jsx2(MenuPrimitive.ItemIndicator, { className: s.indicator(), children: /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx2("path", { d: "M20 6 9 17l-5-5" }) }) })
|
|
137
|
+
] });
|
|
138
|
+
}
|
|
139
|
+
function DropdownMenuLabel({ className, ...props }) {
|
|
140
|
+
return /* @__PURE__ */ jsx2(MenuPrimitive.Label, { className: s.label({ className }), ...props });
|
|
141
|
+
}
|
|
142
|
+
function DropdownMenuSeparator({ className, ...props }) {
|
|
143
|
+
return /* @__PURE__ */ jsx2(MenuPrimitive.Separator, { className: s.separator({ className }), ...props });
|
|
144
|
+
}
|
|
145
|
+
var DropdownMenu = {
|
|
146
|
+
Root: MenuPrimitive.Root,
|
|
147
|
+
Trigger: MenuPrimitive.Trigger,
|
|
148
|
+
Content: DropdownMenuContent,
|
|
149
|
+
Item: DropdownMenuItem,
|
|
150
|
+
CheckboxItem: DropdownMenuCheckboxItem,
|
|
151
|
+
Label: DropdownMenuLabel,
|
|
152
|
+
Separator: DropdownMenuSeparator,
|
|
153
|
+
Group: MenuPrimitive.Group,
|
|
154
|
+
Sub: MenuPrimitive.Sub,
|
|
155
|
+
SubTrigger: DropdownMenuSubTrigger,
|
|
156
|
+
SubContent: DropdownMenuSubContent,
|
|
157
|
+
RadioGroup: MenuPrimitive.RadioGroup,
|
|
158
|
+
RadioItem: DropdownMenuRadioItem
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// src/account-menu/account-menu.tsx
|
|
162
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
163
|
+
function AccountMenu({ label, name, email, avatar: avatar2, showName = true, links = [], linkAs: Link = "a", language, children, signOut, className }) {
|
|
164
|
+
const title = name || email || "";
|
|
165
|
+
const current = language?.options.find((o) => o.value === language.value)?.label;
|
|
166
|
+
return /* @__PURE__ */ jsxs3(DropdownMenu.Root, { children: [
|
|
167
|
+
/* @__PURE__ */ jsxs3(
|
|
168
|
+
DropdownMenu.Trigger,
|
|
169
|
+
{
|
|
170
|
+
"aria-label": label,
|
|
171
|
+
className: cn("flex items-center gap-2.5 rounded-lg p-1 transition-colors duration-150 hover:bg-surface-2 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring", className),
|
|
172
|
+
children: [
|
|
173
|
+
/* @__PURE__ */ jsx3(Avatar, { size: "sm", fallback: title.charAt(0).toUpperCase() || "?", ...avatar2 }),
|
|
174
|
+
showName && title && /* @__PURE__ */ jsx3("span", { "aria-hidden": true, "data-name": title, className: "hidden max-w-40 truncate text-sm font-medium text-fg-secondary after:content-[attr(data-name)] sm:inline-block" })
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
),
|
|
178
|
+
/* @__PURE__ */ jsxs3(DropdownMenu.Content, { align: "end", className: "w-64", children: [
|
|
179
|
+
/* @__PURE__ */ jsxs3(DropdownMenu.Label, { className: "flex items-center gap-3 py-2 normal-case tracking-normal", children: [
|
|
180
|
+
/* @__PURE__ */ jsx3(Avatar, { size: "md", fallback: title.charAt(0).toUpperCase() || "?", ...avatar2 }),
|
|
181
|
+
/* @__PURE__ */ jsxs3("span", { className: "min-w-0", children: [
|
|
182
|
+
/* @__PURE__ */ jsx3("span", { className: "block truncate text-sm font-semibold text-fg", children: title }),
|
|
183
|
+
name && email && /* @__PURE__ */ jsx3("span", { className: "block truncate text-xs font-normal text-fg-secondary", children: email })
|
|
184
|
+
] })
|
|
185
|
+
] }),
|
|
186
|
+
links.length > 0 && /* @__PURE__ */ jsx3(DropdownMenu.Separator, {}),
|
|
187
|
+
links.map((link) => /* @__PURE__ */ jsx3(DropdownMenu.Item, { asChild: true, children: /* @__PURE__ */ jsxs3(Link, { href: link.href, children: [
|
|
188
|
+
link.icon,
|
|
189
|
+
link.label
|
|
190
|
+
] }) }, link.id)),
|
|
191
|
+
(language || children) && /* @__PURE__ */ jsx3(DropdownMenu.Separator, {}),
|
|
192
|
+
language && /* @__PURE__ */ jsxs3(DropdownMenu.Sub, { children: [
|
|
193
|
+
/* @__PURE__ */ jsxs3(DropdownMenu.SubTrigger, { children: [
|
|
194
|
+
language.label,
|
|
195
|
+
current && /* @__PURE__ */ jsx3("span", { className: "ml-auto mr-1 text-xs text-fg-secondary", children: current })
|
|
196
|
+
] }),
|
|
197
|
+
/* @__PURE__ */ jsx3(DropdownMenu.SubContent, { children: /* @__PURE__ */ jsx3(DropdownMenu.RadioGroup, { value: language.value, onValueChange: language.onChange, children: language.options.map((o) => /* @__PURE__ */ jsx3(DropdownMenu.RadioItem, { value: o.value, children: o.label }, o.value)) }) })
|
|
198
|
+
] }),
|
|
199
|
+
children,
|
|
200
|
+
/* @__PURE__ */ jsx3(DropdownMenu.Separator, {}),
|
|
201
|
+
/* @__PURE__ */ jsxs3(DropdownMenu.Item, { onSelect: () => signOut.onSelect(), children: [
|
|
202
|
+
signOut.icon,
|
|
203
|
+
signOut.label
|
|
204
|
+
] })
|
|
205
|
+
] })
|
|
206
|
+
] });
|
|
207
|
+
}
|
|
208
|
+
export {
|
|
209
|
+
AccountMenu
|
|
210
|
+
};
|
|
211
|
+
//# sourceMappingURL=account-menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/avatar/avatar.tsx","../src/avatar/default-avatar.ts","../src/dropdown-menu/dropdown-menu.tsx","../src/floating.ts","../src/account-menu/account-menu.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\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\" | \"yomizaka\" | \"kachizaka\" | \"akinaza\";\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\", \"yomizaka\", \"kachizaka\", \"akinaza\"];\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 yomizaka: { from: [232, 50, 55], to: [248, 50, 58], spread: 10 },\n kachizaka: { from: [226, 80, 56], to: [44, 92, 33], spread: 10 },\n akinaza: { from: [140, 48, 34], to: [22, 60, 46], 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/** Linear on purpose: a `/\\/+$/` regex backtracks quadratically on a long run of slashes. */\nfunction trimTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charCodeAt(end - 1) === 47) end--;\n return value.slice(0, end);\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 `${trimTrailingSlashes(base)}/${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","// Client (a portal). Radix does the roving focus, the arrows, Home/End, typeahead, Escape and the focus return.\nimport { DropdownMenu as MenuPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { floating } from \"../floating\";\n\nexport const menu = tv({\n slots: {\n content: cn(floating, \"min-w-48 max-h-(--radix-dropdown-menu-content-available-height) overflow-y-auto overflow-x-hidden overscroll-contain p-1 origin-(--radix-dropdown-menu-content-transform-origin)\"),\n item:\n \"relative flex cursor-pointer select-none items-center gap-2 rounded-md px-2.5 py-2 text-sm text-fg outline-hidden transition-colors \" +\n \"data-highlighted:bg-surface-3 data-disabled:pointer-events-none data-disabled:opacity-40 [&>svg]:h-4 [&>svg]:w-4 [&>svg]:shrink-0 \" +\n \"[&>svg]:text-fg-secondary\",\n indicator: \"ml-auto flex h-4 w-4 items-center justify-center text-fg-accent\",\n label: \"px-2.5 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n separator: \"-mx-1 my-1 h-px bg-border-subtle\",\n },\n variants: {\n // The label reads in the danger text role (≥ 4.5:1 on the menu surface, both themes); the icon and the highlight\n // carry the danger fill.\n tone: { default: {}, danger: { item: \"text-fg-danger data-highlighted:bg-danger/15 [&>svg]:text-danger\" } },\n },\n defaultVariants: { tone: \"default\" },\n});\nconst s = menu();\n\nexport type DropdownMenuContentProps = React.ComponentProps<typeof MenuPrimitive.Content> & {\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n};\n\nexport function DropdownMenuContent({ sideOffset = 6, align = \"end\", collisionPadding = 8, container, className, ...props }: DropdownMenuContentProps) {\n return (\n <MenuPrimitive.Portal container={container}>\n <MenuPrimitive.Content sideOffset={sideOffset} align={align} collisionPadding={collisionPadding} className={s.content({ className })} {...props} />\n </MenuPrimitive.Portal>\n );\n}\n\nexport type DropdownMenuItemProps = React.ComponentProps<typeof MenuPrimitive.Item> & {\n /** `danger` for a destructive action: the icon and the highlight carry the danger, the label stays legible. */\n tone?: \"default\" | \"danger\";\n};\n\n/** An action. `tone=\"danger\"` for a destructive one; `onSelect` runs it (the menu closes). */\nexport function DropdownMenuItem({ tone, className, ...props }: DropdownMenuItemProps) {\n return <MenuPrimitive.Item data-tone={tone ?? \"default\"} className={menu({ tone }).item({ className })} {...props} />;\n}\n\n/** A toggled option: `checked` + `onCheckedChange`. */\nexport function DropdownMenuCheckboxItem({ className, children, ...props }: React.ComponentProps<typeof MenuPrimitive.CheckboxItem>) {\n return (\n <MenuPrimitive.CheckboxItem className={s.item({ className })} {...props}>\n {children}\n <MenuPrimitive.ItemIndicator className={s.indicator()}>\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </MenuPrimitive.ItemIndicator>\n </MenuPrimitive.CheckboxItem>\n );\n}\n\n/** A submenu's trigger: an item that opens `DropdownMenu.SubContent`, with a chevron. */\nexport function DropdownMenuSubTrigger({ className, children, ...props }: React.ComponentProps<typeof MenuPrimitive.SubTrigger>) {\n return (\n <MenuPrimitive.SubTrigger className={s.item({ className: cn(\"data-[state=open]:bg-surface-3\", className) })} {...props}>\n {children}\n <svg className=\"ml-auto !h-4 !w-4 text-fg-secondary\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </MenuPrimitive.SubTrigger>\n );\n}\n\n/** The submenu's panel (same material as the menu). */\nexport function DropdownMenuSubContent({ sideOffset = 4, collisionPadding = 8, className, ...props }: React.ComponentProps<typeof MenuPrimitive.SubContent>) {\n return (\n <MenuPrimitive.Portal>\n <MenuPrimitive.SubContent sideOffset={sideOffset} collisionPadding={collisionPadding} className={s.content({ className: cn(\"max-h-(--radix-dropdown-menu-content-available-height)\", className) })} {...props} />\n </MenuPrimitive.Portal>\n );\n}\n\n/** One choice among several (`DropdownMenu.RadioGroup` holds the value): a language, a sort order. */\nexport function DropdownMenuRadioItem({ className, children, ...props }: React.ComponentProps<typeof MenuPrimitive.RadioItem>) {\n return (\n <MenuPrimitive.RadioItem className={s.item({ className })} {...props}>\n {children}\n <MenuPrimitive.ItemIndicator className={s.indicator()}>\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </MenuPrimitive.ItemIndicator>\n </MenuPrimitive.RadioItem>\n );\n}\n\nexport function DropdownMenuLabel({ className, ...props }: React.ComponentProps<typeof MenuPrimitive.Label>) {\n return <MenuPrimitive.Label className={s.label({ className })} {...props} />;\n}\n\nexport function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof MenuPrimitive.Separator>) {\n return <MenuPrimitive.Separator className={s.separator({ className })} {...props} />;\n}\n\n/** `DropdownMenu.Root/Trigger/Content/Item/CheckboxItem/Label/Separator/Group`. `open`/`defaultOpen` + `onOpenChange`. */\nexport const DropdownMenu = {\n Root: MenuPrimitive.Root,\n Trigger: MenuPrimitive.Trigger,\n Content: DropdownMenuContent,\n Item: DropdownMenuItem,\n CheckboxItem: DropdownMenuCheckboxItem,\n Label: DropdownMenuLabel,\n Separator: DropdownMenuSeparator,\n Group: MenuPrimitive.Group,\n Sub: MenuPrimitive.Sub,\n SubTrigger: DropdownMenuSubTrigger,\n SubContent: DropdownMenuSubContent,\n RadioGroup: MenuPrimitive.RadioGroup,\n RadioItem: DropdownMenuRadioItem,\n};\n","// The one surface of everything that floats next to its trigger (popover, menu, tooltip): a raised surface, a border,\n// the large shadow and the `kz-pop` entrance from the side Radix measured (`--radix-*-transform-origin`).\nexport const floating =\n \"kz-pop z-50 rounded-lg border border-border-default bg-surface-2 text-fg shadow-lg outline-hidden\";\n","// Client (a portal). The signed-in person's menu under their avatar — who you are first (picture, name, e-mail), then\n// the places (profile, settings), the language as a compact submenu, the extras, and Sign out (a quiet item, not a\n// red alert). Built on DropdownMenu, so the keyboard, the focus return and the collisions are Radix's.\nimport * as React from \"react\";\n\nimport { Avatar, type AvatarProps } from \"../avatar/avatar\";\nimport { cn } from \"../cn\";\nimport { DropdownMenu } from \"../dropdown-menu/dropdown-menu\";\n\nexport interface AccountMenuLink {\n id: string;\n label: string;\n href: string;\n /** A signature icon. */\n icon?: React.ReactNode;\n}\n\nexport interface AccountMenuLanguage {\n /** The row's words (`Language`). */\n label: string;\n /** The current value (`en`). */\n value: string;\n options: readonly { value: string; label: string }[];\n onChange: (value: string) => void;\n}\n\nexport interface AccountMenuProps {\n /** The trigger's accessible name (`Account`). */\n label: string;\n /** Their name; falls back to the e-mail. */\n name?: string;\n /** Their e-mail, shown under the name. */\n email?: string;\n /** The picture: `src`, or `defaultAvatar` (`{ brand, seed }`) for the product's default person. */\n avatar?: Omit<AvatarProps, \"size\">;\n /** Show the name beside the avatar in the trigger from the `sm` breakpoint. */\n showName?: boolean;\n /** Where to go: profile, settings… */\n links?: readonly AccountMenuLink[];\n /** The app's link component (Next's `Link`); an `<a>` otherwise. */\n linkAs?: React.ElementType;\n /** The language, as a submenu that shows the current one. */\n language?: AccountMenuLanguage;\n /** Extra rows between the language and Sign out (a theme switch…). */\n children?: React.ReactNode;\n /** The last row: its words, what it does, an optional icon. */\n signOut: { label: string; onSelect: () => void; icon?: React.ReactNode };\n /** Classes for the trigger. */\n className?: string;\n}\n\n/** The account menu: avatar trigger → identity header, links, language submenu, Sign out. */\nexport function AccountMenu({ label, name, email, avatar, showName = true, links = [], linkAs: Link = \"a\", language, children, signOut, className }: AccountMenuProps) {\n const title = name || email || \"\";\n const current = language?.options.find((o) => o.value === language.value)?.label;\n return (\n <DropdownMenu.Root>\n <DropdownMenu.Trigger\n aria-label={label}\n className={cn(\"flex items-center gap-2.5 rounded-lg p-1 transition-colors duration-150 hover:bg-surface-2 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\", className)}\n >\n <Avatar size=\"sm\" fallback={title.charAt(0).toUpperCase() || \"?\"} {...avatar} />\n {/* Drawn by CSS: the name is not text of the button, so its accessible name stays `label` with no label-content-name-mismatch. */}\n {showName && title && <span aria-hidden data-name={title} className=\"hidden max-w-40 truncate text-sm font-medium text-fg-secondary after:content-[attr(data-name)] sm:inline-block\" />}\n </DropdownMenu.Trigger>\n <DropdownMenu.Content align=\"end\" className=\"w-64\">\n <DropdownMenu.Label className=\"flex items-center gap-3 py-2 normal-case tracking-normal\">\n <Avatar size=\"md\" fallback={title.charAt(0).toUpperCase() || \"?\"} {...avatar} />\n <span className=\"min-w-0\">\n <span className=\"block truncate text-sm font-semibold text-fg\">{title}</span>\n {name && email && <span className=\"block truncate text-xs font-normal text-fg-secondary\">{email}</span>}\n </span>\n </DropdownMenu.Label>\n {links.length > 0 && <DropdownMenu.Separator />}\n {links.map((link) => (\n <DropdownMenu.Item key={link.id} asChild>\n <Link href={link.href}>\n {link.icon}\n {link.label}\n </Link>\n </DropdownMenu.Item>\n ))}\n {(language || children) && <DropdownMenu.Separator />}\n {language && (\n <DropdownMenu.Sub>\n <DropdownMenu.SubTrigger>\n {language.label}\n {current && <span className=\"ml-auto mr-1 text-xs text-fg-secondary\">{current}</span>}\n </DropdownMenu.SubTrigger>\n <DropdownMenu.SubContent>\n <DropdownMenu.RadioGroup value={language.value} onValueChange={language.onChange}>\n {language.options.map((o) => (\n <DropdownMenu.RadioItem key={o.value} value={o.value}>\n {o.label}\n </DropdownMenu.RadioItem>\n ))}\n </DropdownMenu.RadioGroup>\n </DropdownMenu.SubContent>\n </DropdownMenu.Sub>\n )}\n {children}\n <DropdownMenu.Separator />\n <DropdownMenu.Item onSelect={() => signOut.onSelect()}>\n {signOut.icon}\n {signOut.label}\n </DropdownMenu.Item>\n </DropdownMenu.Content>\n </DropdownMenu.Root>\n );\n}\n"],"mappings":"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","names":["avatar","s","tv","jsx","jsxs","tv","jsx","jsxs","avatar"]}
|
package/dist/app-shell.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import {
|
|
3
|
+
SkipLink
|
|
4
|
+
} from "./chunk-QDXV544C.js";
|
|
2
5
|
import {
|
|
3
6
|
ChevronGlyph,
|
|
4
7
|
CloseGlyph,
|
|
5
8
|
SearchGlyph
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-P7GZR5CU.js";
|
|
7
10
|
import {
|
|
8
11
|
cn
|
|
9
12
|
} from "./chunk-WU4VLSEA.js";
|
|
10
|
-
import {
|
|
11
|
-
SkipLink
|
|
12
|
-
} from "./chunk-QDXV544C.js";
|
|
13
13
|
|
|
14
14
|
// src/app-shell/app-shell.tsx
|
|
15
15
|
import { Dialog as DialogPrimitive, Slot } from "radix-ui";
|
package/dist/changelog-page.d.ts
CHANGED
|
@@ -100,5 +100,17 @@ declare function changelogRss(opts: {
|
|
|
100
100
|
}): string;
|
|
101
101
|
/** The template's kind for a tag: feature → added, fix → fixed, security and breaking stay themselves, the rest → changed. */
|
|
102
102
|
declare const kindOfTag: (tag: ChangeTag) => ChangeKind;
|
|
103
|
+
interface MergedPull {
|
|
104
|
+
title: string;
|
|
105
|
+
html_url: string;
|
|
106
|
+
/** ISO timestamp, `null` for a pull request closed without merging. */
|
|
107
|
+
merged_at: string | null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* For a repository that publishes no releases (a product still in development): its merged pull requests, by week.
|
|
111
|
+
* Each week is one entry (`version` = the Monday, `date` = the last merge), its changes the conventional titles in plain
|
|
112
|
+
* language — housekeeping (chore, ci, test, docs…) is left out, weeks with nothing to say are dropped.
|
|
113
|
+
*/
|
|
114
|
+
declare function releasesFromPulls(pulls: readonly MergedPull[], source: string, url?: string): Release[];
|
|
103
115
|
|
|
104
|
-
export { CHANGE_TAGS, type ChangeItem, type ChangeKind, type ChangeTag, type ChangelogEntry, ChangelogPage, type ChangelogPageProps, type Release, anchorOf, changeFromTitle, changelogRss, draftFromTitles, kindOfTag, newestFirst, parseChangesets, parseReleaseBody, plain, prOf, tagOf };
|
|
116
|
+
export { CHANGE_TAGS, type ChangeItem, type ChangeKind, type ChangeTag, type ChangelogEntry, ChangelogPage, type ChangelogPageProps, type MergedPull, type Release, anchorOf, changeFromTitle, changelogRss, draftFromTitles, kindOfTag, newestFirst, parseChangesets, parseReleaseBody, plain, prOf, releasesFromPulls, tagOf };
|
package/dist/changelog-page.js
CHANGED
|
@@ -101,6 +101,25 @@ function changelogRss(opts) {
|
|
|
101
101
|
return `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>${esc(opts.title)}</title><link>${esc(opts.pageUrl)}</link><description>${esc(opts.description)}</description><language>${opts.language}</language><atom:link href="${esc(opts.feedUrl)}" rel="self" type="application/rss+xml"/>${items}</channel></rss>`;
|
|
102
102
|
}
|
|
103
103
|
var kindOfTag = (tag) => tag === "feature" ? "added" : tag === "fix" ? "fixed" : tag === "change" ? "changed" : tag;
|
|
104
|
+
var mondayOf = (iso) => {
|
|
105
|
+
const d = /* @__PURE__ */ new Date(`${iso.slice(0, 10)}T12:00:00Z`);
|
|
106
|
+
d.setUTCDate(d.getUTCDate() - (d.getUTCDay() + 6) % 7);
|
|
107
|
+
return d.toISOString().slice(0, 10);
|
|
108
|
+
};
|
|
109
|
+
function releasesFromPulls(pulls, source, url) {
|
|
110
|
+
const weeks = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const p of pulls) {
|
|
112
|
+
if (!p.merged_at) continue;
|
|
113
|
+
const item = changeFromTitle(p.title, p.html_url);
|
|
114
|
+
if (!item) continue;
|
|
115
|
+
const key = mondayOf(p.merged_at);
|
|
116
|
+
const week = weeks.get(key) ?? { date: p.merged_at.slice(0, 10), changes: [] };
|
|
117
|
+
if (p.merged_at.slice(0, 10) > week.date) week.date = p.merged_at.slice(0, 10);
|
|
118
|
+
week.changes.push(item);
|
|
119
|
+
weeks.set(key, week);
|
|
120
|
+
}
|
|
121
|
+
return [...weeks].map(([version, w]) => ({ source, version, date: w.date, url, changes: w.changes })).sort(newestFirst);
|
|
122
|
+
}
|
|
104
123
|
|
|
105
124
|
// src/changelog-page/changelog-page.tsx
|
|
106
125
|
import { Page } from "@krizaka/ui/page";
|
|
@@ -138,6 +157,7 @@ export {
|
|
|
138
157
|
parseReleaseBody,
|
|
139
158
|
plain,
|
|
140
159
|
prOf,
|
|
160
|
+
releasesFromPulls,
|
|
141
161
|
tagOf
|
|
142
162
|
};
|
|
143
163
|
//# sourceMappingURL=changelog-page.js.map
|