@krizaka/ui 3.4.0 → 3.5.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/argument-card.js +3 -3
- package/dist/chat.d.ts +187 -0
- package/dist/chat.js +132 -0
- package/dist/chat.js.map +1 -0
- package/dist/chunk-ECSCA3CN.js +128 -0
- package/dist/chunk-ECSCA3CN.js.map +1 -0
- package/dist/chunk-F2GKFIYK.js +47 -0
- package/dist/chunk-F2GKFIYK.js.map +1 -0
- package/dist/data-table.js +20 -134
- package/dist/data-table.js.map +1 -1
- package/dist/match-dialog.d.ts +30 -0
- package/dist/match-dialog.js +146 -0
- package/dist/match-dialog.js.map +1 -0
- package/dist/native.d.ts +421 -106
- package/dist/native.js +1535 -973
- package/dist/native.js.map +1 -1
- package/dist/page-hero.js +3 -3
- package/dist/photo-grid.d.ts +122 -0
- package/dist/photo-grid.js +94 -0
- package/dist/photo-grid.js.map +1 -0
- package/dist/step-progress.d.ts +20 -0
- package/dist/step-progress.js +17 -0
- package/dist/step-progress.js.map +1 -0
- package/dist/swipe-deck.d.ts +183 -0
- package/dist/swipe-deck.js +144 -0
- package/dist/swipe-deck.js.map +1 -0
- package/dist/tag-selector.d.ts +26 -0
- package/dist/tag-selector.js +21 -0
- package/dist/tag-selector.js.map +1 -0
- package/dist/verified-badge.d.ts +16 -0
- package/dist/verified-badge.js +23 -0
- package/dist/verified-badge.js.map +1 -0
- package/package.json +105 -7
- package/registry/achievement.json +1 -1
- package/registry/action-bar.json +1 -1
- package/registry/activity-feed.json +1 -1
- package/registry/alert.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/card-grid.json +1 -1
- package/registry/card.json +1 -1
- package/registry/catalogue-page.json +1 -1
- package/registry/changelog-page.json +1 -1
- package/registry/chat.json +917 -0
- 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/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/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/dropdown-menu.json +1 -1
- package/registry/empty-state.json +1 -1
- package/registry/error-page.json +1 -1
- package/registry/examples/chat/conversation.d.ts +4 -0
- package/registry/examples/chat/conversation.js +22 -0
- package/registry/examples/chat/conversation.tsx +21 -0
- package/registry/examples/chat/inbox.d.ts +4 -0
- package/registry/examples/chat/inbox.js +15 -0
- package/registry/examples/chat/inbox.tsx +12 -0
- package/registry/examples/chat/native/conversation.d.ts +4 -0
- package/registry/examples/chat/native/conversation.dark.png +0 -0
- package/registry/examples/chat/native/conversation.js +23 -0
- package/registry/examples/chat/native/conversation.light.png +0 -0
- package/registry/examples/chat/native/conversation.tsx +20 -0
- package/registry/examples/chat/native/inbox.d.ts +4 -0
- package/registry/examples/chat/native/inbox.dark.png +0 -0
- package/registry/examples/chat/native/inbox.js +16 -0
- package/registry/examples/chat/native/inbox.light.png +0 -0
- package/registry/examples/chat/native/inbox.tsx +13 -0
- package/registry/examples/match-dialog/its-a-match.d.ts +4 -0
- package/registry/examples/match-dialog/its-a-match.js +29 -0
- package/registry/examples/match-dialog/its-a-match.tsx +25 -0
- package/registry/examples/match-dialog/native/its-a-match.d.ts +4 -0
- package/registry/examples/match-dialog/native/its-a-match.dark.png +0 -0
- package/registry/examples/match-dialog/native/its-a-match.js +28 -0
- package/registry/examples/match-dialog/native/its-a-match.light.png +0 -0
- package/registry/examples/match-dialog/native/its-a-match.tsx +22 -0
- package/registry/examples/photo-grid/native/profile-photos.d.ts +4 -0
- package/registry/examples/photo-grid/native/profile-photos.dark.png +0 -0
- package/registry/examples/photo-grid/native/profile-photos.js +27 -0
- package/registry/examples/photo-grid/native/profile-photos.light.png +0 -0
- package/registry/examples/photo-grid/native/profile-photos.tsx +24 -0
- package/registry/examples/photo-grid/profile-photos.d.ts +4 -0
- package/registry/examples/photo-grid/profile-photos.js +28 -0
- package/registry/examples/photo-grid/profile-photos.tsx +25 -0
- package/registry/examples/screen/native/app-shell.d.ts +4 -0
- package/registry/examples/screen/native/app-shell.dark.png +0 -0
- package/registry/examples/screen/native/app-shell.js +25 -0
- package/registry/examples/screen/native/app-shell.light.png +0 -0
- package/registry/examples/screen/native/app-shell.tsx +33 -0
- package/registry/examples/screen/native/auth.d.ts +4 -0
- package/registry/examples/screen/native/auth.dark.png +0 -0
- package/registry/examples/screen/native/auth.js +24 -0
- package/registry/examples/screen/native/auth.light.png +0 -0
- package/registry/examples/screen/native/auth.tsx +20 -0
- package/registry/examples/screen/native/chat.d.ts +4 -0
- package/registry/examples/screen/native/chat.dark.png +0 -0
- package/registry/examples/screen/native/chat.js +27 -0
- package/registry/examples/screen/native/chat.light.png +0 -0
- package/registry/examples/screen/native/chat.tsx +22 -0
- package/registry/examples/screen/native/feed.d.ts +4 -0
- package/registry/examples/screen/native/feed.dark.png +0 -0
- package/registry/examples/screen/native/feed.js +24 -0
- package/registry/examples/screen/native/feed.light.png +0 -0
- package/registry/examples/screen/native/feed.tsx +34 -0
- package/registry/examples/screen/native/profile.d.ts +4 -0
- package/registry/examples/screen/native/profile.dark.png +0 -0
- package/registry/examples/screen/native/profile.js +29 -0
- package/registry/examples/screen/native/profile.light.png +0 -0
- package/registry/examples/screen/native/profile.tsx +27 -0
- package/registry/examples/screen/native/settings.d.ts +4 -0
- package/registry/examples/screen/native/settings.dark.png +0 -0
- package/registry/examples/screen/native/settings.js +20 -0
- package/registry/examples/screen/native/settings.light.png +0 -0
- package/registry/examples/screen/native/settings.tsx +19 -0
- package/registry/examples/step-progress/native/sign-up.d.ts +4 -0
- package/registry/examples/step-progress/native/sign-up.dark.png +0 -0
- package/registry/examples/step-progress/native/sign-up.js +12 -0
- package/registry/examples/step-progress/native/sign-up.light.png +0 -0
- package/registry/examples/step-progress/native/sign-up.tsx +11 -0
- package/registry/examples/step-progress/sign-up.d.ts +4 -0
- package/registry/examples/step-progress/sign-up.js +22 -0
- package/registry/examples/step-progress/sign-up.tsx +16 -0
- package/registry/examples/swipe-deck/discover.d.ts +4 -0
- package/registry/examples/swipe-deck/discover.js +41 -0
- package/registry/examples/swipe-deck/discover.tsx +37 -0
- package/registry/examples/swipe-deck/native/discover.d.ts +4 -0
- package/registry/examples/swipe-deck/native/discover.dark.png +0 -0
- package/registry/examples/swipe-deck/native/discover.js +39 -0
- package/registry/examples/swipe-deck/native/discover.light.png +0 -0
- package/registry/examples/swipe-deck/native/discover.tsx +35 -0
- package/registry/examples/tag-selector/interests.d.ts +4 -0
- package/registry/examples/tag-selector/interests.js +14 -0
- package/registry/examples/tag-selector/interests.tsx +12 -0
- package/registry/examples/tag-selector/native/interests.d.ts +4 -0
- package/registry/examples/tag-selector/native/interests.dark.png +0 -0
- package/registry/examples/tag-selector/native/interests.js +15 -0
- package/registry/examples/tag-selector/native/interests.light.png +0 -0
- package/registry/examples/tag-selector/native/interests.tsx +15 -0
- package/registry/examples/verified-badge/native/verified.d.ts +4 -0
- package/registry/examples/verified-badge/native/verified.dark.png +0 -0
- package/registry/examples/verified-badge/native/verified.js +18 -0
- package/registry/examples/verified-badge/native/verified.light.png +0 -0
- package/registry/examples/verified-badge/native/verified.tsx +15 -0
- package/registry/examples/verified-badge/verified.d.ts +4 -0
- package/registry/examples/verified-badge/verified.js +17 -0
- package/registry/examples/verified-badge/verified.tsx +13 -0
- package/registry/field.json +1 -1
- package/registry/file-upload.json +1 -1
- package/registry/filter-bar.json +1 -1
- package/registry/form.json +1 -1
- package/registry/gallery-page.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 +144 -2
- 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 +1 -1
- package/registry/list.json +1 -1
- package/registry/loader.json +1 -1
- package/registry/longform-page.json +1 -1
- package/registry/map.json +2 -2
- 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 +300 -0
- package/registry/media-capture.json +1 -1
- package/registry/meter.json +1 -1
- package/registry/nav-bar.json +1 -1
- package/registry/number-ticker.json +1 -1
- package/registry/page-header.json +1 -1
- package/registry/page-hero.json +1 -1
- package/registry/page.json +1 -1
- package/registry/pagination.json +1 -1
- package/registry/photo-grid.json +302 -0
- 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/screen.json +184 -0
- 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/settings-layout.json +1 -1
- package/registry/skeleton.json +1 -1
- package/registry/slider.json +1 -1
- 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 +255 -0
- package/registry/stepper.json +1 -1
- package/registry/swipe-deck.json +512 -0
- package/registry/switch.json +1 -1
- package/registry/tabs.json +1 -1
- package/registry/tag-input.json +1 -1
- package/registry/tag-selector.json +276 -0
- 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/verified-badge.json +202 -0
- package/registry/video-player.json +1 -1
- package/registry/video-trimmer.json +1 -1
- package/registry/virtual.json +1 -1
package/dist/page-hero.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Page
|
|
3
|
+
} from "./chunk-DGQMDJXK.js";
|
|
1
4
|
import {
|
|
2
5
|
BlockLink
|
|
3
6
|
} from "./chunk-2AGSO3WH.js";
|
|
4
7
|
import {
|
|
5
8
|
ArrowGlyph
|
|
6
9
|
} from "./chunk-XCBVFMNV.js";
|
|
7
|
-
import {
|
|
8
|
-
Page
|
|
9
|
-
} from "./chunk-DGQMDJXK.js";
|
|
10
10
|
import {
|
|
11
11
|
SectionBackdrop
|
|
12
12
|
} from "./chunk-XVLWVTQA.js";
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as tailwind_variants from 'tailwind-variants';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
declare const photoGrid: tailwind_variants.TVReturnType<{
|
|
5
|
+
[key: string]: {
|
|
6
|
+
[key: string]: tailwind_variants.ClassValue | {
|
|
7
|
+
root?: tailwind_variants.ClassValue;
|
|
8
|
+
slot?: tailwind_variants.ClassValue;
|
|
9
|
+
photo?: tailwind_variants.ClassValue;
|
|
10
|
+
image?: tailwind_variants.ClassValue;
|
|
11
|
+
main?: tailwind_variants.ClassValue;
|
|
12
|
+
tools?: tailwind_variants.ClassValue;
|
|
13
|
+
tool?: tailwind_variants.ClassValue;
|
|
14
|
+
plus?: tailwind_variants.ClassValue;
|
|
15
|
+
empty?: tailwind_variants.ClassValue;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
} | {
|
|
19
|
+
[x: string]: {
|
|
20
|
+
[x: string]: tailwind_variants.ClassValue | {
|
|
21
|
+
root?: tailwind_variants.ClassValue;
|
|
22
|
+
slot?: tailwind_variants.ClassValue;
|
|
23
|
+
photo?: tailwind_variants.ClassValue;
|
|
24
|
+
image?: tailwind_variants.ClassValue;
|
|
25
|
+
main?: tailwind_variants.ClassValue;
|
|
26
|
+
tools?: tailwind_variants.ClassValue;
|
|
27
|
+
tool?: tailwind_variants.ClassValue;
|
|
28
|
+
plus?: tailwind_variants.ClassValue;
|
|
29
|
+
empty?: tailwind_variants.ClassValue;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
} | {}, {
|
|
33
|
+
root: string;
|
|
34
|
+
slot: string;
|
|
35
|
+
photo: string;
|
|
36
|
+
image: string;
|
|
37
|
+
main: string;
|
|
38
|
+
tools: string;
|
|
39
|
+
tool: string;
|
|
40
|
+
empty: string;
|
|
41
|
+
plus: string;
|
|
42
|
+
}, undefined, {
|
|
43
|
+
[key: string]: {
|
|
44
|
+
[key: string]: tailwind_variants.ClassValue | {
|
|
45
|
+
root?: tailwind_variants.ClassValue;
|
|
46
|
+
slot?: tailwind_variants.ClassValue;
|
|
47
|
+
photo?: tailwind_variants.ClassValue;
|
|
48
|
+
image?: tailwind_variants.ClassValue;
|
|
49
|
+
main?: tailwind_variants.ClassValue;
|
|
50
|
+
tools?: tailwind_variants.ClassValue;
|
|
51
|
+
tool?: tailwind_variants.ClassValue;
|
|
52
|
+
plus?: tailwind_variants.ClassValue;
|
|
53
|
+
empty?: tailwind_variants.ClassValue;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
} | {}, {
|
|
57
|
+
root: string;
|
|
58
|
+
slot: string;
|
|
59
|
+
photo: string;
|
|
60
|
+
image: string;
|
|
61
|
+
main: string;
|
|
62
|
+
tools: string;
|
|
63
|
+
tool: string;
|
|
64
|
+
empty: string;
|
|
65
|
+
plus: string;
|
|
66
|
+
}, tailwind_variants.TVReturnTypeLike<unknown, {
|
|
67
|
+
root: string;
|
|
68
|
+
slot: string;
|
|
69
|
+
photo: string;
|
|
70
|
+
image: string;
|
|
71
|
+
main: string;
|
|
72
|
+
tools: string;
|
|
73
|
+
tool: string;
|
|
74
|
+
empty: string;
|
|
75
|
+
plus: string;
|
|
76
|
+
}>>;
|
|
77
|
+
type GridPhoto = {
|
|
78
|
+
/** A stable id. */
|
|
79
|
+
id: string;
|
|
80
|
+
/** Its URL. */
|
|
81
|
+
src: string;
|
|
82
|
+
/** What it shows — its text alternative. */
|
|
83
|
+
alt?: string;
|
|
84
|
+
};
|
|
85
|
+
type PhotoGridLabels = {
|
|
86
|
+
/** An empty slot (“Add a photo”). */
|
|
87
|
+
add: string;
|
|
88
|
+
/** The first photo's badge (“Main”). */
|
|
89
|
+
main: string;
|
|
90
|
+
/** A photo's name: `(n) => "Photo " + n`. */
|
|
91
|
+
photo: (position: number) => string;
|
|
92
|
+
/** Move earlier. */
|
|
93
|
+
moveEarlier: string;
|
|
94
|
+
/** Move later. */
|
|
95
|
+
moveLater: string;
|
|
96
|
+
/** Remove. */
|
|
97
|
+
remove: string;
|
|
98
|
+
/** Announced after a move: `(n) => "Moved to place " + n`. */
|
|
99
|
+
moved: (position: number) => string;
|
|
100
|
+
};
|
|
101
|
+
type PhotoGridProps = {
|
|
102
|
+
/** The photos, in order: the first is the main one. */
|
|
103
|
+
value: readonly GridPhoto[];
|
|
104
|
+
/** Called with the new order, or without a removed photo. */
|
|
105
|
+
onValueChange: (photos: GridPhoto[]) => void;
|
|
106
|
+
/** Called by an empty slot: open the file picker, the camera, the editor. */
|
|
107
|
+
onAdd?: () => void;
|
|
108
|
+
/** How many slots. Default 6. */
|
|
109
|
+
slots?: number;
|
|
110
|
+
/** Fewest photos required: the empty slots up to it are marked. Default 1. */
|
|
111
|
+
required?: number;
|
|
112
|
+
/** Columns. Default 3. */
|
|
113
|
+
columns?: number;
|
|
114
|
+
/** The words, passed translated. */
|
|
115
|
+
labels: PhotoGridLabels;
|
|
116
|
+
/** Classes merged on the root. */
|
|
117
|
+
className?: string;
|
|
118
|
+
};
|
|
119
|
+
/** A profile's photos in slots: drag to reorder, or each photo's buttons (earlier, later, remove), every move announced. */
|
|
120
|
+
declare function PhotoGrid({ value, onValueChange, onAdd, slots, required, columns, labels, className }: PhotoGridProps): React.JSX.Element;
|
|
121
|
+
|
|
122
|
+
export { type GridPhoto, PhotoGrid, type PhotoGridLabels, type PhotoGridProps, photoGrid };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
PlusGlyph
|
|
4
|
+
} from "./chunk-CPA5CF35.js";
|
|
5
|
+
|
|
6
|
+
// src/photo-grid/photo-grid.tsx
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { tv } from "tailwind-variants";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
var photoGrid = tv({
|
|
11
|
+
slots: {
|
|
12
|
+
root: "grid gap-2",
|
|
13
|
+
slot: "relative aspect-[3/4] overflow-hidden rounded-xl",
|
|
14
|
+
photo: "group h-full w-full cursor-grab bg-surface-3 outline-hidden focus-visible:ring-2 focus-visible:ring-ring data-[over]:ring-2 data-[over]:ring-accent active:cursor-grabbing",
|
|
15
|
+
image: "h-full w-full object-cover",
|
|
16
|
+
main: "absolute bottom-2 left-2 rounded-full bg-accent px-2 py-0.5 text-[10px] font-extrabold uppercase tracking-wide text-on-accent",
|
|
17
|
+
tools: "absolute right-1.5 top-1.5 flex gap-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 focus-within:opacity-100 motion-reduce:transition-none",
|
|
18
|
+
tool: "inline-flex h-7 w-7 items-center justify-center rounded-full bg-scrim text-fg-on-media backdrop-blur-sm hover:bg-scrim-strong focus-visible:opacity-100 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
|
|
19
|
+
empty: "flex h-full w-full flex-col items-center justify-center gap-2 rounded-xl border-[1.5px] border-dashed border-border-strong bg-surface-1 text-xs font-semibold text-fg-secondary hover:bg-surface-2 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 data-[required]:border-accent",
|
|
20
|
+
plus: "flex h-8 w-8 items-center justify-center rounded-full bg-surface-3 data-[next]:bg-accent data-[next]:text-on-accent [&>svg]:h-4 [&>svg]:w-4"
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
function PhotoGrid({ value, onValueChange, onAdd, slots = 6, required = 1, columns = 3, labels, className }) {
|
|
24
|
+
const s = photoGrid();
|
|
25
|
+
const [dragged, setDragged] = React.useState(null);
|
|
26
|
+
const [over, setOver] = React.useState(null);
|
|
27
|
+
const [message, setMessage] = React.useState("");
|
|
28
|
+
const move = (from, to) => {
|
|
29
|
+
if (to < 0 || to >= value.length || from === to) return;
|
|
30
|
+
const next = [...value];
|
|
31
|
+
const [photo] = next.splice(from, 1);
|
|
32
|
+
next.splice(to, 0, photo);
|
|
33
|
+
onValueChange(next);
|
|
34
|
+
setMessage(labels.moved(to + 1));
|
|
35
|
+
};
|
|
36
|
+
return /* @__PURE__ */ jsxs("div", { className: s.root({ className }), style: { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` }, children: [
|
|
37
|
+
/* @__PURE__ */ jsx("p", { className: "sr-only", "aria-live": "polite", children: message }),
|
|
38
|
+
Array.from({ length: slots }, (_, i) => {
|
|
39
|
+
const photo = value[i];
|
|
40
|
+
if (!photo) {
|
|
41
|
+
return /* @__PURE__ */ jsx("div", { className: s.slot(), children: /* @__PURE__ */ jsxs("button", { type: "button", disabled: !onAdd || i > value.length, onClick: onAdd, "data-required": i < required || void 0, className: s.empty(), children: [
|
|
42
|
+
/* @__PURE__ */ jsx("span", { className: s.plus(), "data-next": i === value.length || void 0, children: /* @__PURE__ */ jsx(PlusGlyph, {}) }),
|
|
43
|
+
i === value.length ? labels.add : /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.add })
|
|
44
|
+
] }) }, `empty-${i}`);
|
|
45
|
+
}
|
|
46
|
+
const name = [labels.photo(i + 1), photo.alt, i === 0 ? labels.main : null].filter(Boolean).join(", ");
|
|
47
|
+
return /* @__PURE__ */ jsx("div", { className: s.slot(), children: /* @__PURE__ */ jsxs(
|
|
48
|
+
"div",
|
|
49
|
+
{
|
|
50
|
+
role: "group",
|
|
51
|
+
"aria-label": name,
|
|
52
|
+
draggable: true,
|
|
53
|
+
tabIndex: 0,
|
|
54
|
+
"data-over": over === i || void 0,
|
|
55
|
+
className: s.photo(),
|
|
56
|
+
onDragStart: (e) => {
|
|
57
|
+
e.dataTransfer.effectAllowed = "move";
|
|
58
|
+
setDragged(i);
|
|
59
|
+
},
|
|
60
|
+
onDragOver: (e) => {
|
|
61
|
+
if (dragged === null) return;
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
setOver(i);
|
|
64
|
+
},
|
|
65
|
+
onDragLeave: () => setOver(null),
|
|
66
|
+
onDrop: (e) => {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
if (dragged !== null) move(dragged, i);
|
|
69
|
+
setDragged(null);
|
|
70
|
+
setOver(null);
|
|
71
|
+
},
|
|
72
|
+
onDragEnd: () => {
|
|
73
|
+
setDragged(null);
|
|
74
|
+
setOver(null);
|
|
75
|
+
},
|
|
76
|
+
children: [
|
|
77
|
+
/* @__PURE__ */ jsx("img", { src: photo.src, alt: photo.alt ?? "", className: s.image(), draggable: false }),
|
|
78
|
+
i === 0 ? /* @__PURE__ */ jsx("span", { className: s.main(), "aria-hidden": true, children: labels.main }) : null,
|
|
79
|
+
/* @__PURE__ */ jsxs("div", { className: s.tools(), children: [
|
|
80
|
+
i > 0 ? /* @__PURE__ */ jsx("button", { type: "button", "aria-label": `${labels.moveEarlier} \u2014 ${labels.photo(i + 1)}`, className: s.tool(), onClick: () => move(i, i - 1), children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M15 5l-7 7 7 7" }) }) }) : null,
|
|
81
|
+
i < value.length - 1 ? /* @__PURE__ */ jsx("button", { type: "button", "aria-label": `${labels.moveLater} \u2014 ${labels.photo(i + 1)}`, className: s.tool(), onClick: () => move(i, i + 1), children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M9 5l7 7-7 7" }) }) }) : null,
|
|
82
|
+
/* @__PURE__ */ jsx("button", { type: "button", "aria-label": `${labels.remove} \u2014 ${labels.photo(i + 1)}`, className: s.tool(), onClick: () => onValueChange(value.filter((_2, j) => j !== i)), children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) })
|
|
83
|
+
] })
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
) }, photo.id);
|
|
87
|
+
})
|
|
88
|
+
] });
|
|
89
|
+
}
|
|
90
|
+
export {
|
|
91
|
+
PhotoGrid,
|
|
92
|
+
photoGrid
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=photo-grid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/photo-grid/photo-grid.tsx"],"sourcesContent":["// Client. A profile's photos in a grid of slots: the first is the main one; drag a photo onto another to reorder, or use\n// its menu (make main, earlier, later, remove) — the keyboard way, every move announced.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { PlusGlyph } from \"../glyphs\";\n\nexport const photoGrid = tv({\n slots: {\n root: \"grid gap-2\",\n slot: \"relative aspect-[3/4] overflow-hidden rounded-xl\",\n photo: \"group h-full w-full cursor-grab bg-surface-3 outline-hidden focus-visible:ring-2 focus-visible:ring-ring data-[over]:ring-2 data-[over]:ring-accent active:cursor-grabbing\",\n image: \"h-full w-full object-cover\",\n main: \"absolute bottom-2 left-2 rounded-full bg-accent px-2 py-0.5 text-[10px] font-extrabold uppercase tracking-wide text-on-accent\",\n tools: \"absolute right-1.5 top-1.5 flex gap-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 focus-within:opacity-100 motion-reduce:transition-none\",\n tool: \"inline-flex h-7 w-7 items-center justify-center rounded-full bg-scrim text-fg-on-media backdrop-blur-sm hover:bg-scrim-strong focus-visible:opacity-100 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n empty:\n \"flex h-full w-full flex-col items-center justify-center gap-2 rounded-xl border-[1.5px] border-dashed border-border-strong bg-surface-1 text-xs font-semibold text-fg-secondary \" +\n \"hover:bg-surface-2 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 data-[required]:border-accent\",\n plus: \"flex h-8 w-8 items-center justify-center rounded-full bg-surface-3 data-[next]:bg-accent data-[next]:text-on-accent [&>svg]:h-4 [&>svg]:w-4\",\n },\n});\n\nexport type GridPhoto = {\n /** A stable id. */\n id: string;\n /** Its URL. */\n src: string;\n /** What it shows — its text alternative. */\n alt?: string;\n};\n\nexport type PhotoGridLabels = {\n /** An empty slot (“Add a photo”). */\n add: string;\n /** The first photo's badge (“Main”). */\n main: string;\n /** A photo's name: `(n) => \"Photo \" + n`. */\n photo: (position: number) => string;\n /** Move earlier. */\n moveEarlier: string;\n /** Move later. */\n moveLater: string;\n /** Remove. */\n remove: string;\n /** Announced after a move: `(n) => \"Moved to place \" + n`. */\n moved: (position: number) => string;\n};\n\nexport type PhotoGridProps = {\n /** The photos, in order: the first is the main one. */\n value: readonly GridPhoto[];\n /** Called with the new order, or without a removed photo. */\n onValueChange: (photos: GridPhoto[]) => void;\n /** Called by an empty slot: open the file picker, the camera, the editor. */\n onAdd?: () => void;\n /** How many slots. Default 6. */\n slots?: number;\n /** Fewest photos required: the empty slots up to it are marked. Default 1. */\n required?: number;\n /** Columns. Default 3. */\n columns?: number;\n /** The words, passed translated. */\n labels: PhotoGridLabels;\n /** Classes merged on the root. */\n className?: string;\n};\n\n/** A profile's photos in slots: drag to reorder, or each photo's buttons (earlier, later, remove), every move announced. */\nexport function PhotoGrid({ value, onValueChange, onAdd, slots = 6, required = 1, columns = 3, labels, className }: PhotoGridProps) {\n const s = photoGrid();\n const [dragged, setDragged] = React.useState<number | null>(null);\n const [over, setOver] = React.useState<number | null>(null);\n const [message, setMessage] = React.useState(\"\");\n const move = (from: number, to: number) => {\n if (to < 0 || to >= value.length || from === to) return;\n const next = [...value];\n const [photo] = next.splice(from, 1);\n next.splice(to, 0, photo!);\n onValueChange(next);\n setMessage(labels.moved(to + 1));\n };\n return (\n <div className={s.root({ className })} style={{ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` }}>\n <p className=\"sr-only\" aria-live=\"polite\">\n {message}\n </p>\n {Array.from({ length: slots }, (_, i) => {\n const photo = value[i];\n if (!photo) {\n return (\n <div key={`empty-${i}`} className={s.slot()}>\n <button type=\"button\" disabled={!onAdd || i > value.length} onClick={onAdd} data-required={i < required || undefined} className={s.empty()}>\n <span className={s.plus()} data-next={i === value.length || undefined}>\n <PlusGlyph />\n </span>\n {i === value.length ? labels.add : <span className=\"sr-only\">{labels.add}</span>}\n </button>\n </div>\n );\n }\n const name = [labels.photo(i + 1), photo.alt, i === 0 ? labels.main : null].filter(Boolean).join(\", \");\n return (\n <div key={photo.id} className={s.slot()}>\n <div\n role=\"group\"\n aria-label={name}\n draggable\n tabIndex={0}\n data-over={over === i || undefined}\n className={s.photo()}\n onDragStart={(e) => {\n e.dataTransfer.effectAllowed = \"move\";\n setDragged(i);\n }}\n onDragOver={(e) => {\n if (dragged === null) return;\n e.preventDefault();\n setOver(i);\n }}\n onDragLeave={() => setOver(null)}\n onDrop={(e) => {\n e.preventDefault();\n if (dragged !== null) move(dragged, i);\n setDragged(null);\n setOver(null);\n }}\n onDragEnd={() => {\n setDragged(null);\n setOver(null);\n }}\n >\n <img src={photo.src} alt={photo.alt ?? \"\"} className={s.image()} draggable={false} />\n {i === 0 ? <span className={s.main()} aria-hidden>{labels.main}</span> : null}\n <div className={s.tools()}>\n {i > 0 ? (\n <button type=\"button\" aria-label={`${labels.moveEarlier} — ${labels.photo(i + 1)}`} className={s.tool()} onClick={() => move(i, i - 1)}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.4\" strokeLinecap=\"round\" aria-hidden><path d=\"M15 5l-7 7 7 7\" /></svg>\n </button>\n ) : null}\n {i < value.length - 1 ? (\n <button type=\"button\" aria-label={`${labels.moveLater} — ${labels.photo(i + 1)}`} className={s.tool()} onClick={() => move(i, i + 1)}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.4\" strokeLinecap=\"round\" aria-hidden><path d=\"M9 5l7 7-7 7\" /></svg>\n </button>\n ) : null}\n <button type=\"button\" aria-label={`${labels.remove} — ${labels.photo(i + 1)}`} className={s.tool()} onClick={() => onValueChange(value.filter((_, j) => j !== i))}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.4\" strokeLinecap=\"round\" aria-hidden><path d=\"M18 6 6 18M6 6l12 12\" /></svg>\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;;AAEA,YAAY,WAAW;AACvB,SAAS,UAAU;AAiFb,cAQQ,YARR;AA7EC,IAAM,YAAY,GAAG;AAAA,EAC1B,OAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OACE;AAAA,IAEF,MAAM;AAAA,EACR;AACF,CAAC;AAgDM,SAAS,UAAU,EAAE,OAAO,eAAe,OAAO,QAAQ,GAAG,WAAW,GAAG,UAAU,GAAG,QAAQ,UAAU,GAAmB;AAClI,QAAM,IAAI,UAAU;AACpB,QAAM,CAAC,SAAS,UAAU,IAAU,eAAwB,IAAI;AAChE,QAAM,CAAC,MAAM,OAAO,IAAU,eAAwB,IAAI;AAC1D,QAAM,CAAC,SAAS,UAAU,IAAU,eAAS,EAAE;AAC/C,QAAM,OAAO,CAAC,MAAc,OAAe;AACzC,QAAI,KAAK,KAAK,MAAM,MAAM,UAAU,SAAS,GAAI;AACjD,UAAM,OAAO,CAAC,GAAG,KAAK;AACtB,UAAM,CAAC,KAAK,IAAI,KAAK,OAAO,MAAM,CAAC;AACnC,SAAK,OAAO,IAAI,GAAG,KAAM;AACzB,kBAAc,IAAI;AAClB,eAAW,OAAO,MAAM,KAAK,CAAC,CAAC;AAAA,EACjC;AACA,SACE,qBAAC,SAAI,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,OAAO,EAAE,qBAAqB,UAAU,OAAO,oBAAoB,GACxG;AAAA,wBAAC,OAAE,WAAU,WAAU,aAAU,UAC9B,mBACH;AAAA,IACC,MAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,CAAC,GAAG,MAAM;AACvC,YAAM,QAAQ,MAAM,CAAC;AACrB,UAAI,CAAC,OAAO;AACV,eACE,oBAAC,SAAuB,WAAW,EAAE,KAAK,GACxC,+BAAC,YAAO,MAAK,UAAS,UAAU,CAAC,SAAS,IAAI,MAAM,QAAQ,SAAS,OAAO,iBAAe,IAAI,YAAY,QAAW,WAAW,EAAE,MAAM,GACvI;AAAA,8BAAC,UAAK,WAAW,EAAE,KAAK,GAAG,aAAW,MAAM,MAAM,UAAU,QAC1D,8BAAC,aAAU,GACb;AAAA,UACC,MAAM,MAAM,SAAS,OAAO,MAAM,oBAAC,UAAK,WAAU,WAAW,iBAAO,KAAI;AAAA,WAC3E,KANQ,SAAS,CAAC,EAOpB;AAAA,MAEJ;AACA,YAAM,OAAO,CAAC,OAAO,MAAM,IAAI,CAAC,GAAG,MAAM,KAAK,MAAM,IAAI,OAAO,OAAO,IAAI,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AACrG,aACE,oBAAC,SAAmB,WAAW,EAAE,KAAK,GACpC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,cAAY;AAAA,UACZ,WAAS;AAAA,UACT,UAAU;AAAA,UACV,aAAW,SAAS,KAAK;AAAA,UACzB,WAAW,EAAE,MAAM;AAAA,UACnB,aAAa,CAAC,MAAM;AAClB,cAAE,aAAa,gBAAgB;AAC/B,uBAAW,CAAC;AAAA,UACd;AAAA,UACA,YAAY,CAAC,MAAM;AACjB,gBAAI,YAAY,KAAM;AACtB,cAAE,eAAe;AACjB,oBAAQ,CAAC;AAAA,UACX;AAAA,UACA,aAAa,MAAM,QAAQ,IAAI;AAAA,UAC/B,QAAQ,CAAC,MAAM;AACb,cAAE,eAAe;AACjB,gBAAI,YAAY,KAAM,MAAK,SAAS,CAAC;AACrC,uBAAW,IAAI;AACf,oBAAQ,IAAI;AAAA,UACd;AAAA,UACA,WAAW,MAAM;AACf,uBAAW,IAAI;AACf,oBAAQ,IAAI;AAAA,UACd;AAAA,UAEA;AAAA,gCAAC,SAAI,KAAK,MAAM,KAAK,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,MAAM,GAAG,WAAW,OAAO;AAAA,YAClF,MAAM,IAAI,oBAAC,UAAK,WAAW,EAAE,KAAK,GAAG,eAAW,MAAE,iBAAO,MAAK,IAAU;AAAA,YACzE,qBAAC,SAAI,WAAW,EAAE,MAAM,GACrB;AAAA,kBAAI,IACH,oBAAC,YAAO,MAAK,UAAS,cAAY,GAAG,OAAO,WAAW,WAAM,OAAO,MAAM,IAAI,CAAC,CAAC,IAAI,WAAW,EAAE,KAAK,GAAG,SAAS,MAAM,KAAK,GAAG,IAAI,CAAC,GACnI,8BAAC,SAAI,SAAQ,aAAY,WAAU,WAAU,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,eAAc,SAAQ,eAAW,MAAC,8BAAC,UAAK,GAAE,kBAAiB,GAAE,GAChK,IACE;AAAA,cACH,IAAI,MAAM,SAAS,IAClB,oBAAC,YAAO,MAAK,UAAS,cAAY,GAAG,OAAO,SAAS,WAAM,OAAO,MAAM,IAAI,CAAC,CAAC,IAAI,WAAW,EAAE,KAAK,GAAG,SAAS,MAAM,KAAK,GAAG,IAAI,CAAC,GACjI,8BAAC,SAAI,SAAQ,aAAY,WAAU,WAAU,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,eAAc,SAAQ,eAAW,MAAC,8BAAC,UAAK,GAAE,gBAAe,GAAE,GAC9J,IACE;AAAA,cACJ,oBAAC,YAAO,MAAK,UAAS,cAAY,GAAG,OAAO,MAAM,WAAM,OAAO,MAAM,IAAI,CAAC,CAAC,IAAI,WAAW,EAAE,KAAK,GAAG,SAAS,MAAM,cAAc,MAAM,OAAO,CAACA,IAAG,MAAM,MAAM,CAAC,CAAC,GAC9J,8BAAC,SAAI,SAAQ,aAAY,WAAU,WAAU,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,eAAc,SAAQ,eAAW,MAAC,8BAAC,UAAK,GAAE,wBAAuB,GAAE,GACtK;AAAA,eACF;AAAA;AAAA;AAAA,MACF,KA9CQ,MAAM,EA+ChB;AAAA,IAEJ,CAAC;AAAA,KACH;AAEJ;","names":["_"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
type StepProgressProps = {
|
|
4
|
+
/** The current step, from 1. */
|
|
5
|
+
step: number;
|
|
6
|
+
/** How many steps. */
|
|
7
|
+
total: number;
|
|
8
|
+
/** The position in words: `(n, total) => "Step " + n + " of " + total` — passed translated. */
|
|
9
|
+
label: (step: number, total: number) => string;
|
|
10
|
+
/** The back link or button (a `Button`), at the left. */
|
|
11
|
+
back?: React.ReactNode;
|
|
12
|
+
/** At the right: a Skip link. */
|
|
13
|
+
trailing?: React.ReactNode;
|
|
14
|
+
/** Classes merged on the root. */
|
|
15
|
+
className?: string;
|
|
16
|
+
};
|
|
17
|
+
/** The head of a sign-up or a profile to fill: back, segments that fill, the position in words. */
|
|
18
|
+
declare function StepProgress({ step, total, label, back, trailing, className }: StepProgressProps): React.JSX.Element;
|
|
19
|
+
|
|
20
|
+
export { StepProgress, type StepProgressProps };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/step-progress/step-progress.tsx
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
function StepProgress({ step, total, label, back, trailing, className }) {
|
|
4
|
+
const words = label(step, total);
|
|
5
|
+
return /* @__PURE__ */ jsxs("div", { className: ["flex flex-col gap-1.5", className].filter(Boolean).join(" "), children: [
|
|
6
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
7
|
+
/* @__PURE__ */ jsx("div", { className: "flex min-w-10 justify-start", children: back }),
|
|
8
|
+
/* @__PURE__ */ jsx("div", { role: "progressbar", "aria-label": words, "aria-valuemin": 1, "aria-valuemax": total, "aria-valuenow": step, "aria-valuetext": words, className: "flex flex-1 gap-1", children: Array.from({ length: total }, (_, i) => /* @__PURE__ */ jsx("span", { className: ["h-1 flex-1 rounded-full transition-colors motion-reduce:transition-none", i < step ? "bg-accent" : "bg-surface-3"].join(" ") }, i)) }),
|
|
9
|
+
/* @__PURE__ */ jsx("div", { className: "flex min-w-10 justify-end", children: trailing })
|
|
10
|
+
] }),
|
|
11
|
+
/* @__PURE__ */ jsx("p", { className: "m-0 text-center text-xs text-fg-secondary", "aria-hidden": true, children: words })
|
|
12
|
+
] });
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
StepProgress
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=step-progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/step-progress/step-progress.tsx"],"sourcesContent":["// Server-safe. The head of a multi-step flow: back, segments that fill, the position in words — a `progressbar`.\nimport type * as React from \"react\";\n\nexport type StepProgressProps = {\n /** The current step, from 1. */\n step: number;\n /** How many steps. */\n total: number;\n /** The position in words: `(n, total) => \"Step \" + n + \" of \" + total` — passed translated. */\n label: (step: number, total: number) => string;\n /** The back link or button (a `Button`), at the left. */\n back?: React.ReactNode;\n /** At the right: a Skip link. */\n trailing?: React.ReactNode;\n /** Classes merged on the root. */\n className?: string;\n};\n\n/** The head of a sign-up or a profile to fill: back, segments that fill, the position in words. */\nexport function StepProgress({ step, total, label, back, trailing, className }: StepProgressProps) {\n const words = label(step, total);\n return (\n <div className={[\"flex flex-col gap-1.5\", className].filter(Boolean).join(\" \")}>\n <div className=\"flex items-center gap-3\">\n <div className=\"flex min-w-10 justify-start\">{back}</div>\n <div role=\"progressbar\" aria-label={words} aria-valuemin={1} aria-valuemax={total} aria-valuenow={step} aria-valuetext={words} className=\"flex flex-1 gap-1\">\n {Array.from({ length: total }, (_, i) => (\n <span key={i} className={[\"h-1 flex-1 rounded-full transition-colors motion-reduce:transition-none\", i < step ? \"bg-accent\" : \"bg-surface-3\"].join(\" \")} />\n ))}\n </div>\n <div className=\"flex min-w-10 justify-end\">{trailing}</div>\n </div>\n <p className=\"m-0 text-center text-xs text-fg-secondary\" aria-hidden>\n {words}\n </p>\n </div>\n );\n}\n"],"mappings":";AAuBM,SACE,KADF;AAJC,SAAS,aAAa,EAAE,MAAM,OAAO,OAAO,MAAM,UAAU,UAAU,GAAsB;AACjG,QAAM,QAAQ,MAAM,MAAM,KAAK;AAC/B,SACE,qBAAC,SAAI,WAAW,CAAC,yBAAyB,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GAC3E;AAAA,yBAAC,SAAI,WAAU,2BACb;AAAA,0BAAC,SAAI,WAAU,+BAA+B,gBAAK;AAAA,MACnD,oBAAC,SAAI,MAAK,eAAc,cAAY,OAAO,iBAAe,GAAG,iBAAe,OAAO,iBAAe,MAAM,kBAAgB,OAAO,WAAU,qBACtI,gBAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,CAAC,GAAG,MACjC,oBAAC,UAAa,WAAW,CAAC,2EAA2E,IAAI,OAAO,cAAc,cAAc,EAAE,KAAK,GAAG,KAA3I,CAA8I,CAC1J,GACH;AAAA,MACA,oBAAC,SAAI,WAAU,6BAA6B,oBAAS;AAAA,OACvD;AAAA,IACA,oBAAC,OAAE,WAAU,6CAA4C,eAAW,MACjE,iBACH;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import * as tailwind_variants from 'tailwind-variants';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
declare const swipeDeck: tailwind_variants.TVReturnType<{
|
|
5
|
+
[key: string]: {
|
|
6
|
+
[key: string]: tailwind_variants.ClassValue | {
|
|
7
|
+
root?: tailwind_variants.ClassValue;
|
|
8
|
+
stack?: tailwind_variants.ClassValue;
|
|
9
|
+
layer?: tailwind_variants.ClassValue;
|
|
10
|
+
stamp?: tailwind_variants.ClassValue;
|
|
11
|
+
buttons?: tailwind_variants.ClassValue;
|
|
12
|
+
card?: tailwind_variants.ClassValue;
|
|
13
|
+
photo?: tailwind_variants.ClassValue;
|
|
14
|
+
bars?: tailwind_variants.ClassValue;
|
|
15
|
+
bar?: tailwind_variants.ClassValue;
|
|
16
|
+
half?: tailwind_variants.ClassValue;
|
|
17
|
+
info?: tailwind_variants.ClassValue;
|
|
18
|
+
name?: tailwind_variants.ClassValue;
|
|
19
|
+
age?: tailwind_variants.ClassValue;
|
|
20
|
+
bio?: tailwind_variants.ClassValue;
|
|
21
|
+
footer?: tailwind_variants.ClassValue;
|
|
22
|
+
button?: tailwind_variants.ClassValue;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
} | {
|
|
26
|
+
[x: string]: {
|
|
27
|
+
[x: string]: tailwind_variants.ClassValue | {
|
|
28
|
+
root?: tailwind_variants.ClassValue;
|
|
29
|
+
stack?: tailwind_variants.ClassValue;
|
|
30
|
+
layer?: tailwind_variants.ClassValue;
|
|
31
|
+
stamp?: tailwind_variants.ClassValue;
|
|
32
|
+
buttons?: tailwind_variants.ClassValue;
|
|
33
|
+
card?: tailwind_variants.ClassValue;
|
|
34
|
+
photo?: tailwind_variants.ClassValue;
|
|
35
|
+
bars?: tailwind_variants.ClassValue;
|
|
36
|
+
bar?: tailwind_variants.ClassValue;
|
|
37
|
+
half?: tailwind_variants.ClassValue;
|
|
38
|
+
info?: tailwind_variants.ClassValue;
|
|
39
|
+
name?: tailwind_variants.ClassValue;
|
|
40
|
+
age?: tailwind_variants.ClassValue;
|
|
41
|
+
bio?: tailwind_variants.ClassValue;
|
|
42
|
+
footer?: tailwind_variants.ClassValue;
|
|
43
|
+
button?: tailwind_variants.ClassValue;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
} | {}, {
|
|
47
|
+
root: string;
|
|
48
|
+
stack: string;
|
|
49
|
+
layer: string;
|
|
50
|
+
stamp: string;
|
|
51
|
+
buttons: string;
|
|
52
|
+
button: string;
|
|
53
|
+
card: string;
|
|
54
|
+
photo: string;
|
|
55
|
+
bars: string;
|
|
56
|
+
bar: string;
|
|
57
|
+
half: string;
|
|
58
|
+
info: string;
|
|
59
|
+
name: string;
|
|
60
|
+
age: string;
|
|
61
|
+
bio: string;
|
|
62
|
+
footer: string;
|
|
63
|
+
}, undefined, {
|
|
64
|
+
[key: string]: {
|
|
65
|
+
[key: string]: tailwind_variants.ClassValue | {
|
|
66
|
+
root?: tailwind_variants.ClassValue;
|
|
67
|
+
stack?: tailwind_variants.ClassValue;
|
|
68
|
+
layer?: tailwind_variants.ClassValue;
|
|
69
|
+
stamp?: tailwind_variants.ClassValue;
|
|
70
|
+
buttons?: tailwind_variants.ClassValue;
|
|
71
|
+
card?: tailwind_variants.ClassValue;
|
|
72
|
+
photo?: tailwind_variants.ClassValue;
|
|
73
|
+
bars?: tailwind_variants.ClassValue;
|
|
74
|
+
bar?: tailwind_variants.ClassValue;
|
|
75
|
+
half?: tailwind_variants.ClassValue;
|
|
76
|
+
info?: tailwind_variants.ClassValue;
|
|
77
|
+
name?: tailwind_variants.ClassValue;
|
|
78
|
+
age?: tailwind_variants.ClassValue;
|
|
79
|
+
bio?: tailwind_variants.ClassValue;
|
|
80
|
+
footer?: tailwind_variants.ClassValue;
|
|
81
|
+
button?: tailwind_variants.ClassValue;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
} | {}, {
|
|
85
|
+
root: string;
|
|
86
|
+
stack: string;
|
|
87
|
+
layer: string;
|
|
88
|
+
stamp: string;
|
|
89
|
+
buttons: string;
|
|
90
|
+
button: string;
|
|
91
|
+
card: string;
|
|
92
|
+
photo: string;
|
|
93
|
+
bars: string;
|
|
94
|
+
bar: string;
|
|
95
|
+
half: string;
|
|
96
|
+
info: string;
|
|
97
|
+
name: string;
|
|
98
|
+
age: string;
|
|
99
|
+
bio: string;
|
|
100
|
+
footer: string;
|
|
101
|
+
}, tailwind_variants.TVReturnTypeLike<unknown, {
|
|
102
|
+
root: string;
|
|
103
|
+
stack: string;
|
|
104
|
+
layer: string;
|
|
105
|
+
stamp: string;
|
|
106
|
+
buttons: string;
|
|
107
|
+
button: string;
|
|
108
|
+
card: string;
|
|
109
|
+
photo: string;
|
|
110
|
+
bars: string;
|
|
111
|
+
bar: string;
|
|
112
|
+
half: string;
|
|
113
|
+
info: string;
|
|
114
|
+
name: string;
|
|
115
|
+
age: string;
|
|
116
|
+
bio: string;
|
|
117
|
+
footer: string;
|
|
118
|
+
}>>;
|
|
119
|
+
type PersonCardProps = {
|
|
120
|
+
/** The person's photos (URLs), the first shown first; click a side, or its buttons, to move between them. */
|
|
121
|
+
photos: readonly {
|
|
122
|
+
src: string;
|
|
123
|
+
alt?: string;
|
|
124
|
+
}[];
|
|
125
|
+
/** The name. */
|
|
126
|
+
name: string;
|
|
127
|
+
/** The age, written after the name. */
|
|
128
|
+
age?: number;
|
|
129
|
+
/** Beside the name: a `VerifiedBadge`. */
|
|
130
|
+
badge?: React.ReactNode;
|
|
131
|
+
/** A line under the name: a distance, a job. */
|
|
132
|
+
meta?: React.ReactNode;
|
|
133
|
+
/** A sentence about the person. */
|
|
134
|
+
bio?: string;
|
|
135
|
+
/** At the bottom: the interests as chips. */
|
|
136
|
+
footer?: React.ReactNode;
|
|
137
|
+
/** The photo buttons' words: `{ previous: "Previous photo", next: "Next photo" }` — passed translated. */
|
|
138
|
+
photoLabels?: {
|
|
139
|
+
previous: string;
|
|
140
|
+
next: string;
|
|
141
|
+
};
|
|
142
|
+
/** Classes merged on the card. */
|
|
143
|
+
className?: string;
|
|
144
|
+
};
|
|
145
|
+
/** A person on a card: photos full bleed (a side to change), name and age over a scrim, a line, a bio, chips. */
|
|
146
|
+
declare function PersonCard({ photos, name, age, badge, meta, bio, footer, photoLabels, className }: PersonCardProps): React.JSX.Element;
|
|
147
|
+
type SwipeDirection = "like" | "pass";
|
|
148
|
+
type SwipeDeckLabels = {
|
|
149
|
+
/** The like button (“Like”). */
|
|
150
|
+
like: string;
|
|
151
|
+
/** The pass button (“Pass”). */
|
|
152
|
+
pass: string;
|
|
153
|
+
/** The deck's name and its keyboard hint (“Profiles. Right arrow to like, left arrow to pass.”). */
|
|
154
|
+
deck: string;
|
|
155
|
+
/** The stamp while dragging right (“LIKE”). */
|
|
156
|
+
likeStamp?: string;
|
|
157
|
+
/** The stamp while dragging left (“NOPE”). */
|
|
158
|
+
passStamp?: string;
|
|
159
|
+
/** Announced after a decision: `(name, direction) => …`. */
|
|
160
|
+
decided?: (name: string, direction: SwipeDirection) => string;
|
|
161
|
+
};
|
|
162
|
+
type SwipeDeckProps<T> = {
|
|
163
|
+
/** The cards still to decide, the first on top. */
|
|
164
|
+
items: readonly T[];
|
|
165
|
+
/** A card's id. */
|
|
166
|
+
getId: (item: T) => string;
|
|
167
|
+
/** A card's name, for the announcements. */
|
|
168
|
+
getName: (item: T) => string;
|
|
169
|
+
/** A card (usually a `PersonCard`). */
|
|
170
|
+
renderCard: (item: T) => React.ReactNode;
|
|
171
|
+
/** Called with the card decided and the decision; remove it from `items`. */
|
|
172
|
+
onSwipe: (item: T, direction: SwipeDirection) => void;
|
|
173
|
+
/** Shown when there is no card left (an `EmptyState`). */
|
|
174
|
+
empty?: React.ReactNode;
|
|
175
|
+
/** The words, passed translated. */
|
|
176
|
+
labels: SwipeDeckLabels;
|
|
177
|
+
/** Classes merged on the root. */
|
|
178
|
+
className?: string;
|
|
179
|
+
};
|
|
180
|
+
/** A deck of cards decided one by one: drag, the buttons, or ← → — each decision announced. */
|
|
181
|
+
declare function SwipeDeck<T>({ items, getId, getName, renderCard, onSwipe, empty, labels, className }: SwipeDeckProps<T>): React.JSX.Element;
|
|
182
|
+
|
|
183
|
+
export { PersonCard, type PersonCardProps, SwipeDeck, type SwipeDeckLabels, type SwipeDeckProps, type SwipeDirection, swipeDeck };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
usePrefersReducedMotion
|
|
4
|
+
} from "./chunk-BRKMQ6JV.js";
|
|
5
|
+
|
|
6
|
+
// src/swipe-deck/swipe-deck.tsx
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { tv } from "tailwind-variants";
|
|
9
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
var swipeDeck = tv({
|
|
11
|
+
slots: {
|
|
12
|
+
root: "flex w-full flex-col items-center gap-5",
|
|
13
|
+
stack: "relative aspect-[3/4] w-full max-w-sm outline-hidden focus-visible:rounded-2xl focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-4 focus-visible:ring-offset-surface-0",
|
|
14
|
+
layer: "absolute inset-0 touch-pan-y select-none",
|
|
15
|
+
stamp: "pointer-events-none absolute top-8 rounded-lg border-[3px] px-2.5 py-0.5 font-display text-2xl font-black uppercase tracking-wide",
|
|
16
|
+
buttons: "flex items-center gap-7",
|
|
17
|
+
button: "inline-flex h-16 w-16 items-center justify-center rounded-full border-2 bg-surface-1 shadow-md transition-transform active:scale-90 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40 motion-reduce:transition-none [&>svg]:h-7 [&>svg]:w-7",
|
|
18
|
+
card: "relative h-full w-full overflow-hidden rounded-2xl bg-surface-3 shadow-lg",
|
|
19
|
+
photo: "absolute inset-0 h-full w-full object-cover",
|
|
20
|
+
bars: "absolute inset-x-2 top-2 flex gap-1",
|
|
21
|
+
bar: "h-[3px] flex-1 rounded-full bg-fg-on-media opacity-40 data-[on]:opacity-100",
|
|
22
|
+
half: "absolute top-0 bottom-2/5 w-2/5 cursor-pointer outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
|
|
23
|
+
info: "absolute inset-x-0 bottom-0 flex flex-col gap-1.5 bg-gradient-to-t from-scrim-strong via-scrim-strong/80 to-transparent p-4 pt-24 text-fg-on-media",
|
|
24
|
+
name: "flex items-center gap-2 font-display text-3xl font-black",
|
|
25
|
+
age: "font-sans text-2xl font-normal",
|
|
26
|
+
bio: "line-clamp-2 text-sm",
|
|
27
|
+
footer: "mt-1 flex flex-wrap gap-1.5"
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
function PersonCard({ photos, name, age, badge, meta, bio, footer, photoLabels = { previous: "Previous photo", next: "Next photo" }, className }) {
|
|
31
|
+
const s = swipeDeck();
|
|
32
|
+
const [index, setIndex] = React.useState(0);
|
|
33
|
+
const photo = photos[index];
|
|
34
|
+
return /* @__PURE__ */ jsxs("article", { "aria-label": age !== void 0 ? `${name}, ${age}` : name, className: s.card({ className }), children: [
|
|
35
|
+
photo ? /* @__PURE__ */ jsx("img", { src: photo.src, alt: photo.alt ?? "", className: s.photo(), draggable: false }) : null,
|
|
36
|
+
photos.length > 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
37
|
+
/* @__PURE__ */ jsx("div", { className: s.bars(), "aria-hidden": true, children: photos.map((_, i) => /* @__PURE__ */ jsx("span", { className: s.bar(), "data-on": i === index || void 0 }, i)) }),
|
|
38
|
+
/* @__PURE__ */ jsx("button", { type: "button", "aria-label": photoLabels.previous, disabled: index === 0, className: s.half({ className: "left-0" }), onClick: () => setIndex((i) => Math.max(0, i - 1)) }),
|
|
39
|
+
/* @__PURE__ */ jsx("button", { type: "button", "aria-label": photoLabels.next, disabled: index === photos.length - 1, className: s.half({ className: "right-0" }), onClick: () => setIndex((i) => Math.min(photos.length - 1, i + 1)) })
|
|
40
|
+
] }) : null,
|
|
41
|
+
/* @__PURE__ */ jsxs("div", { className: s.info(), children: [
|
|
42
|
+
/* @__PURE__ */ jsxs("h3", { className: s.name(), children: [
|
|
43
|
+
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
44
|
+
name,
|
|
45
|
+
age !== void 0 ? /* @__PURE__ */ jsx("span", { className: s.age(), children: ` ${age}` }) : null
|
|
46
|
+
] }),
|
|
47
|
+
badge
|
|
48
|
+
] }),
|
|
49
|
+
meta,
|
|
50
|
+
bio ? /* @__PURE__ */ jsx("p", { className: s.bio(), children: bio }) : null,
|
|
51
|
+
footer ? /* @__PURE__ */ jsx("div", { className: s.footer(), children: footer }) : null
|
|
52
|
+
] })
|
|
53
|
+
] });
|
|
54
|
+
}
|
|
55
|
+
function SwipeDeck({ items, getId, getName, renderCard, onSwipe, empty, labels, className }) {
|
|
56
|
+
const s = swipeDeck();
|
|
57
|
+
const reduce = usePrefersReducedMotion();
|
|
58
|
+
const [dx, setDx] = React.useState(0);
|
|
59
|
+
const [leaving, setLeaving] = React.useState(null);
|
|
60
|
+
const [message, setMessage] = React.useState("");
|
|
61
|
+
const start = React.useRef(null);
|
|
62
|
+
const [dragging, setDragging] = React.useState(false);
|
|
63
|
+
const top = items[0];
|
|
64
|
+
const decide = (direction) => {
|
|
65
|
+
if (!top || leaving) return;
|
|
66
|
+
const finish = () => {
|
|
67
|
+
setLeaving(null);
|
|
68
|
+
setDx(0);
|
|
69
|
+
setMessage(labels.decided ? labels.decided(getName(top), direction) : `${getName(top)}: ${direction === "like" ? labels.like : labels.pass}`);
|
|
70
|
+
onSwipe(top, direction);
|
|
71
|
+
};
|
|
72
|
+
if (reduce) return finish();
|
|
73
|
+
setLeaving(direction);
|
|
74
|
+
setTimeout(finish, 220);
|
|
75
|
+
};
|
|
76
|
+
const offset = leaving ? (leaving === "like" ? 1 : -1) * 900 : dx;
|
|
77
|
+
const stampOpacity = (dir) => Math.max(0, Math.min(1, dir * offset / 120));
|
|
78
|
+
return /* @__PURE__ */ jsxs("div", { className: s.root({ className }), children: [
|
|
79
|
+
/* @__PURE__ */ jsx("p", { className: "sr-only", "aria-live": "polite", children: message }),
|
|
80
|
+
/* @__PURE__ */ jsxs(
|
|
81
|
+
"div",
|
|
82
|
+
{
|
|
83
|
+
role: "group",
|
|
84
|
+
"aria-label": labels.deck,
|
|
85
|
+
tabIndex: top ? 0 : -1,
|
|
86
|
+
className: s.stack(),
|
|
87
|
+
onKeyDown: (e) => {
|
|
88
|
+
if (e.key === "ArrowRight") decide("like");
|
|
89
|
+
if (e.key === "ArrowLeft") decide("pass");
|
|
90
|
+
},
|
|
91
|
+
children: [
|
|
92
|
+
!top ? empty : null,
|
|
93
|
+
items.slice(0, 2).reverse().map((item) => {
|
|
94
|
+
const isTop = item === top;
|
|
95
|
+
return /* @__PURE__ */ jsxs(
|
|
96
|
+
"div",
|
|
97
|
+
{
|
|
98
|
+
"aria-hidden": !isTop || void 0,
|
|
99
|
+
inert: !isTop || void 0,
|
|
100
|
+
className: s.layer(),
|
|
101
|
+
style: isTop ? { transform: `translateX(${offset}px) rotate(${offset / 22}deg)`, transition: dragging && !leaving ? "none" : reduce ? "none" : "transform 220ms var(--kz-ease, ease-out)" } : { transform: "scale(0.96)", opacity: 0.9 },
|
|
102
|
+
onPointerDown: (e) => {
|
|
103
|
+
if (!isTop || e.target.closest("button")) return;
|
|
104
|
+
start.current = { x: e.clientX, width: e.currentTarget.offsetWidth };
|
|
105
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
106
|
+
setDragging(true);
|
|
107
|
+
},
|
|
108
|
+
onPointerMove: (e) => {
|
|
109
|
+
if (start.current) setDx(e.clientX - start.current.x);
|
|
110
|
+
},
|
|
111
|
+
onPointerUp: () => {
|
|
112
|
+
const width = start.current?.width ?? 300;
|
|
113
|
+
start.current = null;
|
|
114
|
+
setDragging(false);
|
|
115
|
+
if (dx > width / 3) decide("like");
|
|
116
|
+
else if (dx < -width / 3) decide("pass");
|
|
117
|
+
else setDx(0);
|
|
118
|
+
},
|
|
119
|
+
children: [
|
|
120
|
+
renderCard(item),
|
|
121
|
+
isTop ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
122
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, className: s.stamp({ className: "left-6 -rotate-12 border-success text-fg-success" }), style: { opacity: stampOpacity(1) }, children: labels.likeStamp ?? labels.like }),
|
|
123
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, className: s.stamp({ className: "right-6 rotate-12 border-danger text-fg-danger" }), style: { opacity: stampOpacity(-1) }, children: labels.passStamp ?? labels.pass })
|
|
124
|
+
] }) : null
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
getId(item)
|
|
128
|
+
);
|
|
129
|
+
})
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
),
|
|
133
|
+
/* @__PURE__ */ jsxs("div", { className: s.buttons(), children: [
|
|
134
|
+
/* @__PURE__ */ jsx("button", { type: "button", "aria-label": labels.pass, disabled: !top, onClick: () => decide("pass"), className: s.button({ className: "border-danger text-fg-danger" }), children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6 6 18" }) }) }),
|
|
135
|
+
/* @__PURE__ */ jsx("button", { type: "button", "aria-label": labels.like, disabled: !top, onClick: () => decide("like"), className: s.button({ className: "border-success text-fg-success" }), children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z" }) }) })
|
|
136
|
+
] })
|
|
137
|
+
] });
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
PersonCard,
|
|
141
|
+
SwipeDeck,
|
|
142
|
+
swipeDeck
|
|
143
|
+
};
|
|
144
|
+
//# sourceMappingURL=swipe-deck.js.map
|