@designa8/vibedesign-stripe-deepseek-2-176b 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @designa8/vibedesign-stripe-deepseek-2-176b
2
2
 
3
- Complete design system generated by Atoms Design, version **0.2.0**.
3
+ Complete design system generated by Atoms Design, version **0.2.1**.
4
4
  Tokens, atoms, molecules, organisms, and templates — import from the package root;
5
5
  never hardcode visual values in consumer code.
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@designa8/vibedesign-stripe-deepseek-2-176b",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Complete design system \"My Project stripe-deepseek\" generated by Atoms Design.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -40,6 +40,9 @@ export interface NotificationFeedMode {
40
40
  label: string;
41
41
  }
42
42
 
43
+ /** `card` draws the bordered surface; `bare` fills its parent (e.g. a Drawer body). */
44
+ export type NotificationFeedChrome = "card" | "bare";
45
+
43
46
  export interface NotificationFeedProps
44
47
  extends Omit<React.ComponentPropsWithoutRef<"div">, "onChange" | "title"> {
45
48
  /** Feed heading. */
@@ -66,6 +69,19 @@ export interface NotificationFeedProps
66
69
  feedLabel?: string;
67
70
  /** Shown when the active filter has no rows. */
68
71
  emptyMessage?: string;
72
+ /**
73
+ * Fires with the clicked row when its action link is followed. Unless a
74
+ * modifier key is held, the browser navigation is prevented so an SPA router
75
+ * can take over (and the consumer can mark the row read).
76
+ */
77
+ onItemClick?: (
78
+ item: NotificationFeedItem,
79
+ event: React.MouseEvent<HTMLAnchorElement>,
80
+ ) => void;
81
+ /** Right-hand slot on the title row — e.g. a "Mark all read" button. */
82
+ actions?: React.ReactNode;
83
+ /** Outer surface treatment. `bare` drops the card's border, radius, shadow and padding. */
84
+ chrome?: NotificationFeedChrome;
69
85
  }
70
86
 
71
87
  const defaultFilters: NotificationFeedFilter[] = [
@@ -149,10 +165,12 @@ const defaultItems: NotificationFeedItem[] = [
149
165
  ];
150
166
 
151
167
  /**
152
- * A notifications timeline: heading, a row of filter pills, a mode switch, and
153
- * one bordered card whose rows are threaded on a hairline rail. The rail leaves
154
- * each row from its vertical centre, the node is indigo while the row is unread,
155
- * and the unread row carries the brand accent bar at the card's left edge.
168
+ * A notifications timeline: heading with an optional right-hand action slot, a
169
+ * row of filter pills, a mode switch, and one bordered card whose rows are
170
+ * threaded on a hairline rail. The rail leaves each row from its vertical
171
+ * centre, the node is indigo while the row is unread, and the unread row
172
+ * carries the brand accent bar at the card's left edge. Pass `chrome="bare"` to
173
+ * drop the card so the feed fills a Drawer body.
156
174
  */
157
175
  export const NotificationFeed = React.forwardRef<
158
176
  HTMLDivElement,
@@ -162,7 +180,7 @@ export const NotificationFeed = React.forwardRef<
162
180
  {
163
181
  title = "Notification center / feed",
164
182
  filters = defaultFilters,
165
- items = defaultItems,
183
+ items,
166
184
  filter,
167
185
  defaultFilter = "all",
168
186
  onFilterChange,
@@ -172,6 +190,9 @@ export const NotificationFeed = React.forwardRef<
172
190
  onModeChange,
173
191
  feedLabel = "Notifications",
174
192
  emptyMessage = "Nothing here yet.",
193
+ onItemClick,
194
+ actions,
195
+ chrome = "card",
175
196
  className,
176
197
  ...rest
177
198
  },
@@ -196,17 +217,69 @@ export const NotificationFeed = React.forwardRef<
196
217
  onModeChange?.(next);
197
218
  };
198
219
 
