@remit/ui 0.0.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.
Files changed (188) hide show
  1. package/package.json +50 -0
  2. package/src/components/address-display.render.test.ts +93 -0
  3. package/src/components/address-display.stories.tsx +55 -0
  4. package/src/components/address-display.tsx +132 -0
  5. package/src/components/address-tag.render.test.ts +39 -0
  6. package/src/components/address-tag.stories.tsx +41 -0
  7. package/src/components/address-tag.tsx +38 -0
  8. package/src/components/app-password-hint.render.test.ts +23 -0
  9. package/src/components/app-password-hint.stories.tsx +19 -0
  10. package/src/components/app-password-hint.tsx +24 -0
  11. package/src/components/app-shell-slotted.render.test.ts +128 -0
  12. package/src/components/app-shell-slotted.tsx +267 -0
  13. package/src/components/app-shell-types.ts +328 -0
  14. package/src/components/app-shell.render.test.ts +415 -0
  15. package/src/components/app-shell.tsx +277 -0
  16. package/src/components/auth-card.render.test.ts +24 -0
  17. package/src/components/auth-card.stories.tsx +50 -0
  18. package/src/components/auth-card.tsx +32 -0
  19. package/src/components/auth-footer.render.test.ts +17 -0
  20. package/src/components/auth-footer.stories.tsx +23 -0
  21. package/src/components/auth-footer.tsx +14 -0
  22. package/src/components/auth-hero.render.test.ts +31 -0
  23. package/src/components/auth-hero.stories.tsx +19 -0
  24. package/src/components/auth-hero.tsx +41 -0
  25. package/src/components/auto-moved-badge.render.test.ts +42 -0
  26. package/src/components/auto-moved-badge.stories.tsx +48 -0
  27. package/src/components/auto-moved-badge.tsx +57 -0
  28. package/src/components/avatar.tsx +60 -0
  29. package/src/components/badge.tsx +49 -0
  30. package/src/components/banner.render.test.ts +58 -0
  31. package/src/components/banner.stories.tsx +92 -0
  32. package/src/components/banner.tsx +81 -0
  33. package/src/components/bottom-sheet.render.test.ts +48 -0
  34. package/src/components/bottom-sheet.stories.tsx +71 -0
  35. package/src/components/bottom-sheet.tsx +146 -0
  36. package/src/components/brief-section.render.test.ts +98 -0
  37. package/src/components/brief-section.stories.tsx +68 -0
  38. package/src/components/brief-section.tsx +102 -0
  39. package/src/components/brief-sections.render.test.ts +72 -0
  40. package/src/components/brief-sections.stories.tsx +178 -0
  41. package/src/components/brief-sections.tsx +201 -0
  42. package/src/components/button.tsx +48 -0
  43. package/src/components/card.tsx +48 -0
  44. package/src/components/category-badge.render.test.ts +40 -0
  45. package/src/components/category-badge.stories.tsx +37 -0
  46. package/src/components/category-badge.tsx +71 -0
  47. package/src/components/checkbox.render.test.ts +65 -0
  48. package/src/components/checkbox.stories.tsx +77 -0
  49. package/src/components/checkbox.tsx +78 -0
  50. package/src/components/compose-action-bar.render.test.ts +47 -0
  51. package/src/components/compose-action-bar.stories.tsx +35 -0
  52. package/src/components/compose-action-bar.tsx +95 -0
  53. package/src/components/compose-form-shell.render.test.ts +54 -0
  54. package/src/components/compose-form-shell.stories.tsx +96 -0
  55. package/src/components/compose-form-shell.tsx +49 -0
  56. package/src/components/danger-zone-section.tsx +36 -0
  57. package/src/components/dialog.render.test.ts +55 -0
  58. package/src/components/dialog.tsx +104 -0
  59. package/src/components/email-frame-css.ts +137 -0
  60. package/src/components/field-label.render.test.ts +16 -0
  61. package/src/components/field-label.tsx +19 -0
  62. package/src/components/filter-sheet.render.test.ts +76 -0
  63. package/src/components/filter-sheet.stories.tsx +146 -0
  64. package/src/components/filter-sheet.tsx +316 -0
  65. package/src/components/folder-role.tsx +66 -0
  66. package/src/components/input.tsx +44 -0
  67. package/src/components/intelligence-panel.stories.tsx +149 -0
  68. package/src/components/intelligence-panel.tsx +546 -0
  69. package/src/components/isolated-email-frame.render.test.ts +147 -0
  70. package/src/components/isolated-email-frame.stories.tsx +192 -0
  71. package/src/components/isolated-email-frame.tsx +265 -0
  72. package/src/components/kbd.tsx +17 -0
  73. package/src/components/keyboard-hint-bar.tsx +58 -0
  74. package/src/components/list-item.tsx +39 -0
  75. package/src/components/mail-action-toolbar.render.test.ts +43 -0
  76. package/src/components/mail-action-toolbar.tsx +180 -0
  77. package/src/components/mail-header.render.test.ts +69 -0
  78. package/src/components/mail-header.stories.tsx +233 -0
  79. package/src/components/mail-header.tsx +123 -0
  80. package/src/components/message-body-view.render.test.ts +59 -0
  81. package/src/components/message-body-view.stories.tsx +146 -0
  82. package/src/components/message-body-view.tsx +150 -0
  83. package/src/components/message-header.render.test.ts +58 -0
  84. package/src/components/message-header.stories.tsx +79 -0
  85. package/src/components/message-header.tsx +59 -0
  86. package/src/components/message-list-pane.render.test.ts +136 -0
  87. package/src/components/message-list-pane.stories.tsx +246 -0
  88. package/src/components/message-list-pane.tsx +248 -0
  89. package/src/components/message-list-state.render.test.ts +72 -0
  90. package/src/components/message-list-state.tsx +97 -0
  91. package/src/components/message-row.render.test.ts +48 -0
  92. package/src/components/message-row.stories.tsx +121 -0
  93. package/src/components/message-row.tsx +231 -0
  94. package/src/components/mobile-message-action-bar.render.test.ts +87 -0
  95. package/src/components/mobile-message-action-bar.stories.tsx +57 -0
  96. package/src/components/mobile-message-action-bar.tsx +175 -0
  97. package/src/components/mobile-reading-pane.render.test.ts +102 -0
  98. package/src/components/mobile-reading-pane.tsx +188 -0
  99. package/src/components/mobile-search-view.stories.tsx +247 -0
  100. package/src/components/mobile-search-view.tsx +100 -0
  101. package/src/components/move-mailbox-picker.render.test.ts +113 -0
  102. package/src/components/move-mailbox-picker.stories.tsx +76 -0
  103. package/src/components/move-mailbox-picker.tsx +324 -0
  104. package/src/components/nav-sidebar.render.test.ts +351 -0
  105. package/src/components/nav-sidebar.stories.tsx +216 -0
  106. package/src/components/nav-sidebar.tsx +625 -0
  107. package/src/components/outbox-row.render.test.ts +61 -0
  108. package/src/components/outbox-row.stories.tsx +53 -0
  109. package/src/components/outbox-row.tsx +120 -0
  110. package/src/components/outbox-status-badge.tsx +60 -0
  111. package/src/components/pane-layout.render.test.ts +105 -0
  112. package/src/components/popover-menu.render.test.ts +32 -0
  113. package/src/components/popover-menu.stories.tsx +57 -0
  114. package/src/components/popover-menu.tsx +110 -0
  115. package/src/components/primitives.stories.tsx +173 -0
  116. package/src/components/pull-to-refresh.render.test.ts +46 -0
  117. package/src/components/pull-to-refresh.stories.tsx +71 -0
  118. package/src/components/pull-to-refresh.tsx +54 -0
  119. package/src/components/quoted-text.render.test.ts +39 -0
  120. package/src/components/quoted-text.stories.tsx +67 -0
  121. package/src/components/quoted-text.tsx +67 -0
  122. package/src/components/reading-pane-empty.tsx +44 -0
  123. package/src/components/reading-pane.render.test.ts +148 -0
  124. package/src/components/reading-pane.stories.tsx +194 -0
  125. package/src/components/reading-pane.tsx +351 -0
  126. package/src/components/rescue-banner.render.test.ts +32 -0
  127. package/src/components/rescue-banner.stories.tsx +31 -0
  128. package/src/components/rescue-banner.tsx +50 -0
  129. package/src/components/rescue-candidate-row.render.test.ts +68 -0
  130. package/src/components/rescue-candidate-row.stories.tsx +108 -0
  131. package/src/components/rescue-candidate-row.tsx +94 -0
  132. package/src/components/rescue-from-spam-flow.render.test.ts +100 -0
  133. package/src/components/rescue-from-spam-flow.stories.tsx +92 -0
  134. package/src/components/rescue-from-spam-flow.tsx +342 -0
  135. package/src/components/resizable.tsx +55 -0
  136. package/src/components/role-appointment-list.render.test.ts +97 -0
  137. package/src/components/role-appointment-list.stories.tsx +138 -0
  138. package/src/components/role-appointment-list.tsx +227 -0
  139. package/src/components/row-actions.render.test.ts +83 -0
  140. package/src/components/row-actions.stories.tsx +87 -0
  141. package/src/components/row-actions.tsx +139 -0
  142. package/src/components/search-bar.render.test.ts +55 -0
  143. package/src/components/search-bar.stories.tsx +46 -0
  144. package/src/components/search-bar.tsx +118 -0
  145. package/src/components/search-result-row.tsx +139 -0
  146. package/src/components/search-results.render.test.ts +89 -0
  147. package/src/components/search-results.stories.tsx +126 -0
  148. package/src/components/search-results.tsx +215 -0
  149. package/src/components/search-token-chip.render.test.ts +42 -0
  150. package/src/components/search-token-chip.stories.tsx +42 -0
  151. package/src/components/search-token-chip.tsx +66 -0
  152. package/src/components/security-select.render.test.ts +33 -0
  153. package/src/components/security-select.tsx +53 -0
  154. package/src/components/segmented-control.tsx +70 -0
  155. package/src/components/select.tsx +33 -0
  156. package/src/components/selection-top-bar.render.test.ts +67 -0
  157. package/src/components/selection-top-bar.stories.tsx +39 -0
  158. package/src/components/selection-top-bar.tsx +95 -0
  159. package/src/components/sender-group-switch.tsx +63 -0
  160. package/src/components/sender-trust-indicator.render.test.ts +67 -0
  161. package/src/components/sender-trust-indicator.stories.tsx +58 -0
  162. package/src/components/sender-trust-indicator.tsx +87 -0
  163. package/src/components/settings-primitives.render.test.ts +160 -0
  164. package/src/components/settings-primitives.stories.tsx +80 -0
  165. package/src/components/settings-screen.tsx +354 -0
  166. package/src/components/swipeable-row.render.test.ts +106 -0
  167. package/src/components/swipeable-row.stories.tsx +117 -0
  168. package/src/components/swipeable-row.tsx +268 -0
  169. package/src/components/touch-list-body.render.test.ts +66 -0
  170. package/src/components/touch-list-body.stories.tsx +76 -0
  171. package/src/components/touch-list.tsx +87 -0
  172. package/src/components/wizard.render.test.ts +49 -0
  173. package/src/components/wizard.stories.tsx +113 -0
  174. package/src/components/wizard.tsx +321 -0
  175. package/src/filter-presets.test.ts +93 -0
  176. package/src/filter-presets.ts +101 -0
  177. package/src/index.ts +378 -0
  178. package/src/lib/cid-resolver.ts +35 -0
  179. package/src/lib/cn.ts +7 -0
  180. package/src/lib/email-layout-clamp.test.ts +109 -0
  181. package/src/lib/email-layout-clamp.ts +64 -0
  182. package/src/lib/email-render-treatment.test.ts +82 -0
  183. package/src/lib/email-render-treatment.ts +53 -0
  184. package/src/lib/email-sanitizer.test.ts +362 -0
  185. package/src/lib/email-sanitizer.ts +298 -0
  186. package/src/lib/row-keyboard.test.ts +41 -0
  187. package/src/lib/row-keyboard.ts +20 -0
  188. package/src/tokens.css +217 -0
