@burdenoff/microfe-coolandlovely 2026.706.2 → 2026.706.3
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/coolandlovely/CoolAndLovelyRoot.js +62 -46
- package/dist/coolandlovely/CoolAndLovelyRoot.js.map +1 -1
- package/dist/coolandlovely/components/AddToLookbookButton.js +22 -22
- package/dist/coolandlovely/components/AddToLookbookButton.js.map +1 -1
- package/dist/coolandlovely/components/BrandCover.js +39 -0
- package/dist/coolandlovely/components/BrandCover.js.map +1 -0
- package/dist/coolandlovely/components/BrandCreatorAvatars.js +57 -0
- package/dist/coolandlovely/components/BrandCreatorAvatars.js.map +1 -0
- package/dist/coolandlovely/components/ColourSwatchRow.js +86 -0
- package/dist/coolandlovely/components/ColourSwatchRow.js.map +1 -0
- package/dist/coolandlovely/components/Comments.js +4 -4
- package/dist/coolandlovely/components/Comments.js.map +1 -1
- package/dist/coolandlovely/components/FileImage.js.map +1 -1
- package/dist/coolandlovely/components/ImageUploadField.js +139 -57
- package/dist/coolandlovely/components/ImageUploadField.js.map +1 -1
- package/dist/coolandlovely/components/LookCard.js +77 -0
- package/dist/coolandlovely/components/LookCard.js.map +1 -0
- package/dist/coolandlovely/components/MetricsCard.js +1 -1
- package/dist/coolandlovely/components/MetricsCard.js.map +1 -1
- package/dist/coolandlovely/components/ShopItemCard.js +45 -0
- package/dist/coolandlovely/components/ShopItemCard.js.map +1 -0
- package/dist/coolandlovely/components/StepTracker.js +35 -0
- package/dist/coolandlovely/components/StepTracker.js.map +1 -0
- package/dist/coolandlovely/components/Thumbnail.js +15 -11
- package/dist/coolandlovely/components/Thumbnail.js.map +1 -1
- package/dist/coolandlovely/components/VisibilityChip.js +32 -0
- package/dist/coolandlovely/components/VisibilityChip.js.map +1 -0
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js +94 -69
- package/dist/coolandlovely/hooks/useCoolAndLovelyApi.js.map +1 -1
- package/dist/coolandlovely/hooks/useFiles.js +49 -32
- package/dist/coolandlovely/hooks/useFiles.js.map +1 -1
- package/dist/coolandlovely/pages/HomePage.js +112 -133
- package/dist/coolandlovely/pages/HomePage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandDetailPage.js +253 -242
- package/dist/coolandlovely/pages/brands/BrandDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandFormPage.js +116 -112
- package/dist/coolandlovely/pages/brands/BrandFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandsListPage.js +13 -13
- package/dist/coolandlovely/pages/brands/BrandsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js +117 -95
- package/dist/coolandlovely/pages/collaborations/CollaborationDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationFormPage.js +1 -1
- package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js +50 -43
- package/dist/coolandlovely/pages/collaborations/CollaborationsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionDetailPage.js +131 -108
- package/dist/coolandlovely/pages/collections/CollectionDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/collections/CollectionFormPage.js +1 -1
- package/dist/coolandlovely/pages/collections/CollectionsListPage.js +59 -56
- package/dist/coolandlovely/pages/collections/CollectionsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/discover/DiscoverPage.js +1 -1
- package/dist/coolandlovely/pages/discover/DiscoverPage.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js +175 -147
- package/dist/coolandlovely/pages/items/StyleItemDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/items/StyleItemFormPage.js +1 -1
- package/dist/coolandlovely/pages/items/StyleItemsListPage.js +71 -62
- package/dist/coolandlovely/pages/items/StyleItemsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js +149 -127
- package/dist/coolandlovely/pages/lookbooks/LookbookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js +69 -53
- package/dist/coolandlovely/pages/lookbooks/LookbooksListPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LookDetailPage.js +135 -113
- package/dist/coolandlovely/pages/looks/LookDetailPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LookFormPage.js +122 -110
- package/dist/coolandlovely/pages/looks/LookFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/looks/LooksListPage.js +60 -97
- package/dist/coolandlovely/pages/looks/LooksListPage.js.map +1 -1
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js +372 -210
- package/dist/coolandlovely/pages/profile/StyleProfilePage.js.map +1 -1
- package/dist/coolandlovely/pages/trends/TrendsPage.js +1 -1
- package/dist/coolandlovely/pages/trends/TrendsPage.js.map +1 -1
- package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js +130 -94
- package/dist/coolandlovely/pages/wardrobe/WardrobeFormPage.js.map +1 -1
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js +75 -76
- package/dist/coolandlovely/pages/wardrobe/WardrobeListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,206 +1,228 @@
|
|
|
1
1
|
import { useCoolAndLovely as e } from "../../context/CoolAndLovelyContext.js";
|
|
2
2
|
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { ErrorView as n, LoadingView as r } from "../../components/StateViews.js";
|
|
4
|
+
import { Thumbnail as i } from "../../components/Thumbnail.js";
|
|
5
5
|
import { useAddLookbookEntry as a, useLookbook as o, useLooks as s, useRemoveLookbookEntry as c, useStyleItems as l } from "../../hooks/useCoolAndLovelyApi.js";
|
|
6
|
+
import { formatRelativeTime as u } from "../../utils/formatters.js";
|
|
6
7
|
import "../../hooks/index.js";
|
|
7
|
-
import { TagList as
|
|
8
|
-
import { SelectField as
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
8
|
+
import { TagList as d } from "../../components/TagList.js";
|
|
9
|
+
import { SelectField as f, TextField as p } from "../../components/Field.js";
|
|
10
|
+
import { VisibilityChip as m } from "../../components/VisibilityChip.js";
|
|
11
|
+
import { useMemo as h, useState as g } from "react";
|
|
12
|
+
import { ArrowLeft as _, BookHeart as v, Clock as y, Loader2 as b, Plus as x, Sparkles as S, Tag as C, X as w } from "lucide-react";
|
|
13
|
+
import { useParams as T } from "react-router-dom";
|
|
14
|
+
import { jsx as E, jsxs as D } from "react/jsx-runtime";
|
|
15
|
+
import { GlassCard as O } from "@burdenoff/fe-libs/ui";
|
|
14
16
|
//#region src/coolandlovely/pages/lookbooks/LookbookDetailPage.tsx
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
var k = [
|
|
18
|
+
"aspect-[3/4]",
|
|
19
|
+
"aspect-square",
|
|
20
|
+
"aspect-[4/5]"
|
|
21
|
+
];
|
|
22
|
+
function A() {
|
|
23
|
+
let { lookbookId: A } = T(), { navigateTo: j } = e(), M = o(A), N = c(), P = a(), [F, I] = g(!1), [L, R] = g("look"), [z, B] = g(""), [V, H] = g(""), U = s({ pagination: {
|
|
17
24
|
limit: 100,
|
|
18
25
|
offset: 0
|
|
19
|
-
} }),
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
offset: 0
|
|
24
|
-
}
|
|
25
|
-
}), H = p(() => (B.data?.items ?? []).map((e) => ({
|
|
26
|
+
} }), W = l({ pagination: {
|
|
27
|
+
limit: 100,
|
|
28
|
+
offset: 0
|
|
29
|
+
} }), G = h(() => new Map((U.data?.items ?? []).map((e) => [e.id, e])), [U.data]), K = h(() => new Map((W.data?.items ?? []).map((e) => [e.id, e])), [W.data]), q = h(() => (W.data?.items ?? []).filter((e) => e.status !== "RETIRED"), [W.data]), J = h(() => (U.data?.items ?? []).map((e) => ({
|
|
26
30
|
value: e.id,
|
|
27
31
|
label: e.title
|
|
28
|
-
})), [
|
|
32
|
+
})), [U.data]), Y = h(() => q.map((e) => ({
|
|
29
33
|
value: e.id,
|
|
30
34
|
label: `${e.name}${e.brand?.name ? ` · ${e.brand.name}` : ""}`
|
|
31
|
-
})), [
|
|
32
|
-
if (
|
|
35
|
+
})), [q]);
|
|
36
|
+
if (M.isLoading) return /* @__PURE__ */ E(t, {
|
|
33
37
|
title: "Lookbook",
|
|
34
|
-
children: /* @__PURE__ */
|
|
38
|
+
children: /* @__PURE__ */ E(r, { label: "Loading lookbook…" })
|
|
35
39
|
});
|
|
36
|
-
if (
|
|
40
|
+
if (M.isError || !M.data) return /* @__PURE__ */ E(t, {
|
|
37
41
|
title: "Lookbook",
|
|
38
|
-
children: /* @__PURE__ */
|
|
42
|
+
children: /* @__PURE__ */ E(n, {
|
|
39
43
|
title: "Lookbook not found",
|
|
40
|
-
onRetry: () => void
|
|
44
|
+
onRetry: () => void M.refetch()
|
|
41
45
|
})
|
|
42
46
|
});
|
|
43
|
-
let
|
|
44
|
-
return /* @__PURE__ */
|
|
45
|
-
title:
|
|
46
|
-
description:
|
|
47
|
-
actions: /* @__PURE__ */
|
|
47
|
+
let X = M.data, Z = [...X.entries ?? []].sort((e, t) => e.position - t.position);
|
|
48
|
+
return /* @__PURE__ */ E(t, {
|
|
49
|
+
title: X.name,
|
|
50
|
+
description: X.description ?? void 0,
|
|
51
|
+
actions: /* @__PURE__ */ D("div", {
|
|
48
52
|
className: "flex flex-wrap items-center gap-2",
|
|
49
|
-
children: [/* @__PURE__ */
|
|
53
|
+
children: [/* @__PURE__ */ D("button", {
|
|
50
54
|
type: "button",
|
|
51
|
-
onClick: () =>
|
|
55
|
+
onClick: () => j("/lookbooks"),
|
|
52
56
|
className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
|
|
53
|
-
children: [/* @__PURE__ */
|
|
54
|
-
}), /* @__PURE__ */
|
|
57
|
+
children: [/* @__PURE__ */ E(_, { className: "h-4 w-4" }), " Back"]
|
|
58
|
+
}), /* @__PURE__ */ D("button", {
|
|
55
59
|
type: "button",
|
|
56
60
|
onClick: () => {
|
|
57
|
-
|
|
61
|
+
I((e) => !e), B("");
|
|
58
62
|
},
|
|
59
63
|
className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90",
|
|
60
|
-
children: [
|
|
64
|
+
children: [E(F ? w : x, { className: "h-4 w-4" }), F ? "Close" : "Add entry"]
|
|
61
65
|
})]
|
|
62
66
|
}),
|
|
63
|
-
children: /* @__PURE__ */
|
|
64
|
-
className: "mx-auto max-w-
|
|
67
|
+
children: /* @__PURE__ */ D("div", {
|
|
68
|
+
className: "mx-auto max-w-5xl",
|
|
65
69
|
children: [
|
|
66
|
-
/* @__PURE__ */
|
|
70
|
+
/* @__PURE__ */ D(O, {
|
|
67
71
|
treatment: "glass",
|
|
68
72
|
className: "p-6",
|
|
69
|
-
children: [/* @__PURE__ */
|
|
73
|
+
children: [/* @__PURE__ */ D("div", {
|
|
70
74
|
className: "flex items-center gap-3",
|
|
71
|
-
children: [/* @__PURE__ */
|
|
72
|
-
className: "flex h-12 w-12 items-center justify-center rounded-lg bg-accent-purple-subtle text-text-primary",
|
|
73
|
-
children: /* @__PURE__ */
|
|
74
|
-
}), /* @__PURE__ */
|
|
75
|
+
children: [/* @__PURE__ */ E("div", {
|
|
76
|
+
className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-accent-purple-subtle text-text-primary",
|
|
77
|
+
children: /* @__PURE__ */ E(v, { className: "h-6 w-6" })
|
|
78
|
+
}), /* @__PURE__ */ D("div", {
|
|
75
79
|
className: "min-w-0 flex-1",
|
|
76
|
-
children: [/* @__PURE__ */
|
|
80
|
+
children: [/* @__PURE__ */ E("h2", {
|
|
77
81
|
className: "truncate text-xl font-semibold text-text-primary",
|
|
78
|
-
children:
|
|
79
|
-
}), /* @__PURE__ */
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
children: X.name
|
|
83
|
+
}), /* @__PURE__ */ D("div", {
|
|
84
|
+
className: "mt-1.5 flex flex-wrap items-center gap-2",
|
|
85
|
+
children: [
|
|
86
|
+
/* @__PURE__ */ E(m, { visibility: X.visibility }),
|
|
87
|
+
/* @__PURE__ */ D("span", {
|
|
88
|
+
className: "inline-flex items-center gap-1 text-xs text-text-secondary",
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ E(y, { className: "h-3.5 w-3.5" }),
|
|
91
|
+
" Updated ",
|
|
92
|
+
u(X.updatedAt)
|
|
93
|
+
]
|
|
94
|
+
}),
|
|
95
|
+
/* @__PURE__ */ D("span", {
|
|
96
|
+
className: "text-xs text-text-secondary tabular-nums",
|
|
97
|
+
children: [
|
|
98
|
+
"· ",
|
|
99
|
+
Z.length,
|
|
100
|
+
" ",
|
|
101
|
+
Z.length === 1 ? "item" : "items"
|
|
102
|
+
]
|
|
103
|
+
})
|
|
104
|
+
]
|
|
82
105
|
})]
|
|
83
106
|
})]
|
|
84
|
-
}),
|
|
85
|
-
tags:
|
|
107
|
+
}), X.tags.length > 0 && /* @__PURE__ */ E(d, {
|
|
108
|
+
tags: X.tags,
|
|
86
109
|
className: "mt-4"
|
|
87
110
|
})]
|
|
88
111
|
}),
|
|
89
|
-
|
|
112
|
+
F && /* @__PURE__ */ E("form", {
|
|
90
113
|
onSubmit: (e) => {
|
|
91
|
-
e.preventDefault(), !(!
|
|
92
|
-
lookbookId:
|
|
93
|
-
lookId:
|
|
94
|
-
styleItemId:
|
|
95
|
-
note:
|
|
114
|
+
e.preventDefault(), !(!A || !z) && P.mutate({
|
|
115
|
+
lookbookId: A,
|
|
116
|
+
lookId: L === "look" ? z : void 0,
|
|
117
|
+
styleItemId: L === "item" ? z : void 0,
|
|
118
|
+
note: V.trim() || void 0
|
|
96
119
|
}, { onSuccess: () => {
|
|
97
|
-
|
|
120
|
+
B(""), H(""), I(!1);
|
|
98
121
|
} });
|
|
99
122
|
},
|
|
100
123
|
className: "mt-6",
|
|
101
|
-
children: /* @__PURE__ */
|
|
124
|
+
children: /* @__PURE__ */ D(O, {
|
|
102
125
|
treatment: "plain",
|
|
103
126
|
className: "p-5",
|
|
104
127
|
children: [
|
|
105
|
-
/* @__PURE__ */
|
|
128
|
+
/* @__PURE__ */ D("h3", {
|
|
106
129
|
className: "mb-4 flex items-center gap-2 text-base font-semibold text-text-primary",
|
|
107
|
-
children: [/* @__PURE__ */
|
|
130
|
+
children: [/* @__PURE__ */ E(x, { className: "h-5 w-5" }), " Add to this lookbook"]
|
|
108
131
|
}),
|
|
109
|
-
/* @__PURE__ */
|
|
132
|
+
/* @__PURE__ */ E("div", {
|
|
110
133
|
className: "mb-4 flex flex-wrap gap-2",
|
|
111
|
-
children: ["look", "item"].map((e) => /* @__PURE__ */
|
|
134
|
+
children: ["look", "item"].map((e) => /* @__PURE__ */ E("button", {
|
|
112
135
|
type: "button",
|
|
113
136
|
onClick: () => {
|
|
114
|
-
|
|
137
|
+
R(e), B("");
|
|
115
138
|
},
|
|
116
|
-
className:
|
|
139
|
+
className: L === e ? "rounded-full bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text" : "rounded-full bg-bg-sunken px-3 py-1.5 text-sm font-medium text-text-secondary hover:text-text-primary",
|
|
117
140
|
children: e === "look" ? "Save a look" : "Clip a piece"
|
|
118
141
|
}, e))
|
|
119
142
|
}),
|
|
120
|
-
/* @__PURE__ */
|
|
121
|
-
label:
|
|
122
|
-
value:
|
|
123
|
-
onChange:
|
|
124
|
-
options:
|
|
125
|
-
placeholder:
|
|
143
|
+
/* @__PURE__ */ E(f, {
|
|
144
|
+
label: L === "look" ? "Look" : "Style item",
|
|
145
|
+
value: z,
|
|
146
|
+
onChange: B,
|
|
147
|
+
options: L === "look" ? J : Y,
|
|
148
|
+
placeholder: L === "look" ? U.isLoading ? "Loading looks…" : "Select a look" : W.isLoading ? "Loading items…" : "Select a style item"
|
|
126
149
|
}),
|
|
127
|
-
/* @__PURE__ */
|
|
150
|
+
/* @__PURE__ */ E(p, {
|
|
128
151
|
label: "Note (optional)",
|
|
129
|
-
value:
|
|
130
|
-
onChange:
|
|
152
|
+
value: V,
|
|
153
|
+
onChange: H,
|
|
131
154
|
placeholder: "Why you saved it…",
|
|
132
155
|
className: "mt-4"
|
|
133
156
|
}),
|
|
134
|
-
|
|
157
|
+
P.isError && /* @__PURE__ */ E("p", {
|
|
135
158
|
className: "mt-3 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
136
|
-
children:
|
|
159
|
+
children: P.error instanceof Error ? P.error.message : "Could not add entry"
|
|
137
160
|
}),
|
|
138
|
-
/* @__PURE__ */
|
|
161
|
+
/* @__PURE__ */ E("div", {
|
|
139
162
|
className: "mt-4 flex justify-end",
|
|
140
|
-
children: /* @__PURE__ */
|
|
163
|
+
children: /* @__PURE__ */ D("button", {
|
|
141
164
|
type: "submit",
|
|
142
|
-
disabled: !
|
|
165
|
+
disabled: !z || P.isPending,
|
|
143
166
|
className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50",
|
|
144
|
-
children: [
|
|
167
|
+
children: [P.isPending ? /* @__PURE__ */ E(b, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ E(x, { className: "h-4 w-4" }), "Add entry"]
|
|
145
168
|
})
|
|
146
169
|
})
|
|
147
170
|
]
|
|
148
171
|
})
|
|
149
172
|
}),
|
|
150
|
-
/* @__PURE__ */
|
|
173
|
+
/* @__PURE__ */ D("section", {
|
|
151
174
|
className: "mt-6",
|
|
152
|
-
children: [/* @__PURE__ */
|
|
175
|
+
children: [/* @__PURE__ */ D("h3", {
|
|
153
176
|
className: "mb-3 text-lg font-semibold text-text-primary",
|
|
154
177
|
children: [
|
|
155
|
-
"
|
|
156
|
-
|
|
178
|
+
"Board (",
|
|
179
|
+
Z.length,
|
|
157
180
|
")"
|
|
158
181
|
]
|
|
159
|
-
}),
|
|
182
|
+
}), Z.length === 0 ? /* @__PURE__ */ E("p", {
|
|
160
183
|
className: "rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary",
|
|
161
184
|
children: "This lookbook has no entries yet. Clip a catalogue piece or save a look to add one."
|
|
162
|
-
}) : /* @__PURE__ */
|
|
163
|
-
className: "
|
|
164
|
-
children:
|
|
165
|
-
let
|
|
166
|
-
return /* @__PURE__ */
|
|
167
|
-
className: "
|
|
168
|
-
children: [
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}),
|
|
195
|
-
/* @__PURE__ */ w("button", {
|
|
196
|
-
type: "button",
|
|
197
|
-
disabled: A.isPending,
|
|
198
|
-
onClick: () => A.mutate(e.id),
|
|
199
|
-
"aria-label": "Remove entry",
|
|
200
|
-
className: "inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-lg text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-50",
|
|
201
|
-
children: A.isPending && A.variables === e.id ? /* @__PURE__ */ w(_, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ w(x, { className: "h-3.5 w-3.5" })
|
|
185
|
+
}) : /* @__PURE__ */ E("div", {
|
|
186
|
+
className: "columns-2 gap-4 md:columns-3",
|
|
187
|
+
children: Z.map((e, t) => {
|
|
188
|
+
let n = !!e.lookId, r = e.lookId ? G.get(e.lookId) : void 0, a = e.styleItemId ? K.get(e.styleItemId) : void 0, o = n ? r?.title ?? "Saved look" : a?.name ?? "Clipped piece", s = n ? r?.image?.id : a?.image?.id, c = N.isPending && N.variables === e.id;
|
|
189
|
+
return /* @__PURE__ */ D("div", {
|
|
190
|
+
className: "group relative mb-4 break-inside-avoid overflow-hidden rounded-xl border border-border-seam bg-bg-surface shadow-sm transition-shadow hover:shadow-lg",
|
|
191
|
+
children: [/* @__PURE__ */ E("button", {
|
|
192
|
+
type: "button",
|
|
193
|
+
onClick: () => {
|
|
194
|
+
n && e.lookId ? j(`/looks/${e.lookId}`) : e.styleItemId && j(`/items/${e.styleItemId}`);
|
|
195
|
+
},
|
|
196
|
+
"aria-label": `Open ${o}`,
|
|
197
|
+
className: "block w-full text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--color-focus-ring)]",
|
|
198
|
+
children: /* @__PURE__ */ E(i, {
|
|
199
|
+
icon: E(n ? S : C, { className: "h-8 w-8" }),
|
|
200
|
+
seed: e.id,
|
|
201
|
+
fileId: s,
|
|
202
|
+
alt: o,
|
|
203
|
+
className: `w-full rounded-none ${k[t % k.length]}`,
|
|
204
|
+
overlay: /* @__PURE__ */ D("div", {
|
|
205
|
+
className: "pointer-events-none absolute inset-x-0 bottom-0 flex flex-col gap-1.5 bg-gradient-to-t from-bg-canvas/90 via-bg-canvas/40 to-transparent p-3 pt-10",
|
|
206
|
+
children: [e.note && /* @__PURE__ */ E("span", {
|
|
207
|
+
className: "inline-flex w-fit max-w-full items-center rounded-md bg-bg-canvas/80 px-2 py-0.5 text-[11px] font-medium text-text-primary backdrop-blur",
|
|
208
|
+
children: /* @__PURE__ */ E("span", {
|
|
209
|
+
className: "truncate",
|
|
210
|
+
children: e.note
|
|
211
|
+
})
|
|
212
|
+
}), /* @__PURE__ */ E("p", {
|
|
213
|
+
className: "truncate text-sm font-semibold text-text-primary",
|
|
214
|
+
children: o
|
|
215
|
+
})]
|
|
216
|
+
})
|
|
202
217
|
})
|
|
203
|
-
|
|
218
|
+
}), /* @__PURE__ */ E("button", {
|
|
219
|
+
type: "button",
|
|
220
|
+
disabled: N.isPending,
|
|
221
|
+
onClick: () => N.mutate(e.id),
|
|
222
|
+
"aria-label": `Remove ${o}`,
|
|
223
|
+
className: "absolute right-2 top-2 z-10 inline-flex h-7 w-7 items-center justify-center rounded-full bg-bg-surface/90 text-text-secondary opacity-100 shadow-sm backdrop-blur transition-opacity hover:text-status-error-text sm:opacity-0 sm:focus-visible:opacity-100 sm:group-hover:opacity-100 disabled:opacity-60",
|
|
224
|
+
children: c ? /* @__PURE__ */ E(b, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ E(w, { className: "h-3.5 w-3.5" })
|
|
225
|
+
})]
|
|
204
226
|
}, e.id);
|
|
205
227
|
})
|
|
206
228
|
})]
|
|
@@ -210,6 +232,6 @@ function D() {
|
|
|
210
232
|
});
|
|
211
233
|
}
|
|
212
234
|
//#endregion
|
|
213
|
-
export {
|
|
235
|
+
export { A as LookbookDetailPage };
|
|
214
236
|
|
|
215
237
|
//# sourceMappingURL=LookbookDetailPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LookbookDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/lookbooks/LookbookDetailPage.tsx"],"sourcesContent":["/**\n * Lookbook Detail Page\n * @module coolandlovely/pages/lookbooks\n *\n * Single lookbook (`cooLookbook`) showing its clipped entries, with per-entry\n * removal (`cooRemoveLookbookEntry`).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, BookHeart, Sparkles, Tag, Trash2, Loader2, Plus, X } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { TagList } from '../../components/TagList';\nimport { SelectField, TextField } from '../../components/Field';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useLookbook,\n useRemoveLookbookEntry,\n useAddLookbookEntry,\n useLooks,\n useStyleItems,\n} from '../../hooks';\n\ntype EntryKind = 'look' | 'item';\n\nexport function LookbookDetailPage() {\n const { lookbookId } = useParams<{ lookbookId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useLookbook(lookbookId);\n const removeEntry = useRemoveLookbookEntry();\n const addEntry = useAddLookbookEntry();\n\n const [adding, setAdding] = useState(false);\n const [entryKind, setEntryKind] = useState<EntryKind>('look');\n const [selectedId, setSelectedId] = useState('');\n const [note, setNote] = useState('');\n\n const looks = useLooks({ pagination: { limit: 100, offset: 0 } });\n const items = useStyleItems({ status: 'ACTIVE', pagination: { limit: 100, offset: 0 } });\n\n const lookOptions = useMemo(\n () => (looks.data?.items ?? []).map((l) => ({ value: l.id, label: l.title })),\n [looks.data]\n );\n const itemOptions = useMemo(\n () =>\n (items.data?.items ?? []).map((i) => ({\n value: i.id,\n label: `${i.name}${i.brand?.name ? ` · ${i.brand.name}` : ''}`,\n })),\n [items.data]\n );\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Lookbook\">\n <LoadingView label=\"Loading lookbook…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Lookbook\">\n <ErrorView title=\"Lookbook not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const lookbook = query.data;\n const entries = [...(lookbook.entries ?? [])].sort((a, b) => a.position - b.position);\n\n const handleAddEntry = (e: React.FormEvent) => {\n e.preventDefault();\n if (!lookbookId || !selectedId) return;\n addEntry.mutate(\n {\n lookbookId,\n lookId: entryKind === 'look' ? selectedId : undefined,\n styleItemId: entryKind === 'item' ? selectedId : undefined,\n note: note.trim() || undefined,\n },\n {\n onSuccess: () => {\n setSelectedId('');\n setNote('');\n setAdding(false);\n },\n }\n );\n };\n\n return (\n <PageLayout\n title={lookbook.name}\n description={lookbook.description ?? undefined}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/lookbooks')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setAdding((v) => !v);\n setSelectedId('');\n }}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n {adding ? <X className=\"h-4 w-4\" /> : <Plus className=\"h-4 w-4\" />}\n {adding ? 'Close' : 'Add entry'}\n </button>\n </div>\n }\n >\n <div className=\"mx-auto max-w-3xl\">\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-12 w-12 items-center justify-center rounded-lg bg-accent-purple-subtle text-text-primary\">\n <BookHeart className=\"h-6 w-6\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <h2 className=\"truncate text-xl font-semibold text-text-primary\">{lookbook.name}</h2>\n <StatusBadge status={lookbook.visibility} className=\"mt-1\" />\n </div>\n </div>\n {lookbook.tags.length > 0 && <TagList tags={lookbook.tags} className=\"mt-4\" />}\n </GlassCard>\n\n {adding && (\n <form onSubmit={handleAddEntry} className=\"mt-6\">\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-4 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <Plus className=\"h-5 w-5\" /> Add to this lookbook\n </h3>\n <div className=\"mb-4 flex flex-wrap gap-2\">\n {(['look', 'item'] as EntryKind[]).map((k) => (\n <button\n key={k}\n type=\"button\"\n onClick={() => {\n setEntryKind(k);\n setSelectedId('');\n }}\n className={\n entryKind === k\n ? 'rounded-full bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text'\n : 'rounded-full bg-bg-sunken px-3 py-1.5 text-sm font-medium text-text-secondary hover:text-text-primary'\n }\n >\n {k === 'look' ? 'Save a look' : 'Clip a piece'}\n </button>\n ))}\n </div>\n <SelectField\n label={entryKind === 'look' ? 'Look' : 'Style item'}\n value={selectedId}\n onChange={setSelectedId}\n options={entryKind === 'look' ? lookOptions : itemOptions}\n placeholder={\n entryKind === 'look'\n ? looks.isLoading\n ? 'Loading looks…'\n : 'Select a look'\n : items.isLoading\n ? 'Loading items…'\n : 'Select a style item'\n }\n />\n <TextField\n label=\"Note (optional)\"\n value={note}\n onChange={setNote}\n placeholder=\"Why you saved it…\"\n className=\"mt-4\"\n />\n {addEntry.isError && (\n <p className=\"mt-3 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {addEntry.error instanceof Error ? addEntry.error.message : 'Could not add entry'}\n </p>\n )}\n <div className=\"mt-4 flex justify-end\">\n <button\n type=\"submit\"\n disabled={!selectedId || addEntry.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {addEntry.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Plus className=\"h-4 w-4\" />\n )}\n Add entry\n </button>\n </div>\n </GlassCard>\n </form>\n )}\n\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">\n Entries ({entries.length})\n </h3>\n {entries.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n This lookbook has no entries yet. Clip a catalogue piece or save a look to add one.\n </p>\n ) : (\n <div className=\"divide-y divide-border-seam rounded-lg border border-border-seam bg-bg-surface\">\n {entries.map((entry) => {\n const isLook = Boolean(entry.lookId);\n return (\n <div key={entry.id} className=\"flex items-center gap-4 px-4 py-3\">\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-bg-sunken text-text-secondary\">\n {isLook ? <Sparkles className=\"h-5 w-5\" /> : <Tag className=\"h-5 w-5\" />}\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n {isLook ? 'Saved look' : 'Clipped piece'}\n </p>\n {entry.note && (\n <p className=\"truncate text-xs text-text-secondary\">{entry.note}</p>\n )}\n </div>\n {isLook && entry.lookId && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/looks/${entry.lookId}`)}\n className=\"shrink-0 text-xs font-medium text-primary hover:underline\"\n >\n View\n </button>\n )}\n {!isLook && entry.styleItemId && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/items/${entry.styleItemId}`)}\n className=\"shrink-0 text-xs font-medium text-primary hover:underline\"\n >\n View\n </button>\n )}\n <button\n type=\"button\"\n disabled={removeEntry.isPending}\n onClick={() => removeEntry.mutate(entry.id)}\n aria-label=\"Remove entry\"\n className=\"inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-lg text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-50\"\n >\n {removeEntry.isPending && removeEntry.variables === entry.id ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"h-3.5 w-3.5\" />\n )}\n </button>\n </div>\n );\n })}\n </div>\n )}\n </section>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LookbookDetailPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/lookbooks/LookbookDetailPage.tsx"],"sourcesContent":["/**\n * Lookbook Detail Page\n * @module coolandlovely/pages/lookbooks\n *\n * Single lookbook (`cooLookbook`) rendered as a Pinterest-style masonry board.\n * Each entry's `lookId` / `styleItemId` is resolved client-side against the\n * already-fetched looks/items lists to a real title + image, with an optional\n * note badge and a hover X-remove (`cooRemoveLookbookEntry`). New entries are\n * added inline (`cooAddLookbookEntry`).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, BookHeart, Sparkles, Tag, Loader2, Plus, X, Clock } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Thumbnail } from '../../components/Thumbnail';\nimport { TagList } from '../../components/TagList';\nimport { VisibilityChip } from '../../components/VisibilityChip';\nimport { SelectField, TextField } from '../../components/Field';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport {\n useLookbook,\n useRemoveLookbookEntry,\n useAddLookbookEntry,\n useLooks,\n useStyleItems,\n} from '../../hooks';\nimport { formatRelativeTime } from '../../utils/formatters';\n\ntype EntryKind = 'look' | 'item';\n\n/** Alternating aspect ratios give the board its masonry rhythm. */\nconst TILE_ASPECTS = ['aspect-[3/4]', 'aspect-square', 'aspect-[4/5]'];\n\nexport function LookbookDetailPage() {\n const { lookbookId } = useParams<{ lookbookId: string }>();\n const { navigateTo } = useCoolAndLovely();\n const query = useLookbook(lookbookId);\n const removeEntry = useRemoveLookbookEntry();\n const addEntry = useAddLookbookEntry();\n\n const [adding, setAdding] = useState(false);\n const [entryKind, setEntryKind] = useState<EntryKind>('look');\n const [selectedId, setSelectedId] = useState('');\n const [note, setNote] = useState('');\n\n const looks = useLooks({ pagination: { limit: 100, offset: 0 } });\n const items = useStyleItems({ pagination: { limit: 100, offset: 0 } });\n\n const lookById = useMemo(\n () => new Map((looks.data?.items ?? []).map((l) => [l.id, l])),\n [looks.data]\n );\n const itemById = useMemo(\n () => new Map((items.data?.items ?? []).map((i) => [i.id, i])),\n [items.data]\n );\n const usableItems = useMemo(\n () => (items.data?.items ?? []).filter((i) => i.status !== 'RETIRED'),\n [items.data]\n );\n\n const lookOptions = useMemo(\n () => (looks.data?.items ?? []).map((l) => ({ value: l.id, label: l.title })),\n [looks.data]\n );\n const itemOptions = useMemo(\n () =>\n usableItems.map((i) => ({\n value: i.id,\n label: `${i.name}${i.brand?.name ? ` · ${i.brand.name}` : ''}`,\n })),\n [usableItems]\n );\n\n if (query.isLoading) {\n return (\n <PageLayout title=\"Lookbook\">\n <LoadingView label=\"Loading lookbook…\" />\n </PageLayout>\n );\n }\n\n if (query.isError || !query.data) {\n return (\n <PageLayout title=\"Lookbook\">\n <ErrorView title=\"Lookbook not found\" onRetry={() => void query.refetch()} />\n </PageLayout>\n );\n }\n\n const lookbook = query.data;\n const entries = [...(lookbook.entries ?? [])].sort((a, b) => a.position - b.position);\n\n const handleAddEntry = (e: React.FormEvent) => {\n e.preventDefault();\n if (!lookbookId || !selectedId) return;\n addEntry.mutate(\n {\n lookbookId,\n lookId: entryKind === 'look' ? selectedId : undefined,\n styleItemId: entryKind === 'item' ? selectedId : undefined,\n note: note.trim() || undefined,\n },\n {\n onSuccess: () => {\n setSelectedId('');\n setNote('');\n setAdding(false);\n },\n }\n );\n };\n\n return (\n <PageLayout\n title={lookbook.name}\n description={lookbook.description ?? undefined}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/lookbooks')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setAdding((v) => !v);\n setSelectedId('');\n }}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n {adding ? <X className=\"h-4 w-4\" /> : <Plus className=\"h-4 w-4\" />}\n {adding ? 'Close' : 'Add entry'}\n </button>\n </div>\n }\n >\n <div className=\"mx-auto max-w-5xl\">\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-accent-purple-subtle text-text-primary\">\n <BookHeart className=\"h-6 w-6\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <h2 className=\"truncate text-xl font-semibold text-text-primary\">{lookbook.name}</h2>\n <div className=\"mt-1.5 flex flex-wrap items-center gap-2\">\n <VisibilityChip visibility={lookbook.visibility} />\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5\" /> Updated {formatRelativeTime(lookbook.updatedAt)}\n </span>\n <span className=\"text-xs text-text-secondary tabular-nums\">\n · {entries.length} {entries.length === 1 ? 'item' : 'items'}\n </span>\n </div>\n </div>\n </div>\n {lookbook.tags.length > 0 && <TagList tags={lookbook.tags} className=\"mt-4\" />}\n </GlassCard>\n\n {adding && (\n <form onSubmit={handleAddEntry} className=\"mt-6\">\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <h3 className=\"mb-4 flex items-center gap-2 text-base font-semibold text-text-primary\">\n <Plus className=\"h-5 w-5\" /> Add to this lookbook\n </h3>\n <div className=\"mb-4 flex flex-wrap gap-2\">\n {(['look', 'item'] as EntryKind[]).map((k) => (\n <button\n key={k}\n type=\"button\"\n onClick={() => {\n setEntryKind(k);\n setSelectedId('');\n }}\n className={\n entryKind === k\n ? 'rounded-full bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text'\n : 'rounded-full bg-bg-sunken px-3 py-1.5 text-sm font-medium text-text-secondary hover:text-text-primary'\n }\n >\n {k === 'look' ? 'Save a look' : 'Clip a piece'}\n </button>\n ))}\n </div>\n <SelectField\n label={entryKind === 'look' ? 'Look' : 'Style item'}\n value={selectedId}\n onChange={setSelectedId}\n options={entryKind === 'look' ? lookOptions : itemOptions}\n placeholder={\n entryKind === 'look'\n ? looks.isLoading\n ? 'Loading looks…'\n : 'Select a look'\n : items.isLoading\n ? 'Loading items…'\n : 'Select a style item'\n }\n />\n <TextField\n label=\"Note (optional)\"\n value={note}\n onChange={setNote}\n placeholder=\"Why you saved it…\"\n className=\"mt-4\"\n />\n {addEntry.isError && (\n <p className=\"mt-3 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {addEntry.error instanceof Error ? addEntry.error.message : 'Could not add entry'}\n </p>\n )}\n <div className=\"mt-4 flex justify-end\">\n <button\n type=\"submit\"\n disabled={!selectedId || addEntry.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {addEntry.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Plus className=\"h-4 w-4\" />\n )}\n Add entry\n </button>\n </div>\n </GlassCard>\n </form>\n )}\n\n <section className=\"mt-6\">\n <h3 className=\"mb-3 text-lg font-semibold text-text-primary\">Board ({entries.length})</h3>\n {entries.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-surface p-4 text-sm text-text-secondary\">\n This lookbook has no entries yet. Clip a catalogue piece or save a look to add one.\n </p>\n ) : (\n <div className=\"columns-2 gap-4 md:columns-3\">\n {entries.map((entry, idx) => {\n const isLook = Boolean(entry.lookId);\n const look = entry.lookId ? lookById.get(entry.lookId) : undefined;\n const item = entry.styleItemId ? itemById.get(entry.styleItemId) : undefined;\n const title = isLook\n ? (look?.title ?? 'Saved look')\n : (item?.name ?? 'Clipped piece');\n const imageFileId = isLook ? look?.image?.id : item?.image?.id;\n const removing = removeEntry.isPending && removeEntry.variables === entry.id;\n const onOpen = () => {\n if (isLook && entry.lookId) navigateTo(`/looks/${entry.lookId}`);\n else if (entry.styleItemId) navigateTo(`/items/${entry.styleItemId}`);\n };\n return (\n <div\n key={entry.id}\n className=\"group relative mb-4 break-inside-avoid overflow-hidden rounded-xl border border-border-seam bg-bg-surface shadow-sm transition-shadow hover:shadow-lg\"\n >\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${title}`}\n className=\"block w-full text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Thumbnail\n icon={\n isLook ? <Sparkles className=\"h-8 w-8\" /> : <Tag className=\"h-8 w-8\" />\n }\n seed={entry.id}\n fileId={imageFileId}\n alt={title}\n className={`w-full rounded-none ${TILE_ASPECTS[idx % TILE_ASPECTS.length]}`}\n overlay={\n <div className=\"pointer-events-none absolute inset-x-0 bottom-0 flex flex-col gap-1.5 bg-gradient-to-t from-bg-canvas/90 via-bg-canvas/40 to-transparent p-3 pt-10\">\n {entry.note && (\n <span className=\"inline-flex w-fit max-w-full items-center rounded-md bg-bg-canvas/80 px-2 py-0.5 text-[11px] font-medium text-text-primary backdrop-blur\">\n <span className=\"truncate\">{entry.note}</span>\n </span>\n )}\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {title}\n </p>\n </div>\n }\n />\n </button>\n <button\n type=\"button\"\n disabled={removeEntry.isPending}\n onClick={() => removeEntry.mutate(entry.id)}\n aria-label={`Remove ${title}`}\n className=\"absolute right-2 top-2 z-10 inline-flex h-7 w-7 items-center justify-center rounded-full bg-bg-surface/90 text-text-secondary opacity-100 shadow-sm backdrop-blur transition-opacity hover:text-status-error-text sm:opacity-0 sm:focus-visible:opacity-100 sm:group-hover:opacity-100 disabled:opacity-60\"\n >\n {removing ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <X className=\"h-3.5 w-3.5\" />\n )}\n </button>\n </div>\n );\n })}\n </div>\n )}\n </section>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|