199
- const visible = items.filter(
220
+ // An explicitly passed array — including an empty one — always wins, so a
221
+ // consumer with no notifications sees the empty state, never the demo rows.
222
+ const rows = items ?? defaultItems;
223
+
224
+ const visible = rows.filter(
200
225
  (item) =>
201
226
  activeFilter === "all" ||
202
227
  item.filter === undefined ||
203
228
  item.filter === activeFilter,
204
229
  );
205
230
 
231
+ const unreadCount = visible.filter((item) => item.unread).length;
232
+
233
+ const isBare = chrome === "bare";
234
+
235
+ const handleItemClick = (
236
+ item: NotificationFeedItem,
237
+ event: React.MouseEvent<HTMLAnchorElement>,
238
+ ) => {
239
+ if (!onItemClick) return;
240
+ // Let the browser handle new-tab / new-window intents.
241
+ if (
242
+ event.metaKey ||
243
+ event.altKey ||
244
+ event.ctrlKey ||
245
+ event.shiftKey ||
246
+ event.button !== 0
247
+ ) {
248
+ onItemClick(item, event);
249
+ return;
250
+ }
251
+ event.preventDefault();
252
+ onItemClick(item, event);
253
+ };
254
+
206
255
  return (
207
256
  <div ref={ref} className={cn("w-full", className)} {...rest}>
208
257
  <div className="flex w-full flex-col gap-lg">
209
- <h2 className="text-display-lg text-ink">{title}</h2>
258
+ <div className="flex w-full flex-wrap items-start justify-between gap-md">
259
+ <div className="flex min-w-0 items-center gap-sm">
260
+ <h2 className="text-display-lg text-ink">{title}</h2>
261
+ {unreadCount > 0 ? (
262
+ <span
263
+ className={cn(
264
+ "inline-flex shrink-0 items-center justify-center rounded-full bg-primary px-xs text-body-sm text-on-primary",
265
+ )}
266
+ style={tabular}
267
+ >
268
+ {unreadCount}
269
+ <span className="sr-only">
270
+ {" "}
271
+ unread {unreadCount === 1 ? "notification" : "notifications"}
272
+ </span>
273
+ </span>
274
+ ) : null}
275
+ </div>
276
+
277
+ {actions ? (
278
+ <div className="ml-auto flex shrink-0 items-center gap-sm">
279
+ {actions}
280
+ </div>
281
+ ) : null}
282
+ </div>
210
283
 
211
284
  <div className="flex w-full flex-wrap items-center justify-between gap-md">
212
285
  <div
@@ -240,10 +313,20 @@ export const NotificationFeed = React.forwardRef<
240
313
 
241
314
  <section
242
315
  aria-label={feedLabel}
243
- className="overflow-hidden rounded-lg border border-hairline bg-canvas shadow-card"
316
+ className={cn(
317
+ "w-full",
318
+ isBare
319
+ ? "overflow-hidden"
320
+ : "overflow-hidden rounded-lg border border-hairline bg-canvas shadow-card",
321
+ )}
244
322
  >
245
323
  {visible.length === 0 ? (
246
- <p className="px-xxl py-xl text-body-md text-muted">
324
+ <p
325
+ className={cn(
326
+ "text-body-md text-muted",
327
+ isBare ? "py-xl" : "px-xxl py-xl",
328
+ )}
329
+ >
247
330
  {emptyMessage}
248
331
  </p>
249
332
  ) : (
@@ -314,6 +397,7 @@ export const NotificationFeed = React.forwardRef<
314
397
  size="md"
315
398
  underline="always"
316
399
  className="whitespace-nowrap"
400
+ onClick={(event) => handleItemClick(item, event)}
317
401
  >
318
402
  {item.linkLabel ?? "View details"}
319
403
  </Link>
@@ -1,10 +1,11 @@
1
1
  import * as React from "react";
2
+ import { Button } from "../Button";
2
3
  import {
3
4
  NotificationFeed,
4
5
  type NotificationFeedItem,
5
6
  } from "./NotificationFeed";
6
7
 
7
- const items: NotificationFeedItem[] = [
8
+ const seedItems: NotificationFeedItem[] = [
8
9
  {
9
10
  id: "n-1",
10
11
  title: "Payout paid",
@@ -44,15 +45,6 @@ const items: NotificationFeedItem[] = [
44
45
  },
45
46
  {
46
47
  id: "n-5",
47
- title: "Payout paid",
48
- description: "$1,200.00 sent to Acme Store",
49
- timestamp: "Jan 26, 09:12",
50
- href: "#payout-5",
51
- linkLabel: "View payout",
52
- filter: "payouts",
53
- },
54
- {
55
- id: "n-6",
56
48
  title: "Payment received",
57
49
  description: "$1,480.00 from Acme Supply",
58
50
  timestamp: "Jan 26, 08:48",
@@ -62,7 +54,7 @@ const items: NotificationFeedItem[] = [
62
54
  unread: true,
63
55
  },
64
56
  {
65
- id: "n-7",
57
+ id: "n-6",
66
58
  title: "Risk review opened",
67
59
  description: "Case 4821 flagged for manual review",
68
60
  timestamp: "Jan 25, 18:22",
@@ -75,26 +67,81 @@ const items: NotificationFeedItem[] = [
75
67
  export default function NotificationFeedPreview() {
76
68
  const [filter, setFilter] = React.useState("all");
77
69
  const [mode, setMode] = React.useState("live");
70
+ const [drawerFilter, setDrawerFilter] = React.useState("all");
71
+ const [items, setItems] = React.useState(seedItems);
72
+ const [lastOpened, setLastOpened] = React.useState<string | null>(null);
73
+
74
+ const unreadCount = items.filter((item) => item.unread).length;
75
+
76
+ const markRead = (id: string) => {
77
+ setItems((current) =>
78
+ current.map((item) =>
79
+ item.id === id ? { ...item, unread: false } : item,
80
+ ),
81
+ );
82
+ };
83
+
84
+ const markAllRead = () => {
85
+ setItems((current) => current.map((item) => ({ ...item, unread: false })));
86
+ };
78
87
 
79
88
  return (
80
89
  <div className="w-full max-w-5xl mx-auto p-lg space-y-xl">
81
- <NotificationFeed
82
- filter={filter}
83
- onFilterChange={setFilter}
84
- mode={mode}
85
- onModeChange={setMode}
86
- items={items}
87
- />
90
+ <div className="space-y-md">
91
+ <NotificationFeed
92
+ title="Notifications"
93
+ items={items}
94
+ filter={filter}
95
+ onFilterChange={setFilter}
96
+ mode={mode}
97
+ onModeChange={setMode}
98
+ actions={
99
+ <Button
100
+ variant="outline-soft"
101
+ size="sm"
102
+ onClick={markAllRead}
103
+ disabled={unreadCount === 0}
104
+ >
105
+ Mark all read
106
+ </Button>
107
+ }
108
+ onItemClick={(item) => {
109
+ setLastOpened(item.title);
110
+ markRead(item.id);
111
+ }}
112
+ />
113
+ <p aria-live="polite" className="text-body-sm text-muted">
114
+ {lastOpened
115
+ ? `Router would navigate to the ${lastOpened} detail view; the row is marked read.`
116
+ : "Click a row action — it marks that item read instead of navigating."}
117
+ </p>
118
+ </div>
88
119
 
89
120
  <div className="rounded-lg border border-hairline bg-surface-soft p-lg">
90
- <p className="mb-md text-caption font-weight-label uppercase tracking-[0.06em] text-muted">
91
- Empty filter
121
+ <p className="mb-md text-caption font-weight-label text-muted">
122
+ Bare chrome inside a drawer body
123
+ </p>
124
+ <div className="rounded-lg border border-hairline bg-canvas p-lg shadow-card">
125
+ <NotificationFeed
126
+ chrome="bare"
127
+ title="Notifications"
128
+ items={items.slice(0, 3)}
129
+ filter={drawerFilter}
130
+ onFilterChange={setDrawerFilter}
131
+ onItemClick={(item) => markRead(item.id)}
132
+ />
133
+ </div>
134
+ </div>
135
+
136
+ <div className="space-y-md">
137
+ <p className="text-caption font-weight-label text-muted">
138
+ Explicit empty array — demo rows never leak in
92
139
  </p>
93
140
  <NotificationFeed
94
- title="Notification center"
95
- filter="risk"
96
- items={items.filter((item) => item.filter === "payments")}
97
- emptyMessage="No risk reviews in this window."
141
+ chrome="bare"
142
+ title="Notifications"
143
+ items={[]}
144
+ emptyMessage="No notifications yet — payouts and payments land here."
98
145
  />
99
146
  </div>
100
147
  </div>