@@ -0,0 +1,58 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { SenderTrustIndicator } from "./sender-trust-indicator.js";
3
+
4
+ const meta: Meta<typeof SenderTrustIndicator> = {
5
+ title: "Mail/SenderTrustIndicator",
6
+ component: SenderTrustIndicator,
7
+ parameters: { layout: "centered" },
8
+ };
9
+ export default meta;
10
+
11
+ type Story = StoryObj<typeof SenderTrustIndicator>;
12
+
13
+ const Cell = ({
14
+ label,
15
+ children,
16
+ }: {
17
+ label: string;
18
+ children: React.ReactNode;
19
+ }) => (
20
+ <div className="flex items-center gap-2">
21
+ <span className="w-40 text-sm text-fg-muted">{label}</span>
22
+ {children}
23
+ </div>
24
+ );
25
+
26
+ export const HeaderSize: Story = {
27
+ name: "Header (md)",
28
+ render: () => (
29
+ <div className="space-y-2">
30
+ <Cell label="vip">
31
+ <SenderTrustIndicator senderTrust="vip" size="md" />
32
+ </Cell>
33
+ <Cell label="unknown — new sender">
34
+ <SenderTrustIndicator senderTrust="unknown" size="md" />
35
+ </Cell>
36
+ <Cell label="wellknown — silent">
37
+ <SenderTrustIndicator senderTrust="wellknown" size="md" />
38
+ </Cell>
39
+ </div>
40
+ ),
41
+ };
42
+
43
+ export const RowSize: Story = {
44
+ name: "Inbox row (sm)",
45
+ render: () => (
46
+ <div className="space-y-2">
47
+ <Cell label="vip">
48
+ <SenderTrustIndicator senderTrust="vip" size="sm" />
49
+ </Cell>
50
+ <Cell label="unknown — silent on rows">
51
+ <SenderTrustIndicator senderTrust="unknown" size="sm" />
52
+ </Cell>
53
+ <Cell label="wellknown — silent">
54
+ <SenderTrustIndicator senderTrust="wellknown" size="sm" />
55
+ </Cell>
56
+ </div>
57
+ ),
58
+ };
@@ -0,0 +1,87 @@
1
+ import { Sparkles } from "lucide-react";
2
+ import { cn } from "../lib/cn.js";
3
+
4
+ export type SenderTrust = "unknown" | "wellknown" | "vip";
5
+
6
+ export interface SenderTrustIndicatorProps {
7
+ senderTrust: SenderTrust;
8
+ /** Larger size for the open-message header. */
9
+ size?: "sm" | "md";
10
+ className?: string;
11
+ }
12
+
13
+ export type SenderTrustVariant = "vip" | "unknown-pill" | "hidden";
14
+
15
+ /**
16
+ * Decide which visual to render for a given trust level + size.
17
+ *
18
+ * - `vip` → sparkles icon (every size).
19
+ * - `unknown-pill` → "new sender" pill, only at `size="md"` (open-message header).
20
+ * Hidden at `size="sm"` (inbox row): post-rollout, most
21
+ * senders start as `unknown`, so painting a "new" pill on
22
+ * every row would be noisy and defeat the purpose of the
23
+ * signal.
24
+ * - `hidden` → render nothing. Includes `wellknown` at every size and
25
+ * `unknown` at `size="sm"`.
26
+ *
27
+ * Pulled out of the React component so the decision is testable without a
28
+ * DOM renderer.
29
+ */
30
+ export const selectSenderTrustVariant = (
31
+ senderTrust: SenderTrust,
32
+ size: "sm" | "md",
33
+ ): SenderTrustVariant => {
34
+ if (senderTrust === "vip") return "vip";
35
+ if (senderTrust === "unknown" && size === "md") return "unknown-pill";
36
+ return "hidden";
37
+ };
38
+
39
+ /**
40
+ * Inline indicator for the sender's trust gradient.
41
+ *
42
+ * Per EDD #232: trust is a UI hint, not a security boundary. Keep visuals
43
+ * subtle. The `trusted` flag (image rendering) stays orthogonal — handled
44
+ * separately by `AddressDisplay`.
45
+ */
46
+ export const SenderTrustIndicator = ({
47
+ senderTrust,
48
+ size = "sm",
49
+ className,
50
+ }: SenderTrustIndicatorProps) => {
51
+ const variant = selectSenderTrustVariant(senderTrust, size);
52
+
53
+ if (variant === "vip") {
54
+ return (
55
+ <Sparkles
56
+ className={cn(
57
+ "text-warning shrink-0",
58
+ size === "sm" && "size-3.5",
59
+ size === "md" && "size-4",
60
+ className,
61
+ )}
62
+ aria-label="VIP sender"
63
+ data-testid="sender-trust-vip"
64
+ />
65
+ );
66
+ }
67
+
68
+ if (variant === "unknown-pill") {
69
+ return (
70
+ // biome-ignore lint/a11y/useAriaPropsSupportedByRole: aria-label provides useful context for assistive tech despite generic span role
71
+ <span
72
+ className={cn(
73
+ "inline-flex items-center rounded border border-dashed border-fg-subtle/40 font-medium uppercase tracking-wide text-fg-muted/80 shrink-0",
74
+ "px-2 py-0.5 text-xs",
75
+ className,
76
+ )}
77
+ title="First message from this sender"
78
+ aria-label="First message from this sender"
79
+ data-testid="sender-trust-unknown"
80
+ >
81
+ new
82
+ </span>
83
+ );
84
+ }
85
+
86
+ return null;
87
+ };
@@ -0,0 +1,160 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { createElement } from "react";
4
+ import { renderToString } from "react-dom/server";
5
+ import { Button } from "./button.js";
6
+ import { DangerZoneSection } from "./danger-zone-section.js";
7
+ import { SegmentedControl } from "./segmented-control.js";
8
+ import { SenderGroupSwitch } from "./sender-group-switch.js";
9
+ import { SettingsShell } from "./settings-screen.js";
10
+
11
+ describe("SegmentedControl (#802)", () => {
12
+ it("is a radiogroup with one checked radio and thumb-sized targets", () => {
13
+ const html = renderToString(
14
+ createElement(SegmentedControl, {
15
+ name: "density",
16
+ value: "compact",
17
+ onChange: () => undefined,
18
+ options: [
19
+ { value: "comfortable", label: "Comfortable" },
20
+ { value: "compact", label: "Compact" },
21
+ ],
22
+ }),
23
+ );
24
+ assert.match(html, /role="radiogroup"/);
25
+ assert.match(html, /min-h-11/);
26
+ assert.equal((html.match(/type="radio"/g) ?? []).length, 2);
27
+ assert.match(html, /checked[^>]*value="compact"/);
28
+ });
29
+
30
+ it("active thumb uses filled card shadow (#848)", () => {
31
+ const html = renderToString(
32
+ createElement(SegmentedControl, {
33
+ name: "theme",
34
+ value: "system",
35
+ onChange: () => undefined,
36
+ options: [
37
+ { value: "system", label: "System" },
38
+ { value: "light", label: "Light" },
39
+ { value: "dark", label: "Dark" },
40
+ ],
41
+ }),
42
+ );
43
+ // shadow (not shadow-sm) on the active thumb
44
+ assert.match(html, /bg-surface[^"]*\bshadow\b/);
45
+ });
46
+
47
+ it("renders dividers between inactive segments (#848)", () => {
48
+ // active = system (index 0): light→dark boundary gets a divider
49
+ const html = renderToString(
50
+ createElement(SegmentedControl, {
51
+ name: "theme",
52
+ value: "system",
53
+ onChange: () => undefined,
54
+ options: [
55
+ { value: "system", label: "System" },
56
+ { value: "light", label: "Light" },
57
+ { value: "dark", label: "Dark" },
58
+ ],
59
+ }),
60
+ );
61
+ assert.match(html, /after:bg-line/);
62
+ });
63
+ });
64
+
65
+ describe("SenderGroupSwitch (#790)", () => {
66
+ it("renders a tablist that is a strip on mobile and a rail (lg) on desktop", () => {
67
+ const html = renderToString(
68
+ createElement(SenderGroupSwitch, {
69
+ active: "vip",
70
+ onSelect: () => undefined,
71
+ options: [
72
+ { id: "vip", label: "VIPs", count: 12 },
73
+ { id: "muted", label: "Muted", count: null },
74
+ { id: "blocked", label: "Blocked", count: null },
75
+ ],
76
+ }),
77
+ );
78
+ assert.match(html, /role="tablist"/);
79
+ assert.match(html, /lg:w-44/);
80
+ assert.match(html, /lg:flex-col/);
81
+ assert.match(html, /aria-selected="true"/);
82
+ assert.match(html, /—/);
83
+ });
84
+ });
85
+
86
+ describe("DangerZoneSection (#791)", () => {
87
+ it("keeps the strong 'Delete your Remit account' label and renders its action", () => {
88
+ const html = renderToString(
89
+ createElement(DangerZoneSection, {
90
+ title: "Delete your Remit account",
91
+ description: "Disconnects every account.",
92
+ action: createElement(
93
+ Button,
94
+ { variant: "danger", size: "sm" },
95
+ "Delete your Remit account",
96
+ ),
97
+ }),
98
+ );
99
+ assert.equal(
100
+ (html.match(/Delete your Remit account/g) ?? []).length >= 2,
101
+ true,
102
+ );
103
+ assert.match(html, /Danger zone/);
104
+ });
105
+ });
106
+
107
+ describe("SettingsShell responsive (#789)", () => {
108
+ const shell = (flush?: boolean) =>
109
+ renderToString(
110
+ createElement(SettingsShell, {
111
+ items: [
112
+ { id: "accounts", label: "Accounts" },
113
+ { id: "senders", label: "Senders & Rules" },
114
+ ],
115
+ activeId: "accounts",
116
+ title: "Accounts",
117
+ help: createElement("p", null, "tips"),
118
+ flush,
119
+ // biome-ignore lint/correctness/noChildrenProp: React 19 types require children in props object when using createElement
120
+ children: createElement("div", null, "pane body"),
121
+ }),
122
+ );
123
+
124
+ const shellNoHelp = () =>
125
+ renderToString(
126
+ createElement(SettingsShell, {
127
+ items: [
128
+ { id: "accounts", label: "Accounts" },
129
+ { id: "senders", label: "Senders & Rules" },
130
+ ],
131
+ activeId: "accounts",
132
+ title: "Accounts",
133
+ // biome-ignore lint/correctness/noChildrenProp: React 19 types require children in props object when using createElement
134
+ children: createElement("div", null, "pane body"),
135
+ }),
136
+ );
137
+
138
+ it("hides the nav rail below desktop and shows a menu button to reach it", () => {
139
+ const html = shell();
140
+ assert.match(html, /hidden w-60[^"]*lg:flex/);
141
+ assert.match(html, /aria-label="Open settings menu"/);
142
+ });
143
+
144
+ it("folds the tips rail to desktop-only with a mobile tips disclosure", () => {
145
+ const html = shell();
146
+ assert.match(html, /hidden w-64[^"]*lg:flex/);
147
+ assert.match(html, /aria-label="Tips"/);
148
+ });
149
+
150
+ it("renders the active pane content so it is reachable on every width", () => {
151
+ assert.match(shell(), /pane body/);
152
+ assert.match(shell(true), /pane body/);
153
+ });
154
+
155
+ it("omits the tips toggle when no help prop is passed (#872)", () => {
156
+ const html = shellNoHelp();
157
+ assert.doesNotMatch(html, /aria-label="Tips"/);
158
+ assert.doesNotMatch(html, /aria-label="Show tips"/);
159
+ });
160
+ });
@@ -0,0 +1,80 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Button } from "./button.js";
4
+ import { DangerZoneSection } from "./danger-zone-section.js";
5
+ import { SegmentedControl } from "./segmented-control.js";
6
+ import { SenderGroupSwitch } from "./sender-group-switch.js";
7
+
8
+ const meta: Meta = {
9
+ title: "Settings/Primitives",
10
+ };
11
+ export default meta;
12
+
13
+ type Story = StoryObj;
14
+
15
+ export const SegmentedControls: Story = {
16
+ render: () => {
17
+ const [density, setDensity] = useState("comfortable");
18
+ const [theme, setTheme] = useState("system");
19
+ return (
20
+ <div className="flex flex-col gap-6 p-8">
21
+ <SegmentedControl
22
+ name="density"
23
+ aria-label="Density"
24
+ value={density}
25
+ onChange={setDensity}
26
+ options={[
27
+ { value: "comfortable", label: "Comfortable" },
28
+ { value: "compact", label: "Compact" },
29
+ ]}
30
+ />
31
+ <SegmentedControl
32
+ name="theme"
33
+ aria-label="Theme"
34
+ value={theme}
35
+ onChange={setTheme}
36
+ options={[
37
+ { value: "system", label: "System" },
38
+ { value: "light", label: "Light" },
39
+ { value: "dark", label: "Dark" },
40
+ ]}
41
+ />
42
+ </div>
43
+ );
44
+ },
45
+ };
46
+
47
+ export const SenderGroupSwitchRail: Story = {
48
+ render: () => {
49
+ const [active, setActive] = useState("vip");
50
+ return (
51
+ <div className="flex h-96 p-8">
52
+ <SenderGroupSwitch
53
+ active={active}
54
+ onSelect={setActive}
55
+ options={[
56
+ { id: "vip", label: "VIPs", count: 12 },
57
+ { id: "muted", label: "Muted", count: null },
58
+ { id: "blocked", label: "Blocked", count: null },
59
+ ]}
60
+ />
61
+ </div>
62
+ );
63
+ },
64
+ };
65
+
66
+ export const DangerZone: Story = {
67
+ render: () => (
68
+ <div className="max-w-2xl p-8">
69
+ <DangerZoneSection
70
+ title="Delete your Remit account"
71
+ description="Disconnects every account and erases Remit's copy of your mail, insights and preferences. Your mail at the providers is untouched."
72
+ action={
73
+ <Button variant="danger" size="sm">
74
+ Delete your Remit account
75
+ </Button>
76
+ }
77
+ />
78
+ </div>
79
+ ),
80
+ };