@devalok/shilp-sutra 0.42.1 → 0.44.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.
Files changed (130) hide show
  1. package/MIGRATION.md +78 -0
  2. package/dist/_chunks/chat.js +164 -163
  3. package/dist/_chunks/chat.js.map +1 -1
  4. package/dist/_chunks/mention-suggestion.js +35 -33
  5. package/dist/_chunks/mention-suggestion.js.map +1 -1
  6. package/dist/_chunks/oauth-button.js +102 -97
  7. package/dist/_chunks/oauth-button.js.map +1 -1
  8. package/dist/_chunks/popover.js +1 -1
  9. package/dist/_chunks/popover.js.map +1 -1
  10. package/dist/_chunks/shared.js +1 -1
  11. package/dist/_chunks/shared.js.map +1 -1
  12. package/dist/_chunks/tree-view.js +1 -1
  13. package/dist/_chunks/tree-view.js.map +1 -1
  14. package/dist/ai/command-bar.js +1 -1
  15. package/dist/ai/command-bar.js.map +1 -1
  16. package/dist/composed/bulk-action-bar.js +1 -1
  17. package/dist/composed/bulk-action-bar.js.map +1 -1
  18. package/dist/composed/command-palette.js +4 -4
  19. package/dist/composed/command-palette.js.map +1 -1
  20. package/dist/composed/content-card.d.ts +6 -0
  21. package/dist/composed/content-card.d.ts.map +1 -1
  22. package/dist/composed/content-card.js +1 -1
  23. package/dist/composed/content-card.js.map +1 -1
  24. package/dist/composed/error-boundary.js +1 -1
  25. package/dist/composed/error-boundary.js.map +1 -1
  26. package/dist/composed/extensions/emoji-suggestion.js +1 -1
  27. package/dist/composed/extensions/emoji-suggestion.js.map +1 -1
  28. package/dist/composed/extensions/file-attachment.d.ts.map +1 -1
  29. package/dist/composed/file-preview/audio-preview.d.ts.map +1 -1
  30. package/dist/composed/file-preview.d.ts.map +1 -1
  31. package/dist/composed/file-preview.js +147 -143
  32. package/dist/composed/file-preview.js.map +1 -1
  33. package/dist/composed/filter-bar.js +5 -2
  34. package/dist/composed/filter-bar.js.map +1 -1
  35. package/dist/composed/master-detail.js +1 -1
  36. package/dist/composed/master-detail.js.map +1 -1
  37. package/dist/composed/page-header.js +1 -1
  38. package/dist/composed/page-header.js.map +1 -1
  39. package/dist/composed/rich-chat-input/attachment-strip.d.ts.map +1 -1
  40. package/dist/composed/rich-chat-input/audio-player.d.ts.map +1 -1
  41. package/dist/composed/rich-chat-input/schedule-send.d.ts.map +1 -1
  42. package/dist/composed/rich-chat-input.js +600 -591
  43. package/dist/composed/rich-chat-input.js.map +1 -1
  44. package/dist/composed/rich-text-editor.d.ts.map +1 -1
  45. package/dist/composed/rich-text-editor.js +166 -164
  46. package/dist/composed/rich-text-editor.js.map +1 -1
  47. package/dist/hooks/index.d.ts +2 -2
  48. package/dist/hooks/index.d.ts.map +1 -1
  49. package/dist/shell/notification-center.d.ts.map +1 -1
  50. package/dist/shell/notification-center.js +102 -98
  51. package/dist/shell/notification-center.js.map +1 -1
  52. package/dist/shell/sidebar.d.ts +6 -0
  53. package/dist/shell/sidebar.d.ts.map +1 -1
  54. package/dist/shell/sidebar.js +142 -118
  55. package/dist/shell/sidebar.js.map +1 -1
  56. package/dist/shell/top-bar.d.ts.map +1 -1
  57. package/dist/shell/top-bar.js +87 -84
  58. package/dist/shell/top-bar.js.map +1 -1
  59. package/dist/tokens/semantic.css +9 -2
  60. package/dist/ui/alert-dialog.js +1 -1
  61. package/dist/ui/alert-dialog.js.map +1 -1
  62. package/dist/ui/autocomplete.js +1 -1
  63. package/dist/ui/autocomplete.js.map +1 -1
  64. package/dist/ui/avatar.js +1 -1
  65. package/dist/ui/avatar.js.map +1 -1
  66. package/dist/ui/breadcrumb.js +2 -2
  67. package/dist/ui/breadcrumb.js.map +1 -1
  68. package/dist/ui/button.js.map +1 -1
  69. package/dist/ui/card.d.ts +82 -7
  70. package/dist/ui/card.d.ts.map +1 -1
  71. package/dist/ui/card.js +76 -110
  72. package/dist/ui/card.js.map +1 -1
  73. package/dist/ui/chat/message.d.ts.map +1 -1
  74. package/dist/ui/color-input.js +1 -1
  75. package/dist/ui/color-input.js.map +1 -1
  76. package/dist/ui/combobox.js +3 -3
  77. package/dist/ui/combobox.js.map +1 -1
  78. package/dist/ui/context-menu.js +2 -2
  79. package/dist/ui/context-menu.js.map +1 -1
  80. package/dist/ui/data-table-bulk-actions.js +1 -1
  81. package/dist/ui/data-table-bulk-actions.js.map +1 -1
  82. package/dist/ui/dialog.js +1 -1
  83. package/dist/ui/dialog.js.map +1 -1
  84. package/dist/ui/dropdown-menu.js +11 -5
  85. package/dist/ui/dropdown-menu.js.map +1 -1
  86. package/dist/ui/hover-card.js +1 -1
  87. package/dist/ui/hover-card.js.map +1 -1
  88. package/dist/ui/index.d.ts +3 -1
  89. package/dist/ui/index.d.ts.map +1 -1
  90. package/dist/ui/index.js +58 -56
  91. package/dist/ui/index.js.map +1 -1
  92. package/dist/ui/input-otp.js +1 -1
  93. package/dist/ui/input-otp.js.map +1 -1
  94. package/dist/ui/menubar.js +12 -6
  95. package/dist/ui/menubar.js.map +1 -1
  96. package/dist/ui/navigation-menu.js +1 -1
  97. package/dist/ui/navigation-menu.js.map +1 -1
  98. package/dist/ui/oauth-button/oauth-button.d.ts.map +1 -1
  99. package/dist/ui/select.js +6 -3
  100. package/dist/ui/select.js.map +1 -1
  101. package/dist/ui/sidebar.js +74 -74
  102. package/dist/ui/sidebar.js.map +1 -1
  103. package/dist/ui/split-button.js +1 -1
  104. package/dist/ui/split-button.js.map +1 -1
  105. package/dist/ui/stat-card.d.ts +26 -3
  106. package/dist/ui/stat-card.d.ts.map +1 -1
  107. package/dist/ui/stat-card.js +183 -150
  108. package/dist/ui/stat-card.js.map +1 -1
  109. package/dist/ui/stat-flash.d.ts +46 -0
  110. package/dist/ui/stat-flash.d.ts.map +1 -0
  111. package/dist/ui/stat-flash.js +114 -0
  112. package/dist/ui/stat-flash.js.map +1 -0
  113. package/dist/ui/tabs.d.ts.map +1 -1
  114. package/dist/ui/tabs.js +5 -2
  115. package/dist/ui/tabs.js.map +1 -1
  116. package/dist/ui/toast.js +2 -2
  117. package/dist/ui/toast.js.map +1 -1
  118. package/dist/ui/truncated-text.d.ts +37 -0
  119. package/dist/ui/truncated-text.d.ts.map +1 -0
  120. package/dist/ui/truncated-text.js +53 -0
  121. package/dist/ui/truncated-text.js.map +1 -0
  122. package/docs/components/ui/card.md +13 -4
  123. package/docs/components/ui/stat-card.md +18 -3
  124. package/docs/components/ui/stat-flash.md +44 -0
  125. package/docs/components/ui/truncated-text.md +49 -0
  126. package/llms-full.txt +125 -8
  127. package/make-kit/components/card.md +14 -4
  128. package/package.json +1 -1
  129. package/skill/SKILL.md +1 -1
  130. package/skill/references/components-full.md +125 -8
@@ -2,11 +2,12 @@
2
2
  import { cn as e } from "../ui/lib/utils.js";
3
3
  import { t } from "./normalize-icon.js";
4
4
  import { Button as n } from "../ui/button.js";
5
- import * as r from "react";
6
- import { jsx as i, jsxs as a } from "react/jsx-runtime";
7
- import { IconBrandApple as o, IconBrandDiscord as s, IconBrandFacebook as c, IconBrandGithub as l, IconBrandGitlab as u, IconBrandGoogleFilled as d, IconBrandLinkedin as f, IconBrandSlack as p, IconBrandWindows as m, IconBrandXFilled as h, IconFingerprint as g, IconKey as _, IconMail as v } from "@tabler/icons-react";
5
+ import { TruncatedText as r } from "../ui/truncated-text.js";
6
+ import * as i from "react";
7
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
8
+ import { IconBrandApple as s, IconBrandDiscord as c, IconBrandFacebook as l, IconBrandGithub as u, IconBrandGitlab as d, IconBrandGoogleFilled as f, IconBrandLinkedin as p, IconBrandSlack as m, IconBrandWindows as h, IconBrandXFilled as g, IconFingerprint as _, IconKey as v, IconMail as y } from "@tabler/icons-react";
8
9
  //#region src/ui/oauth-button/oauth-button.tsx
9
- var y = {
10
+ var b = {
10
11
  google: "Google",
11
12
  apple: "Apple",
12
13
  github: "GitHub",
@@ -20,45 +21,45 @@ var y = {
20
21
  sso: "SSO",
21
22
  email: "email",
22
23
  passkey: "a passkey"
23
- }, b = {
24
- google: d,
25
- apple: o,
26
- github: l,
27
- microsoft: m,
28
- x: h,
29
- linkedin: f,
30
- facebook: c,
31
- discord: s,
32
- slack: p,
33
- gitlab: u,
34
- sso: _,
35
- email: v,
36
- passkey: g
24
+ }, x = {
25
+ google: f,
26
+ apple: s,
27
+ github: u,
28
+ microsoft: h,
29
+ x: g,
30
+ linkedin: p,
31
+ facebook: l,
32
+ discord: c,
33
+ slack: m,
34
+ gitlab: d,
35
+ sso: v,
36
+ email: y,
37
+ passkey: _
37
38
  };
38
- function x(e) {
39
- return e === "passkey" ? "Passkey" : y[e];
39
+ function S(e) {
40
+ return e === "passkey" ? "Passkey" : b[e];
40
41
  }
41
- function S(e, t, n) {
42
- if (n) return x(e);
43
- let r = y[e];
42
+ function C(e, t, n) {
43
+ if (n) return S(e);
44
+ let r = b[e];
44
45
  switch (t) {
45
46
  case "signin": return `Sign in with ${r}`;
46
47
  case "signup": return `Sign up with ${r}`;
47
48
  default: return `Continue with ${r}`;
48
49
  }
49
50
  }
50
- var C = { passkey: {
51
+ var w = { passkey: {
51
52
  continue: "Continue with a passkey",
52
53
  signin: "Sign in with a passkey",
53
54
  signup: "Create a passkey"
54
55
  } };
55
- function w(e, t, n) {
56
- return n ? x(e) : C[e]?.[t] ?? S(e, t, n);
56
+ function T(e, t, n) {
57
+ return n ? S(e) : w[e]?.[t] ?? C(e, t, n);
57
58
  }
58
- function T(e, t) {
59
- return C[e]?.[t] ?? S(e, t, !1);
59
+ function E(e, t) {
60
+ return w[e]?.[t] ?? C(e, t, !1);
60
61
  }
61
- var E = {
62
+ var D = {
62
63
  xs: 14,
63
64
  sm: 16,
64
65
  md: 18,
@@ -71,7 +72,7 @@ var E = {
71
72
  "icon-sm": 16,
72
73
  "icon-md": 18,
73
74
  "icon-lg": 20
74
- }, D = {
75
+ }, O = {
75
76
  xs: "icon-xs",
76
77
  sm: "icon-sm",
77
78
  md: "icon-md",
@@ -79,18 +80,18 @@ var E = {
79
80
  "compact-xs": "icon-xs",
80
81
  "compact-sm": "icon-sm",
81
82
  "compact-md": "icon-md"
82
- }, O = r.forwardRef(({ provider: r, intent: o = "continue", variant: s, appearance: c, icon: l, iconOnly: u = !1, compact: d = !1, lastUsed: f = !1, lastUsedSlot: p, lastUsedLabel: m = "Last used", helperText: h, children: g, className: _, size: v, fullWidth: y = !1, ...x }, S) => {
83
- let C = v ?? "md", O = b[r], k = E[C] ?? 18, A = t(l) ?? /* @__PURE__ */ i(O, {
83
+ }, k = i.forwardRef(({ provider: r, intent: i = "continue", variant: s, appearance: c, icon: l, iconOnly: u = !1, compact: d = !1, lastUsed: f = !1, lastUsedSlot: p, lastUsedLabel: m = "Last used", helperText: h, children: g, className: _, size: v, fullWidth: y = !1, ...b }, S) => {
84
+ let C = v ?? "md", w = x[r], k = D[C] ?? 18, A = t(l) ?? /* @__PURE__ */ a(w, {
84
85
  size: k,
85
86
  "aria-hidden": !0
86
- }), j = g ?? w(r, o, d), M = s ?? (c === "outline" ? "outline" : "solid"), N = T(r, o), P = u || d || f ? `${N}${f ? " (last used)" : ""}` : void 0, F = /* @__PURE__ */ i("span", {
87
- className: "pointer-events-none inline-flex items-center rounded-pill bg-accent-9 text-accent-fg px-2 py-1 text-[9px] leading-none font-semibold uppercase tracking-wide shadow-overlay",
87
+ }), j = g ?? T(r, i, d), M = s ?? (c === "outline" ? "outline" : "solid"), N = E(r, i), P = u || d || f ? `${N}${f ? " (last used)" : ""}` : void 0, F = /* @__PURE__ */ a("span", {
88
+ className: "pointer-events-none inline-flex items-center rounded-pill bg-accent-9 text-accent-fg px-2 py-1 text-ds-2xs leading-none font-semibold uppercase tracking-wide shadow-overlay",
88
89
  children: m
89
- }), I = (typeof p == "function" ? p({ label: m }) : p) ?? F, L = /* @__PURE__ */ i(n, {
90
+ }), I = (typeof p == "function" ? p({ label: m }) : p) ?? F, L = /* @__PURE__ */ a(n, {
90
91
  ref: S,
91
92
  variant: M,
92
93
  color: "neutral",
93
- size: u ? D[C] ?? "icon-md" : C,
94
+ size: u ? O[C] ?? "icon-md" : C,
94
95
  fullWidth: y && !u,
95
96
  startIcon: u ? void 0 : A,
96
97
  "aria-label": P,
@@ -98,130 +99,134 @@ var E = {
98
99
  "data-oauth-variant": M,
99
100
  "data-last-used": f || void 0,
100
101
  className: _,
101
- ...x,
102
+ ...b,
102
103
  children: u ? A : j
103
104
  }), R = f && !u;
104
- return !R && !h ? L : /* @__PURE__ */ a("div", {
105
+ return !R && !h ? L : /* @__PURE__ */ o("div", {
105
106
  className: e("relative inline-flex flex-col", y && "w-full"),
106
107
  children: [
107
108
  L,
108
- R ? /* @__PURE__ */ i("span", {
109
+ R ? /* @__PURE__ */ a("span", {
109
110
  "aria-hidden": "true",
110
111
  "data-last-used-badge": "",
111
112
  className: "pointer-events-none absolute -top-[10px] -right-2 z-10",
112
113
  children: I
113
114
  }) : null,
114
- h ? /* @__PURE__ */ i("span", {
115
+ h ? /* @__PURE__ */ a("span", {
115
116
  className: "mt-ds-02 text-ds-sm text-surface-fg-subtle",
116
117
  children: h
117
118
  }) : null
118
119
  ]
119
120
  });
120
121
  });
121
- O.displayName = "OAuthButton";
122
- var k = r.forwardRef(({ orientation: t = "vertical", gap: n = "ds-03", fullWidth: a = !0, reorderLastUsedFirst: o = !1, className: s, children: c, ...l }, u) => {
123
- let d = n === "ds-02" ? "gap-ds-02" : n === "ds-04" ? "gap-ds-04" : "gap-ds-03", f = t === "vertical" && a, p = r.useMemo(() => {
122
+ k.displayName = "OAuthButton";
123
+ var A = i.forwardRef(({ orientation: t = "vertical", gap: n = "ds-03", fullWidth: r = !0, reorderLastUsedFirst: o = !1, className: s, children: c, ...l }, u) => {
124
+ let d = n === "ds-02" ? "gap-ds-02" : n === "ds-04" ? "gap-ds-04" : "gap-ds-03", f = t === "vertical" && r, p = i.useMemo(() => {
124
125
  if (!o) return c;
125
- let e = r.Children.toArray(c), t = e.findIndex((e) => r.isValidElement(e) && e.props?.lastUsed);
126
+ let e = i.Children.toArray(c), t = e.findIndex((e) => i.isValidElement(e) && e.props?.lastUsed);
126
127
  if (t <= 0) return c;
127
- let n = [...e], [i] = n.splice(t, 1);
128
- return n.unshift(i), n;
128
+ let n = [...e], [r] = n.splice(t, 1);
129
+ return n.unshift(r), n;
129
130
  }, [c, o]);
130
- return /* @__PURE__ */ i("div", {
131
+ return /* @__PURE__ */ a("div", {
131
132
  ref: u,
132
133
  className: e("flex", t === "vertical" ? "flex-col" : "flex-row flex-wrap items-center", d, f && "w-full [&>*]:w-full [&_button]:w-full", s),
133
134
  ...l,
134
135
  children: p
135
136
  });
136
137
  });
137
- k.displayName = "OAuthGroup";
138
- var A = r.forwardRef(({ label: t = "or", className: n, ...r }, o) => /* @__PURE__ */ a("div", {
139
- ref: o,
138
+ A.displayName = "OAuthGroup";
139
+ var j = i.forwardRef(({ label: t = "or", className: n, ...r }, i) => /* @__PURE__ */ o("div", {
140
+ ref: i,
140
141
  role: "separator",
141
142
  "aria-orientation": "horizontal",
142
143
  className: e("flex items-center gap-ds-03", n),
143
144
  ...r,
144
145
  children: [
145
- /* @__PURE__ */ i("hr", {
146
+ /* @__PURE__ */ a("hr", {
146
147
  "aria-hidden": "true",
147
148
  className: "flex-1 border-t border-surface-border-subtle"
148
149
  }),
149
- /* @__PURE__ */ i("span", {
150
+ /* @__PURE__ */ a("span", {
150
151
  className: "text-ds-sm text-surface-fg-subtle uppercase tracking-wide",
151
152
  children: t
152
153
  }),
153
- /* @__PURE__ */ i("hr", {
154
+ /* @__PURE__ */ a("hr", {
154
155
  "aria-hidden": "true",
155
156
  className: "flex-1 border-t border-surface-border-subtle"
156
157
  })
157
158
  ]
158
159
  }));
159
- A.displayName = "OAuthDivider";
160
- var j = r.forwardRef(({ provider: r, accountLabel: o, connected: s, onAction: c, onActionAsync: l, actionLabel: u, disabled: d, icon: f, className: p, ...m }, h) => {
161
- let g = b[r], _ = y[r], v = u ?? (s ? "Disconnect" : `Connect ${_}`);
162
- return /* @__PURE__ */ a("div", {
163
- ref: h,
164
- className: e("flex items-center justify-between gap-ds-04 py-ds-04 px-ds-04 rounded-surface border border-surface-border bg-surface-raised", p),
165
- "data-provider": r,
166
- "data-connected": s ? "true" : "false",
167
- ...m,
168
- children: [/* @__PURE__ */ a("div", {
160
+ j.displayName = "OAuthDivider";
161
+ var M = i.forwardRef(({ provider: i, accountLabel: s, connected: c, onAction: l, onActionAsync: u, actionLabel: d, disabled: f, icon: p, className: m, ...h }, g) => {
162
+ let _ = x[i], v = b[i], y = d ?? (c ? "Disconnect" : `Connect ${v}`);
163
+ return /* @__PURE__ */ o("div", {
164
+ ref: g,
165
+ className: e("flex items-center justify-between gap-ds-04 py-ds-04 px-ds-04 rounded-surface border border-surface-border bg-surface-raised", m),
166
+ "data-provider": i,
167
+ "data-connected": c ? "true" : "false",
168
+ ...h,
169
+ children: [/* @__PURE__ */ o("div", {
169
170
  className: "flex items-center gap-ds-03 min-w-0",
170
- children: [t(f) ?? /* @__PURE__ */ i(g, {
171
+ children: [t(p) ?? /* @__PURE__ */ a(_, {
171
172
  size: 24,
172
173
  "aria-hidden": !0
173
- }), /* @__PURE__ */ a("div", {
174
+ }), /* @__PURE__ */ o("div", {
174
175
  className: "flex flex-col min-w-0",
175
- children: [/* @__PURE__ */ i("span", {
176
- className: "text-ds-md font-semibold text-surface-fg truncate",
177
- children: _
178
- }), o ? /* @__PURE__ */ i("span", {
176
+ children: [/* @__PURE__ */ a(r, {
177
+ className: "text-ds-md font-semibold text-surface-fg",
178
+ children: v
179
+ }), s ? typeof s == "string" ? /* @__PURE__ */ a(r, {
180
+ mode: "middle",
181
+ className: "text-ds-sm text-surface-fg-muted",
182
+ children: s
183
+ }) : /* @__PURE__ */ a("span", {
179
184
  className: "text-ds-sm text-surface-fg-muted truncate",
180
- children: o
181
- }) : s ? /* @__PURE__ */ i("span", {
185
+ children: s
186
+ }) : c ? /* @__PURE__ */ a("span", {
182
187
  className: "text-ds-sm text-success-11",
183
188
  children: "Connected"
184
- }) : /* @__PURE__ */ i("span", {
189
+ }) : /* @__PURE__ */ a("span", {
185
190
  className: "text-ds-sm text-surface-fg-subtle",
186
191
  children: "Not connected"
187
192
  })]
188
193
  })]
189
- }), /* @__PURE__ */ i(n, {
194
+ }), /* @__PURE__ */ a(n, {
190
195
  size: "sm",
191
- variant: s ? "soft" : "solid",
192
- color: s ? "neutral" : "accent",
193
- disabled: d,
194
- onClick: c,
195
- onClickAsync: l,
196
- children: v
196
+ variant: c ? "soft" : "solid",
197
+ color: c ? "neutral" : "accent",
198
+ disabled: f,
199
+ onClick: l,
200
+ onClickAsync: u,
201
+ children: y
197
202
  })]
198
203
  });
199
204
  });
200
- j.displayName = "OAuthConnectionRow";
201
- var M = r.forwardRef(({ provider: e, size: t = 18, ...n }, r) => {
202
- let a = b[e];
203
- return /* @__PURE__ */ i(a, {
205
+ M.displayName = "OAuthConnectionRow";
206
+ var N = i.forwardRef(({ provider: e, size: t = 18, ...n }, r) => {
207
+ let i = x[e];
208
+ return /* @__PURE__ */ a(i, {
204
209
  ref: r,
205
210
  size: t,
206
211
  "aria-hidden": !0,
207
212
  ...n
208
213
  });
209
214
  });
210
- M.displayName = "OAuthGlyph";
211
- function N(e, t = "continue", n = !1) {
212
- return w(e, t, n);
215
+ N.displayName = "OAuthGlyph";
216
+ function P(e, t = "continue", n = !1) {
217
+ return T(e, t, n);
213
218
  }
214
- function P(e) {
215
- return x(e);
219
+ function F(e) {
220
+ return S(e);
216
221
  }
217
- var F = {
218
- Button: O,
219
- Group: k,
220
- Divider: A,
221
- ConnectionRow: j,
222
- Glyph: M,
223
- getLabel: N,
224
- getName: P
222
+ var I = {
223
+ Button: k,
224
+ Group: A,
225
+ Divider: j,
226
+ ConnectionRow: M,
227
+ Glyph: N,
228
+ getLabel: P,
229
+ getName: F
225
230
  };
226
231
  //#endregion
227
- export { M as a, P as c, A as i, O as n, k as o, j as r, N as s, F as t };
232
+ export { N as a, F as c, j as i, k as n, A as o, M as r, P as s, I as t };
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-button.js","names":[],"sources":["../../src/ui/oauth-button/oauth-button.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconBrandApple,\n IconBrandDiscord,\n IconBrandFacebook,\n IconBrandGithub,\n IconBrandGitlab,\n IconBrandGoogleFilled,\n IconBrandLinkedin,\n IconBrandSlack,\n IconBrandWindows,\n IconBrandXFilled,\n IconFingerprint,\n IconKey,\n IconMail,\n} from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Button, type ButtonProps } from '../button'\nimport type { IconInput } from '../lib/icon-input'\nimport { normalizeIcon } from '../lib/normalize-icon'\nimport { cn } from '../lib/utils'\n\n// ── Types ───────────────────────────────────────────────────────\n\nexport type OAuthProvider =\n | 'google'\n | 'apple'\n | 'github'\n | 'microsoft'\n | 'x'\n | 'linkedin'\n | 'facebook'\n | 'discord'\n | 'slack'\n | 'gitlab'\n | 'sso'\n | 'email'\n | 'passkey'\n\nexport type OAuthIntent = 'continue' | 'signin' | 'signup'\n\n/**\n * Visual treatment — 1:1 with Button's variant axis. OAuthButton is a thin\n * wrapper over Button, so every variant renders pixel-for-pixel identical\n * to a regular Button at the same variant + size. Brand identity lives in\n * the glyph (override via `icon` for a brand-multicolour SVG), not the\n * button surface.\n *\n * - `solid` (default) | `soft` | `outline` | `ghost`\n */\nexport type OAuthVariant = 'solid' | 'soft' | 'outline' | 'ghost'\n\n/** @deprecated Use `OAuthVariant`. brand/dark map to solid; outline passes through. */\nexport type OAuthAppearance = 'brand' | 'outline' | 'dark'\n\n// ── Static metadata ─────────────────────────────────────────────\n\nconst providerDisplay: Record<OAuthProvider, string> = {\n google: 'Google',\n apple: 'Apple',\n github: 'GitHub',\n microsoft: 'Microsoft',\n x: 'X',\n linkedin: 'LinkedIn',\n facebook: 'Facebook',\n discord: 'Discord',\n slack: 'Slack',\n gitlab: 'GitLab',\n sso: 'SSO',\n email: 'email',\n passkey: 'a passkey',\n}\n\n// Default glyph per provider — sourced from Tabler (already a peer dep).\n// Consumer can override with the `icon` prop to drop in a brand-multicolour SVG.\nconst providerIcon: Record<OAuthProvider, React.ComponentType<{ size?: number | string; 'aria-hidden'?: boolean }>> = {\n google: IconBrandGoogleFilled,\n apple: IconBrandApple,\n github: IconBrandGithub,\n microsoft: IconBrandWindows,\n x: IconBrandXFilled,\n linkedin: IconBrandLinkedin,\n facebook: IconBrandFacebook,\n discord: IconBrandDiscord,\n slack: IconBrandSlack,\n gitlab: IconBrandGitlab,\n sso: IconKey,\n email: IconMail,\n passkey: IconFingerprint,\n}\n\nfunction providerName(provider: OAuthProvider): string {\n // Display name for `compact` mode — drops the \"a\" article on passkey.\n return provider === 'passkey' ? 'Passkey' : providerDisplay[provider]\n}\n\nfunction defaultLabel(provider: OAuthProvider, intent: OAuthIntent, compact: boolean): string {\n if (compact) return providerName(provider)\n const name = providerDisplay[provider]\n switch (intent) {\n case 'signin':\n return `Sign in with ${name}`\n case 'signup':\n return `Sign up with ${name}`\n case 'continue':\n default:\n return `Continue with ${name}`\n }\n}\n\nconst providerCopyOverride: Partial<Record<OAuthProvider, Partial<Record<OAuthIntent, string>>>> = {\n passkey: {\n continue: 'Continue with a passkey',\n signin: 'Sign in with a passkey',\n signup: 'Create a passkey',\n },\n}\n\nfunction resolveLabel(provider: OAuthProvider, intent: OAuthIntent, compact: boolean): string {\n if (compact) return providerName(provider)\n return providerCopyOverride[provider]?.[intent] ?? defaultLabel(provider, intent, compact)\n}\n\n/** Long-form name used for aria-label even when compact label is rendered. */\nfunction resolveAriaName(provider: OAuthProvider, intent: OAuthIntent): string {\n return providerCopyOverride[provider]?.[intent] ?? defaultLabel(provider, intent, false)\n}\n\n// **Visual treatment:** OAuthButton is a thin wrapper over `<Button>` — every\n// height, padding, radius, border, hover, focus, active, and disabled state is\n// the DS Button's. We do NOT inject per-provider hex backgrounds: brand\n// identity lives in the glyph (Tabler default; consumer can pass a real\n// multicolour SVG via `icon`). This guarantees pixel-for-pixel consistency\n// with every other button in the system at every variant.\n\nconst ICON_PX: Record<string, number> = {\n xs: 14,\n sm: 16,\n md: 18,\n lg: 20,\n 'compact-xs': 14,\n 'compact-sm': 14,\n 'compact-md': 16,\n icon: 18,\n 'icon-xs': 14,\n 'icon-sm': 16,\n 'icon-md': 18,\n 'icon-lg': 20,\n}\n\nconst ICON_ONLY_SIZE: Record<string, NonNullable<ButtonProps['size']>> = {\n xs: 'icon-xs',\n sm: 'icon-sm',\n md: 'icon-md',\n lg: 'icon-lg',\n 'compact-xs': 'icon-xs',\n 'compact-sm': 'icon-sm',\n 'compact-md': 'icon-md',\n}\n\n// ── Component ───────────────────────────────────────────────────\n\n/**\n * Props for `OAuthButton` — a brand-aware sign-in button for OAuth providers.\n *\n * Composes on top of `<Button>`, inheriting `size`, `loading`, `onClickAsync`,\n * `fullWidth`, and async state machinery. The provider's icon, label, and\n * brand-coloured styling are derived from the `provider` prop.\n *\n * **Provider** (required): includes 10 social providers + 3 generic\n * identifiers (`sso` enterprise, `email` magic-link, `passkey` WebAuthn).\n *\n * **Intent**: drives the verb in the label.\n * - `continue` (default) — \"Continue with Google\" — works on login and signup.\n * - `signin` — \"Sign in with Google\".\n * - `signup` — \"Sign up with Google\".\n *\n * **Appearance**: visual treatment.\n * - `brand` (default) — provider's brand background colour.\n * - `outline` — DS-neutral outline with monochrome glyph. Best for uniform rows.\n * - `dark` — unified Apple-style across all providers for visual consistency.\n *\n * **icon**: override the default Tabler glyph (e.g. drop in the provider's\n * official multicolour SVG downloaded from their brand page).\n *\n * **iconOnly**: render a square icon button. Provider name kept in `aria-label`.\n *\n * **lastUsed**: shows a small \"Last used\" pill above the button — high-converting\n * UX hint. Consumer tracks which provider the user picked previously.\n *\n * **helperText**: optional reassurance copy rendered below the button.\n *\n * @example\n * <OAuthButton provider=\"google\" intent=\"continue\" fullWidth onClickAsync={signInWithGoogle} />\n *\n * @example\n * // True brand-multicolour glyph:\n * <OAuthButton provider=\"google\" icon={<GoogleGSvg />} />\n *\n * @example\n * <OAuthGroup>\n * <OAuthButton provider=\"google\" appearance=\"dark\" />\n * <OAuthButton provider=\"apple\" appearance=\"dark\" />\n * <OAuthButton provider=\"github\" appearance=\"dark\" />\n * </OAuthGroup>\n */\nexport interface OAuthButtonProps\n extends Omit<\n ButtonProps,\n 'variant' | 'color' | 'startIcon' | 'endIcon' | 'shape' | 'children'\n > {\n /** OAuth provider this button represents */\n provider: OAuthProvider\n /** Verb in the default label. Default: `continue` */\n intent?: OAuthIntent\n /**\n * Visual treatment. Default: `solid`. Mirrors Button's variant axis with\n * an extra `dark` for row coherence across providers.\n */\n variant?: OAuthVariant\n /** @deprecated Use `variant`. `appearance=\"brand\"` maps to `variant=\"solid\"`. */\n appearance?: OAuthAppearance\n /** Override the default glyph (e.g. brand-multicolour SVG). Accepts any `IconInput`. */\n icon?: IconInput\n /** Render a compact icon-only button (provider name kept in aria-label) */\n iconOnly?: boolean\n /**\n * Short label — renders just the provider name (\"Google\") instead of the\n * full \"Continue with Google\". Use under an explicit \"Or sign in with\"\n * divider where the verb is already established. aria-label still gets the\n * full long form for screen readers.\n */\n compact?: boolean\n /**\n * Show \"Last used\" hint — a small pill anchored to the top-right corner of\n * the button (sits ON the corner, half outside the button bounds).\n */\n lastUsed?: boolean\n /**\n * Customise the \"Last used\" badge. Pass a ReactNode to replace the entire\n * pill (use this for custom copy, icons, or styling), or a render fn that\n * receives the default props for tweaks. Only rendered when `lastUsed` is\n * also set so the wrapper / aria augmentation logic stays consistent.\n *\n * @example\n * <OAuthButton provider=\"google\" lastUsed lastUsedSlot={<MyBadge />} />\n */\n lastUsedSlot?: React.ReactNode | ((defaults: { label: string }) => React.ReactNode)\n /** Override the visible badge label text. Default: \"Last used\". */\n lastUsedLabel?: string\n /** Reassurance copy rendered below the button */\n helperText?: React.ReactNode\n /** Override the default label entirely (i18n / custom copy) */\n children?: React.ReactNode\n}\n\nconst OAuthButton = React.forwardRef<HTMLButtonElement, OAuthButtonProps>(\n (\n {\n provider,\n intent = 'continue',\n variant: variantProp,\n appearance,\n icon,\n iconOnly = false,\n compact = false,\n lastUsed = false,\n lastUsedSlot,\n lastUsedLabel = 'Last used',\n helperText,\n children,\n className,\n size: sizeProp,\n fullWidth = false,\n ...buttonProps\n },\n ref,\n ) => {\n const size: NonNullable<ButtonProps['size']> = sizeProp ?? 'md'\n const DefaultGlyph = providerIcon[provider]\n const iconPx = ICON_PX[size] ?? 18\n const glyphNode = normalizeIcon(icon) ?? <DefaultGlyph size={iconPx} aria-hidden />\n const label = children ?? resolveLabel(provider, intent, compact)\n\n // Resolve final variant. `appearance` is a deprecated alias mapped to\n // the closest DS variant — brand/dark both collapse to solid since\n // OAuthButton no longer injects per-provider brand colours.\n const variant: OAuthVariant =\n variantProp ??\n (appearance === 'outline' ? 'outline' : 'solid')\n\n // aria-label always carries the long-form name so screen readers stay\n // informative even when the visual label is compact or icon-only.\n const longName = resolveAriaName(provider, intent)\n const ariaLabelForButton = iconOnly || compact || lastUsed\n ? `${longName}${lastUsed ? ' (last used)' : ''}`\n : undefined\n\n // \"Last used\" badge — sits ON the top-right corner of the button,\n // overlapping the button edge for the classic \"notification badge\"\n // look. Rendered as a SIBLING to Button (not a child) because Button's\n // `overflow-hidden` would clip a child poking outside the corner.\n //\n // Composable: pass `lastUsedSlot` (ReactNode or render fn) to replace\n // the default pill. `lastUsedLabel` swaps just the text inside it.\n // aria-label augments the accessible name with \"(last used)\".\n const defaultBadge = (\n <span className=\"pointer-events-none inline-flex items-center rounded-pill bg-accent-9 text-accent-fg px-2 py-1 text-[9px] leading-none font-semibold uppercase tracking-wide shadow-overlay\">\n {lastUsedLabel}\n </span>\n )\n const customBadge =\n typeof lastUsedSlot === 'function' ? lastUsedSlot({ label: lastUsedLabel }) : lastUsedSlot\n const renderedBadge = customBadge ?? defaultBadge\n\n const buttonEl = (\n <Button\n ref={ref}\n variant={variant}\n color=\"neutral\"\n size={iconOnly ? ICON_ONLY_SIZE[size] ?? 'icon-md' : size}\n fullWidth={fullWidth && !iconOnly}\n startIcon={iconOnly ? undefined : (glyphNode as React.ReactElement)}\n aria-label={ariaLabelForButton}\n data-provider={provider}\n data-oauth-variant={variant}\n data-last-used={lastUsed || undefined}\n className={className}\n {...buttonProps}\n >\n {iconOnly ? glyphNode : label}\n </Button>\n )\n\n const showBadge = lastUsed && !iconOnly\n if (!showBadge && !helperText) return buttonEl\n\n return (\n <div className={cn('relative inline-flex flex-col', fullWidth && 'w-full')}>\n {buttonEl}\n {showBadge ? (\n <span\n aria-hidden=\"true\"\n data-last-used-badge=\"\"\n className=\"pointer-events-none absolute -top-[10px] -right-2 z-10\"\n >\n {renderedBadge}\n </span>\n ) : null}\n {helperText ? (\n <span className=\"mt-ds-02 text-ds-sm text-surface-fg-subtle\">{helperText}</span>\n ) : null}\n </div>\n )\n },\n)\nOAuthButton.displayName = 'OAuthButton'\n\nexport { OAuthButton }\n\n// ── OAuthGroup ─────────────────────────────────────────────────\n\nexport interface OAuthGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Layout. `vertical` (default) stacks full-width buttons. */\n orientation?: 'vertical' | 'horizontal'\n /** Gap token between buttons. Default: `ds-03` */\n gap?: 'ds-02' | 'ds-03' | 'ds-04'\n /** Stretch children to fill width (vertical only). Default: true */\n fullWidth?: boolean\n /**\n * Move any child with `lastUsed` to position 0. Stripe-style ordering — the\n * provider the user picked last time becomes the top option, which gives a\n * stronger conversion lift than a \"Last used\" badge does on its own.\n * Default: false (preserve source order).\n */\n reorderLastUsedFirst?: boolean\n}\n\n/**\n * Stacked layout wrapper for OAuth buttons with consistent spacing. Defaults to\n * vertical, full-width children — the dominant sign-in pattern. For an icon-only\n * row, set `orientation=\"horizontal\"` and use `<OAuthButton iconOnly />`.\n */\nconst OAuthGroup = React.forwardRef<HTMLDivElement, OAuthGroupProps>(\n (\n {\n orientation = 'vertical',\n gap = 'ds-03',\n fullWidth = true,\n reorderLastUsedFirst = false,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const gapClass =\n gap === 'ds-02' ? 'gap-ds-02' : gap === 'ds-04' ? 'gap-ds-04' : 'gap-ds-03'\n const stretch = orientation === 'vertical' && fullWidth\n\n const ordered = React.useMemo(() => {\n if (!reorderLastUsedFirst) return children\n const arr = React.Children.toArray(children)\n const lastUsedIdx = arr.findIndex(\n (c) => React.isValidElement(c) && (c.props as { lastUsed?: boolean })?.lastUsed,\n )\n if (lastUsedIdx <= 0) return children\n const next = [...arr]\n const [pulled] = next.splice(lastUsedIdx, 1)\n next.unshift(pulled)\n return next\n }, [children, reorderLastUsedFirst])\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex',\n orientation === 'vertical' ? 'flex-col' : 'flex-row flex-wrap items-center',\n gapClass,\n // Force both the direct wrapper AND the leaf <button> to stretch.\n // OAuthButton's outer wrapper picks up [&>*]:w-full; the button itself\n // sits two layers deep when a helperText wrapper exists, so\n // [&_button]:w-full covers both cases (wrapped + unwrapped) without\n // affecting unrelated descendants — only Buttons live inside.\n stretch && 'w-full [&>*]:w-full [&_button]:w-full',\n className,\n )}\n {...props}\n >\n {ordered}\n </div>\n )\n },\n)\nOAuthGroup.displayName = 'OAuthGroup'\n\nexport { OAuthGroup }\n\n// ── OAuthDivider ───────────────────────────────────────────────\n\nexport interface OAuthDividerProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Text between the rules. Default: \"or\" */\n label?: React.ReactNode\n}\n\nconst OAuthDivider = React.forwardRef<HTMLDivElement, OAuthDividerProps>(\n ({ label = 'or', className, ...props }, ref) => (\n <div\n ref={ref}\n role=\"separator\"\n aria-orientation=\"horizontal\"\n className={cn('flex items-center gap-ds-03', className)}\n {...props}\n >\n <hr aria-hidden=\"true\" className=\"flex-1 border-t border-surface-border-subtle\" />\n <span className=\"text-ds-sm text-surface-fg-subtle uppercase tracking-wide\">{label}</span>\n <hr aria-hidden=\"true\" className=\"flex-1 border-t border-surface-border-subtle\" />\n </div>\n ),\n)\nOAuthDivider.displayName = 'OAuthDivider'\n\nexport { OAuthDivider }\n\n// ── OAuthConnectionRow (settings page — linked state) ──────────\n\nexport interface OAuthConnectionRowProps extends React.HTMLAttributes<HTMLDivElement> {\n provider: OAuthProvider\n /** Optional account identifier shown next to the provider name (e.g. email) */\n accountLabel?: React.ReactNode\n /** Whether the provider is currently linked */\n connected: boolean\n /** Click handler for the connect/disconnect action */\n onAction?: (e: React.MouseEvent<HTMLButtonElement>) => void\n /** Promise-returning click handler — uses Button's async state machine */\n onActionAsync?: (e: React.MouseEvent<HTMLButtonElement>) => Promise<void>\n /** Override the action label */\n actionLabel?: React.ReactNode\n /** Disable the action button */\n disabled?: boolean\n /** Override the default glyph. Accepts any `IconInput`. */\n icon?: IconInput\n}\n\n/**\n * Settings-page row representing a linked OAuth provider. Different surface\n * from sign-in: this is an existing connection the user can disconnect or\n * (re-)connect. Renders glyph + provider name + optional account label + a\n * connect/disconnect button.\n */\nconst OAuthConnectionRow = React.forwardRef<HTMLDivElement, OAuthConnectionRowProps>(\n (\n {\n provider,\n accountLabel,\n connected,\n onAction,\n onActionAsync,\n actionLabel,\n disabled,\n icon,\n className,\n ...props\n },\n ref,\n ) => {\n const DefaultGlyph = providerIcon[provider]\n const name = providerDisplay[provider]\n const resolvedLabel = actionLabel ?? (connected ? 'Disconnect' : `Connect ${name}`)\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex items-center justify-between gap-ds-04 py-ds-04 px-ds-04 rounded-surface border border-surface-border bg-surface-raised',\n className,\n )}\n data-provider={provider}\n data-connected={connected ? 'true' : 'false'}\n {...props}\n >\n <div className=\"flex items-center gap-ds-03 min-w-0\">\n {normalizeIcon(icon) ?? <DefaultGlyph size={24} aria-hidden />}\n <div className=\"flex flex-col min-w-0\">\n <span className=\"text-ds-md font-semibold text-surface-fg truncate\">{name}</span>\n {accountLabel ? (\n <span className=\"text-ds-sm text-surface-fg-muted truncate\">{accountLabel}</span>\n ) : connected ? (\n <span className=\"text-ds-sm text-success-11\">Connected</span>\n ) : (\n <span className=\"text-ds-sm text-surface-fg-subtle\">Not connected</span>\n )}\n </div>\n </div>\n <Button\n size=\"sm\"\n variant={connected ? 'soft' : 'solid'}\n color={connected ? 'neutral' : 'accent'}\n disabled={disabled}\n onClick={onAction}\n onClickAsync={onActionAsync}\n >\n {resolvedLabel}\n </Button>\n </div>\n )\n },\n)\nOAuthConnectionRow.displayName = 'OAuthConnectionRow'\n\nexport { OAuthConnectionRow }\n\n// ── OAuthGlyph (atomic glyph render) ───────────────────────────\n\nexport interface OAuthGlyphProps extends Omit<React.SVGAttributes<SVGElement>, 'children'> {\n provider: OAuthProvider\n /** Glyph pixel size. Default 18. */\n size?: number\n}\n\n/**\n * Render just the provider glyph — useful when building a custom OAuth\n * trigger (e.g. an icon-button inside a Tooltip, or a provider chip on a\n * profile card). Defaults to the Tabler glyph; the icon prop on\n * `OAuthButton` is the right escape hatch for the brand multi-colour mark.\n */\nconst OAuthGlyph = React.forwardRef<SVGSVGElement, OAuthGlyphProps>(\n ({ provider, size = 18, ...props }, ref) => {\n const Cmp = providerIcon[provider] as React.ComponentType<{\n size?: number\n 'aria-hidden'?: boolean\n ref?: React.Ref<SVGSVGElement>\n }>\n return <Cmp ref={ref} size={size} aria-hidden {...(props as Record<string, unknown>)} />\n },\n)\nOAuthGlyph.displayName = 'OAuthGlyph'\n\nexport { OAuthGlyph }\n\n// ── Static helpers (composable building blocks) ────────────────\n\n/**\n * Resolve the visible label for a provider × intent combo. Useful when\n * rendering OAuth UIs outside of `OAuthButton` (e.g. a Confirm dialog\n * \"Disconnect from Google?\" title).\n */\nexport function getOAuthLabel(provider: OAuthProvider, intent: OAuthIntent = 'continue', compact = false): string {\n return resolveLabel(provider, intent, compact)\n}\n\n/** Display name for a provider, ready for sentence interpolation. */\nexport function getOAuthName(provider: OAuthProvider): string {\n return providerName(provider)\n}\n\n// ── Compound namespace ─────────────────────────────────────────\n\n/**\n * Namespaced API — drop-in alternative to importing each piece separately.\n *\n * @example\n * import { OAuth } from '@devalok/shilp-sutra/ui/oauth-button'\n *\n * <OAuth.Group reorderLastUsedFirst>\n * <OAuth.Button provider=\"google\" lastUsed />\n * <OAuth.Button provider=\"passkey\" />\n * <OAuth.Divider />\n * <OAuth.Button provider=\"sso\" variant=\"ghost\" />\n * </OAuth.Group>\n */\nexport const OAuth = {\n Button: OAuthButton,\n Group: OAuthGroup,\n Divider: OAuthDivider,\n ConnectionRow: OAuthConnectionRow,\n Glyph: OAuthGlyph,\n getLabel: getOAuthLabel,\n getName: getOAuthName,\n} as const\n"],"mappings":"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"}
1
+ {"version":3,"file":"oauth-button.js","names":[],"sources":["../../src/ui/oauth-button/oauth-button.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconBrandApple,\n IconBrandDiscord,\n IconBrandFacebook,\n IconBrandGithub,\n IconBrandGitlab,\n IconBrandGoogleFilled,\n IconBrandLinkedin,\n IconBrandSlack,\n IconBrandWindows,\n IconBrandXFilled,\n IconFingerprint,\n IconKey,\n IconMail,\n} from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Button, type ButtonProps } from '../button'\nimport type { IconInput } from '../lib/icon-input'\nimport { normalizeIcon } from '../lib/normalize-icon'\nimport { cn } from '../lib/utils'\nimport { TruncatedText } from '../truncated-text'\n\n// ── Types ───────────────────────────────────────────────────────\n\nexport type OAuthProvider =\n | 'google'\n | 'apple'\n | 'github'\n | 'microsoft'\n | 'x'\n | 'linkedin'\n | 'facebook'\n | 'discord'\n | 'slack'\n | 'gitlab'\n | 'sso'\n | 'email'\n | 'passkey'\n\nexport type OAuthIntent = 'continue' | 'signin' | 'signup'\n\n/**\n * Visual treatment — 1:1 with Button's variant axis. OAuthButton is a thin\n * wrapper over Button, so every variant renders pixel-for-pixel identical\n * to a regular Button at the same variant + size. Brand identity lives in\n * the glyph (override via `icon` for a brand-multicolour SVG), not the\n * button surface.\n *\n * - `solid` (default) | `soft` | `outline` | `ghost`\n */\nexport type OAuthVariant = 'solid' | 'soft' | 'outline' | 'ghost'\n\n/** @deprecated Use `OAuthVariant`. brand/dark map to solid; outline passes through. */\nexport type OAuthAppearance = 'brand' | 'outline' | 'dark'\n\n// ── Static metadata ─────────────────────────────────────────────\n\nconst providerDisplay: Record<OAuthProvider, string> = {\n google: 'Google',\n apple: 'Apple',\n github: 'GitHub',\n microsoft: 'Microsoft',\n x: 'X',\n linkedin: 'LinkedIn',\n facebook: 'Facebook',\n discord: 'Discord',\n slack: 'Slack',\n gitlab: 'GitLab',\n sso: 'SSO',\n email: 'email',\n passkey: 'a passkey',\n}\n\n// Default glyph per provider — sourced from Tabler (already a peer dep).\n// Consumer can override with the `icon` prop to drop in a brand-multicolour SVG.\nconst providerIcon: Record<OAuthProvider, React.ComponentType<{ size?: number | string; 'aria-hidden'?: boolean }>> = {\n google: IconBrandGoogleFilled,\n apple: IconBrandApple,\n github: IconBrandGithub,\n microsoft: IconBrandWindows,\n x: IconBrandXFilled,\n linkedin: IconBrandLinkedin,\n facebook: IconBrandFacebook,\n discord: IconBrandDiscord,\n slack: IconBrandSlack,\n gitlab: IconBrandGitlab,\n sso: IconKey,\n email: IconMail,\n passkey: IconFingerprint,\n}\n\nfunction providerName(provider: OAuthProvider): string {\n // Display name for `compact` mode — drops the \"a\" article on passkey.\n return provider === 'passkey' ? 'Passkey' : providerDisplay[provider]\n}\n\nfunction defaultLabel(provider: OAuthProvider, intent: OAuthIntent, compact: boolean): string {\n if (compact) return providerName(provider)\n const name = providerDisplay[provider]\n switch (intent) {\n case 'signin':\n return `Sign in with ${name}`\n case 'signup':\n return `Sign up with ${name}`\n case 'continue':\n default:\n return `Continue with ${name}`\n }\n}\n\nconst providerCopyOverride: Partial<Record<OAuthProvider, Partial<Record<OAuthIntent, string>>>> = {\n passkey: {\n continue: 'Continue with a passkey',\n signin: 'Sign in with a passkey',\n signup: 'Create a passkey',\n },\n}\n\nfunction resolveLabel(provider: OAuthProvider, intent: OAuthIntent, compact: boolean): string {\n if (compact) return providerName(provider)\n return providerCopyOverride[provider]?.[intent] ?? defaultLabel(provider, intent, compact)\n}\n\n/** Long-form name used for aria-label even when compact label is rendered. */\nfunction resolveAriaName(provider: OAuthProvider, intent: OAuthIntent): string {\n return providerCopyOverride[provider]?.[intent] ?? defaultLabel(provider, intent, false)\n}\n\n// **Visual treatment:** OAuthButton is a thin wrapper over `<Button>` — every\n// height, padding, radius, border, hover, focus, active, and disabled state is\n// the DS Button's. We do NOT inject per-provider hex backgrounds: brand\n// identity lives in the glyph (Tabler default; consumer can pass a real\n// multicolour SVG via `icon`). This guarantees pixel-for-pixel consistency\n// with every other button in the system at every variant.\n\nconst ICON_PX: Record<string, number> = {\n xs: 14,\n sm: 16,\n md: 18,\n lg: 20,\n 'compact-xs': 14,\n 'compact-sm': 14,\n 'compact-md': 16,\n icon: 18,\n 'icon-xs': 14,\n 'icon-sm': 16,\n 'icon-md': 18,\n 'icon-lg': 20,\n}\n\nconst ICON_ONLY_SIZE: Record<string, NonNullable<ButtonProps['size']>> = {\n xs: 'icon-xs',\n sm: 'icon-sm',\n md: 'icon-md',\n lg: 'icon-lg',\n 'compact-xs': 'icon-xs',\n 'compact-sm': 'icon-sm',\n 'compact-md': 'icon-md',\n}\n\n// ── Component ───────────────────────────────────────────────────\n\n/**\n * Props for `OAuthButton` — a brand-aware sign-in button for OAuth providers.\n *\n * Composes on top of `<Button>`, inheriting `size`, `loading`, `onClickAsync`,\n * `fullWidth`, and async state machinery. The provider's icon, label, and\n * brand-coloured styling are derived from the `provider` prop.\n *\n * **Provider** (required): includes 10 social providers + 3 generic\n * identifiers (`sso` enterprise, `email` magic-link, `passkey` WebAuthn).\n *\n * **Intent**: drives the verb in the label.\n * - `continue` (default) — \"Continue with Google\" — works on login and signup.\n * - `signin` — \"Sign in with Google\".\n * - `signup` — \"Sign up with Google\".\n *\n * **Appearance**: visual treatment.\n * - `brand` (default) — provider's brand background colour.\n * - `outline` — DS-neutral outline with monochrome glyph. Best for uniform rows.\n * - `dark` — unified Apple-style across all providers for visual consistency.\n *\n * **icon**: override the default Tabler glyph (e.g. drop in the provider's\n * official multicolour SVG downloaded from their brand page).\n *\n * **iconOnly**: render a square icon button. Provider name kept in `aria-label`.\n *\n * **lastUsed**: shows a small \"Last used\" pill above the button — high-converting\n * UX hint. Consumer tracks which provider the user picked previously.\n *\n * **helperText**: optional reassurance copy rendered below the button.\n *\n * @example\n * <OAuthButton provider=\"google\" intent=\"continue\" fullWidth onClickAsync={signInWithGoogle} />\n *\n * @example\n * // True brand-multicolour glyph:\n * <OAuthButton provider=\"google\" icon={<GoogleGSvg />} />\n *\n * @example\n * <OAuthGroup>\n * <OAuthButton provider=\"google\" appearance=\"dark\" />\n * <OAuthButton provider=\"apple\" appearance=\"dark\" />\n * <OAuthButton provider=\"github\" appearance=\"dark\" />\n * </OAuthGroup>\n */\nexport interface OAuthButtonProps\n extends Omit<\n ButtonProps,\n 'variant' | 'color' | 'startIcon' | 'endIcon' | 'shape' | 'children'\n > {\n /** OAuth provider this button represents */\n provider: OAuthProvider\n /** Verb in the default label. Default: `continue` */\n intent?: OAuthIntent\n /**\n * Visual treatment. Default: `solid`. Mirrors Button's variant axis with\n * an extra `dark` for row coherence across providers.\n */\n variant?: OAuthVariant\n /** @deprecated Use `variant`. `appearance=\"brand\"` maps to `variant=\"solid\"`. */\n appearance?: OAuthAppearance\n /** Override the default glyph (e.g. brand-multicolour SVG). Accepts any `IconInput`. */\n icon?: IconInput\n /** Render a compact icon-only button (provider name kept in aria-label) */\n iconOnly?: boolean\n /**\n * Short label — renders just the provider name (\"Google\") instead of the\n * full \"Continue with Google\". Use under an explicit \"Or sign in with\"\n * divider where the verb is already established. aria-label still gets the\n * full long form for screen readers.\n */\n compact?: boolean\n /**\n * Show \"Last used\" hint — a small pill anchored to the top-right corner of\n * the button (sits ON the corner, half outside the button bounds).\n */\n lastUsed?: boolean\n /**\n * Customise the \"Last used\" badge. Pass a ReactNode to replace the entire\n * pill (use this for custom copy, icons, or styling), or a render fn that\n * receives the default props for tweaks. Only rendered when `lastUsed` is\n * also set so the wrapper / aria augmentation logic stays consistent.\n *\n * @example\n * <OAuthButton provider=\"google\" lastUsed lastUsedSlot={<MyBadge />} />\n */\n lastUsedSlot?: React.ReactNode | ((defaults: { label: string }) => React.ReactNode)\n /** Override the visible badge label text. Default: \"Last used\". */\n lastUsedLabel?: string\n /** Reassurance copy rendered below the button */\n helperText?: React.ReactNode\n /** Override the default label entirely (i18n / custom copy) */\n children?: React.ReactNode\n}\n\nconst OAuthButton = React.forwardRef<HTMLButtonElement, OAuthButtonProps>(\n (\n {\n provider,\n intent = 'continue',\n variant: variantProp,\n appearance,\n icon,\n iconOnly = false,\n compact = false,\n lastUsed = false,\n lastUsedSlot,\n lastUsedLabel = 'Last used',\n helperText,\n children,\n className,\n size: sizeProp,\n fullWidth = false,\n ...buttonProps\n },\n ref,\n ) => {\n const size: NonNullable<ButtonProps['size']> = sizeProp ?? 'md'\n const DefaultGlyph = providerIcon[provider]\n const iconPx = ICON_PX[size] ?? 18\n const glyphNode = normalizeIcon(icon) ?? <DefaultGlyph size={iconPx} aria-hidden />\n const label = children ?? resolveLabel(provider, intent, compact)\n\n // Resolve final variant. `appearance` is a deprecated alias mapped to\n // the closest DS variant — brand/dark both collapse to solid since\n // OAuthButton no longer injects per-provider brand colours.\n const variant: OAuthVariant =\n variantProp ??\n (appearance === 'outline' ? 'outline' : 'solid')\n\n // aria-label always carries the long-form name so screen readers stay\n // informative even when the visual label is compact or icon-only.\n const longName = resolveAriaName(provider, intent)\n const ariaLabelForButton = iconOnly || compact || lastUsed\n ? `${longName}${lastUsed ? ' (last used)' : ''}`\n : undefined\n\n // \"Last used\" badge — sits ON the top-right corner of the button,\n // overlapping the button edge for the classic \"notification badge\"\n // look. Rendered as a SIBLING to Button (not a child) because Button's\n // `overflow-hidden` would clip a child poking outside the corner.\n //\n // Composable: pass `lastUsedSlot` (ReactNode or render fn) to replace\n // the default pill. `lastUsedLabel` swaps just the text inside it.\n // aria-label augments the accessible name with \"(last used)\".\n // intentional: a 9px corner pill, smaller than Badge's smallest size (xs).\n // Not worth a sub-xs Badge size for one call site — kept as a documented exception.\n const defaultBadge = (\n <span className=\"pointer-events-none inline-flex items-center rounded-pill bg-accent-9 text-accent-fg px-2 py-1 text-ds-2xs leading-none font-semibold uppercase tracking-wide shadow-overlay\">\n {lastUsedLabel}\n </span>\n )\n const customBadge =\n typeof lastUsedSlot === 'function' ? lastUsedSlot({ label: lastUsedLabel }) : lastUsedSlot\n const renderedBadge = customBadge ?? defaultBadge\n\n const buttonEl = (\n <Button\n ref={ref}\n variant={variant}\n color=\"neutral\"\n size={iconOnly ? ICON_ONLY_SIZE[size] ?? 'icon-md' : size}\n fullWidth={fullWidth && !iconOnly}\n startIcon={iconOnly ? undefined : (glyphNode as React.ReactElement)}\n aria-label={ariaLabelForButton}\n data-provider={provider}\n data-oauth-variant={variant}\n data-last-used={lastUsed || undefined}\n className={className}\n {...buttonProps}\n >\n {iconOnly ? glyphNode : label}\n </Button>\n )\n\n const showBadge = lastUsed && !iconOnly\n if (!showBadge && !helperText) return buttonEl\n\n return (\n <div className={cn('relative inline-flex flex-col', fullWidth && 'w-full')}>\n {buttonEl}\n {showBadge ? (\n <span\n aria-hidden=\"true\"\n data-last-used-badge=\"\"\n className=\"pointer-events-none absolute -top-[10px] -right-2 z-10\"\n >\n {renderedBadge}\n </span>\n ) : null}\n {helperText ? (\n <span className=\"mt-ds-02 text-ds-sm text-surface-fg-subtle\">{helperText}</span>\n ) : null}\n </div>\n )\n },\n)\nOAuthButton.displayName = 'OAuthButton'\n\nexport { OAuthButton }\n\n// ── OAuthGroup ─────────────────────────────────────────────────\n\nexport interface OAuthGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Layout. `vertical` (default) stacks full-width buttons. */\n orientation?: 'vertical' | 'horizontal'\n /** Gap token between buttons. Default: `ds-03` */\n gap?: 'ds-02' | 'ds-03' | 'ds-04'\n /** Stretch children to fill width (vertical only). Default: true */\n fullWidth?: boolean\n /**\n * Move any child with `lastUsed` to position 0. Stripe-style ordering — the\n * provider the user picked last time becomes the top option, which gives a\n * stronger conversion lift than a \"Last used\" badge does on its own.\n * Default: false (preserve source order).\n */\n reorderLastUsedFirst?: boolean\n}\n\n/**\n * Stacked layout wrapper for OAuth buttons with consistent spacing. Defaults to\n * vertical, full-width children — the dominant sign-in pattern. For an icon-only\n * row, set `orientation=\"horizontal\"` and use `<OAuthButton iconOnly />`.\n */\nconst OAuthGroup = React.forwardRef<HTMLDivElement, OAuthGroupProps>(\n (\n {\n orientation = 'vertical',\n gap = 'ds-03',\n fullWidth = true,\n reorderLastUsedFirst = false,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const gapClass =\n gap === 'ds-02' ? 'gap-ds-02' : gap === 'ds-04' ? 'gap-ds-04' : 'gap-ds-03'\n const stretch = orientation === 'vertical' && fullWidth\n\n const ordered = React.useMemo(() => {\n if (!reorderLastUsedFirst) return children\n const arr = React.Children.toArray(children)\n const lastUsedIdx = arr.findIndex(\n (c) => React.isValidElement(c) && (c.props as { lastUsed?: boolean })?.lastUsed,\n )\n if (lastUsedIdx <= 0) return children\n const next = [...arr]\n const [pulled] = next.splice(lastUsedIdx, 1)\n next.unshift(pulled)\n return next\n }, [children, reorderLastUsedFirst])\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex',\n orientation === 'vertical' ? 'flex-col' : 'flex-row flex-wrap items-center',\n gapClass,\n // Force both the direct wrapper AND the leaf <button> to stretch.\n // OAuthButton's outer wrapper picks up [&>*]:w-full; the button itself\n // sits two layers deep when a helperText wrapper exists, so\n // [&_button]:w-full covers both cases (wrapped + unwrapped) without\n // affecting unrelated descendants — only Buttons live inside.\n stretch && 'w-full [&>*]:w-full [&_button]:w-full',\n className,\n )}\n {...props}\n >\n {ordered}\n </div>\n )\n },\n)\nOAuthGroup.displayName = 'OAuthGroup'\n\nexport { OAuthGroup }\n\n// ── OAuthDivider ───────────────────────────────────────────────\n\nexport interface OAuthDividerProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Text between the rules. Default: \"or\" */\n label?: React.ReactNode\n}\n\nconst OAuthDivider = React.forwardRef<HTMLDivElement, OAuthDividerProps>(\n ({ label = 'or', className, ...props }, ref) => (\n <div\n ref={ref}\n role=\"separator\"\n aria-orientation=\"horizontal\"\n className={cn('flex items-center gap-ds-03', className)}\n {...props}\n >\n <hr aria-hidden=\"true\" className=\"flex-1 border-t border-surface-border-subtle\" />\n <span className=\"text-ds-sm text-surface-fg-subtle uppercase tracking-wide\">{label}</span>\n <hr aria-hidden=\"true\" className=\"flex-1 border-t border-surface-border-subtle\" />\n </div>\n ),\n)\nOAuthDivider.displayName = 'OAuthDivider'\n\nexport { OAuthDivider }\n\n// ── OAuthConnectionRow (settings page — linked state) ──────────\n\nexport interface OAuthConnectionRowProps extends React.HTMLAttributes<HTMLDivElement> {\n provider: OAuthProvider\n /** Optional account identifier shown next to the provider name (e.g. email) */\n accountLabel?: React.ReactNode\n /** Whether the provider is currently linked */\n connected: boolean\n /** Click handler for the connect/disconnect action */\n onAction?: (e: React.MouseEvent<HTMLButtonElement>) => void\n /** Promise-returning click handler — uses Button's async state machine */\n onActionAsync?: (e: React.MouseEvent<HTMLButtonElement>) => Promise<void>\n /** Override the action label */\n actionLabel?: React.ReactNode\n /** Disable the action button */\n disabled?: boolean\n /** Override the default glyph. Accepts any `IconInput`. */\n icon?: IconInput\n}\n\n/**\n * Settings-page row representing a linked OAuth provider. Different surface\n * from sign-in: this is an existing connection the user can disconnect or\n * (re-)connect. Renders glyph + provider name + optional account label + a\n * connect/disconnect button.\n */\nconst OAuthConnectionRow = React.forwardRef<HTMLDivElement, OAuthConnectionRowProps>(\n (\n {\n provider,\n accountLabel,\n connected,\n onAction,\n onActionAsync,\n actionLabel,\n disabled,\n icon,\n className,\n ...props\n },\n ref,\n ) => {\n const DefaultGlyph = providerIcon[provider]\n const name = providerDisplay[provider]\n const resolvedLabel = actionLabel ?? (connected ? 'Disconnect' : `Connect ${name}`)\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex items-center justify-between gap-ds-04 py-ds-04 px-ds-04 rounded-surface border border-surface-border bg-surface-raised',\n className,\n )}\n data-provider={provider}\n data-connected={connected ? 'true' : 'false'}\n {...props}\n >\n <div className=\"flex items-center gap-ds-03 min-w-0\">\n {normalizeIcon(icon) ?? <DefaultGlyph size={24} aria-hidden />}\n <div className=\"flex flex-col min-w-0\">\n <TruncatedText className=\"text-ds-md font-semibold text-surface-fg\">{name}</TruncatedText>\n {accountLabel ? (\n // Account labels are usually an email — middle-truncate to keep the domain.\n // Only a string can be measured/truncated; fall back to a plain clip otherwise.\n typeof accountLabel === 'string' ? (\n <TruncatedText mode=\"middle\" className=\"text-ds-sm text-surface-fg-muted\">\n {accountLabel}\n </TruncatedText>\n ) : (\n <span className=\"text-ds-sm text-surface-fg-muted truncate\">{accountLabel}</span>\n )\n ) : connected ? (\n <span className=\"text-ds-sm text-success-11\">Connected</span>\n ) : (\n <span className=\"text-ds-sm text-surface-fg-subtle\">Not connected</span>\n )}\n </div>\n </div>\n <Button\n size=\"sm\"\n variant={connected ? 'soft' : 'solid'}\n color={connected ? 'neutral' : 'accent'}\n disabled={disabled}\n onClick={onAction}\n onClickAsync={onActionAsync}\n >\n {resolvedLabel}\n </Button>\n </div>\n )\n },\n)\nOAuthConnectionRow.displayName = 'OAuthConnectionRow'\n\nexport { OAuthConnectionRow }\n\n// ── OAuthGlyph (atomic glyph render) ───────────────────────────\n\nexport interface OAuthGlyphProps extends Omit<React.SVGAttributes<SVGElement>, 'children'> {\n provider: OAuthProvider\n /** Glyph pixel size. Default 18. */\n size?: number\n}\n\n/**\n * Render just the provider glyph — useful when building a custom OAuth\n * trigger (e.g. an icon-button inside a Tooltip, or a provider chip on a\n * profile card). Defaults to the Tabler glyph; the icon prop on\n * `OAuthButton` is the right escape hatch for the brand multi-colour mark.\n */\nconst OAuthGlyph = React.forwardRef<SVGSVGElement, OAuthGlyphProps>(\n ({ provider, size = 18, ...props }, ref) => {\n const Cmp = providerIcon[provider] as React.ComponentType<{\n size?: number\n 'aria-hidden'?: boolean\n ref?: React.Ref<SVGSVGElement>\n }>\n return <Cmp ref={ref} size={size} aria-hidden {...(props as Record<string, unknown>)} />\n },\n)\nOAuthGlyph.displayName = 'OAuthGlyph'\n\nexport { OAuthGlyph }\n\n// ── Static helpers (composable building blocks) ────────────────\n\n/**\n * Resolve the visible label for a provider × intent combo. Useful when\n * rendering OAuth UIs outside of `OAuthButton` (e.g. a Confirm dialog\n * \"Disconnect from Google?\" title).\n */\nexport function getOAuthLabel(provider: OAuthProvider, intent: OAuthIntent = 'continue', compact = false): string {\n return resolveLabel(provider, intent, compact)\n}\n\n/** Display name for a provider, ready for sentence interpolation. */\nexport function getOAuthName(provider: OAuthProvider): string {\n return providerName(provider)\n}\n\n// ── Compound namespace ─────────────────────────────────────────\n\n/**\n * Namespaced API — drop-in alternative to importing each piece separately.\n *\n * @example\n * import { OAuth } from '@devalok/shilp-sutra/ui/oauth-button'\n *\n * <OAuth.Group reorderLastUsedFirst>\n * <OAuth.Button provider=\"google\" lastUsed />\n * <OAuth.Button provider=\"passkey\" />\n * <OAuth.Divider />\n * <OAuth.Button provider=\"sso\" variant=\"ghost\" />\n * </OAuth.Group>\n */\nexport const OAuth = {\n Button: OAuthButton,\n Group: OAuthGroup,\n Divider: OAuthDivider,\n ConnectionRow: OAuthConnectionRow,\n Glyph: OAuthGlyph,\n getLabel: getOAuthLabel,\n getName: getOAuthName,\n} as const\n"],"mappings":";;;;;;;;AA4DA,IAAM,IAAiD;CACrD,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,WAAW;CACX,GAAG;CACH,UAAU;CACV,UAAU;CACV,SAAS;CACT,OAAO;CACP,QAAQ;CACR,KAAK;CACL,OAAO;CACP,SAAS;CACV,EAIK,IAAgH;CACpH,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,WAAW;CACX,GAAG;CACH,UAAU;CACV,UAAU;CACV,SAAS;CACT,OAAO;CACP,QAAQ;CACR,KAAK;CACL,OAAO;CACP,SAAS;CACV;AAED,SAAS,EAAa,GAAiC;AAErD,QAAO,MAAa,YAAY,YAAY,EAAgB;;AAG9D,SAAS,EAAa,GAAyB,GAAqB,GAA0B;AAC5F,KAAI,EAAS,QAAO,EAAa,EAAS;CAC1C,IAAM,IAAO,EAAgB;AAC7B,SAAQ,GAAR;EACE,KAAK,SACH,QAAO,gBAAgB;EACzB,KAAK,SACH,QAAO,gBAAgB;EAEzB,QACE,QAAO,iBAAiB;;;AAI9B,IAAM,IAA6F,EACjG,SAAS;CACP,UAAU;CACV,QAAQ;CACR,QAAQ;CACT,EACF;AAED,SAAS,EAAa,GAAyB,GAAqB,GAA0B;AAE5F,QADI,IAAgB,EAAa,EAAS,GACnC,EAAqB,KAAY,MAAW,EAAa,GAAU,GAAQ,EAAQ;;AAI5F,SAAS,EAAgB,GAAyB,GAA6B;AAC7E,QAAO,EAAqB,KAAY,MAAW,EAAa,GAAU,GAAQ,GAAM;;AAU1F,IAAM,IAAkC;CACtC,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,cAAc;CACd,cAAc;CACd,cAAc;CACd,MAAM;CACN,WAAW;CACX,WAAW;CACX,WAAW;CACX,WAAW;CACZ,EAEK,IAAmE;CACvE,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,cAAc;CACd,cAAc;CACd,cAAc;CACf,EAkGK,IAAc,EAAM,YAEtB,EACE,aACA,YAAS,YACT,SAAS,GACT,eACA,SACA,cAAW,IACX,aAAU,IACV,cAAW,IACX,iBACA,mBAAgB,aAChB,eACA,aACA,cACA,MAAM,GACN,eAAY,IACZ,GAAG,KAEL,MACG;CACH,IAAM,IAAyC,KAAY,MACrD,IAAe,EAAa,IAC5B,IAAS,EAAQ,MAAS,IAC1B,IAAY,EAAc,EAAK,IAAI,kBAAC,GAAD;EAAc,MAAM;EAAQ,eAAA;EAAc,CAAA,EAC7E,IAAQ,KAAY,EAAa,GAAU,GAAQ,EAAQ,EAK3D,IACJ,MACC,MAAe,YAAY,YAAY,UAIpC,IAAW,EAAgB,GAAU,EAAO,EAC5C,IAAqB,KAAY,KAAW,IAC9C,GAAG,IAAW,IAAW,iBAAiB,OAC1C,KAAA,GAYE,IACJ,kBAAC,QAAD;EAAM,WAAU;YACb;EACI,CAAA,EAIH,KADJ,OAAO,KAAiB,aAAa,EAAa,EAAE,OAAO,GAAe,CAAC,GAAG,MAC3C,GAE/B,IACJ,kBAAC,GAAD;EACO;EACI;EACT,OAAM;EACN,MAAM,IAAW,EAAe,MAAS,YAAY;EACrD,WAAW,KAAa,CAAC;EACzB,WAAW,IAAW,KAAA,IAAa;EACnC,cAAY;EACZ,iBAAe;EACf,sBAAoB;EACpB,kBAAgB,KAAY,KAAA;EACjB;EACX,GAAI;YAEH,IAAW,IAAY;EACjB,CAAA,EAGL,IAAY,KAAY,CAAC;AAG/B,QAFI,CAAC,KAAa,CAAC,IAAmB,IAGpC,kBAAC,OAAD;EAAK,WAAW,EAAG,iCAAiC,KAAa,SAAS;YAA1E;GACG;GACA,IACC,kBAAC,QAAD;IACE,eAAY;IACZ,wBAAqB;IACrB,WAAU;cAET;IACI,CAAA,GACL;GACH,IACC,kBAAC,QAAD;IAAM,WAAU;cAA8C;IAAkB,CAAA,GAC9E;GACA;;EAGX;AACD,EAAY,cAAc;AA2B1B,IAAM,IAAa,EAAM,YAErB,EACE,iBAAc,YACd,SAAM,SACN,eAAY,IACZ,0BAAuB,IACvB,cACA,aACA,GAAG,KAEL,MACG;CACH,IAAM,IACJ,MAAQ,UAAU,cAAc,MAAQ,UAAU,cAAc,aAC5D,IAAU,MAAgB,cAAc,GAExC,IAAU,EAAM,cAAc;AAClC,MAAI,CAAC,EAAsB,QAAO;EAClC,IAAM,IAAM,EAAM,SAAS,QAAQ,EAAS,EACtC,IAAc,EAAI,WACrB,MAAM,EAAM,eAAe,EAAE,IAAK,EAAE,OAAkC,SACxE;AACD,MAAI,KAAe,EAAG,QAAO;EAC7B,IAAM,IAAO,CAAC,GAAG,EAAI,EACf,CAAC,KAAU,EAAK,OAAO,GAAa,EAAE;AAE5C,SADA,EAAK,QAAQ,EAAO,EACb;IACN,CAAC,GAAU,EAAqB,CAAC;AAEpC,QACE,kBAAC,OAAD;EACO;EACL,WAAW,EACT,QACA,MAAgB,aAAa,aAAa,mCAC1C,GAMA,KAAW,yCACX,EACD;EACD,GAAI;YAEH;EACG,CAAA;EAGX;AACD,EAAW,cAAc;AAWzB,IAAM,IAAe,EAAM,YACxB,EAAE,WAAQ,MAAM,cAAW,GAAG,KAAS,MACtC,kBAAC,OAAD;CACO;CACL,MAAK;CACL,oBAAiB;CACjB,WAAW,EAAG,+BAA+B,EAAU;CACvD,GAAI;WALN;EAOE,kBAAC,MAAD;GAAI,eAAY;GAAO,WAAU;GAAiD,CAAA;EAClF,kBAAC,QAAD;GAAM,WAAU;aAA6D;GAAa,CAAA;EAC1F,kBAAC,MAAD;GAAI,eAAY;GAAO,WAAU;GAAiD,CAAA;EAC9E;GAET;AACD,EAAa,cAAc;AA8B3B,IAAM,IAAqB,EAAM,YAE7B,EACE,aACA,iBACA,cACA,aACA,kBACA,gBACA,aACA,SACA,cACA,GAAG,KAEL,MACG;CACH,IAAM,IAAe,EAAa,IAC5B,IAAO,EAAgB,IACvB,IAAgB,MAAgB,IAAY,eAAe,WAAW;AAE5E,QACE,kBAAC,OAAD;EACO;EACL,WAAW,EACT,gIACA,EACD;EACD,iBAAe;EACf,kBAAgB,IAAY,SAAS;EACrC,GAAI;YARN,CAUE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,EAAc,EAAK,IAAI,kBAAC,GAAD;IAAc,MAAM;IAAI,eAAA;IAAc,CAAA,EAC9D,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAe,WAAU;eAA4C;KAAqB,CAAA,EACzF,IAGC,OAAO,KAAiB,WACtB,kBAAC,GAAD;KAAe,MAAK;KAAS,WAAU;eACpC;KACa,CAAA,GAEhB,kBAAC,QAAD;KAAM,WAAU;eAA6C;KAAoB,CAAA,GAEjF,IACF,kBAAC,QAAD;KAAM,WAAU;eAA6B;KAAgB,CAAA,GAE7D,kBAAC,QAAD;KAAM,WAAU;eAAoC;KAAoB,CAAA,CAEtE;MACF;MACN,kBAAC,GAAD;GACE,MAAK;GACL,SAAS,IAAY,SAAS;GAC9B,OAAO,IAAY,YAAY;GACrB;GACV,SAAS;GACT,cAAc;aAEb;GACM,CAAA,CACL;;EAGX;AACD,EAAmB,cAAc;AAkBjC,IAAM,IAAa,EAAM,YACtB,EAAE,aAAU,UAAO,IAAI,GAAG,KAAS,MAAQ;CAC1C,IAAM,IAAM,EAAa;AAKzB,QAAO,kBAAC,GAAD;EAAU;EAAW;EAAM,eAAA;EAAY,GAAK;EAAqC,CAAA;EAE3F;AACD,EAAW,cAAc;AAWzB,SAAgB,EAAc,GAAyB,IAAsB,YAAY,IAAU,IAAe;AAChH,QAAO,EAAa,GAAU,GAAQ,EAAQ;;AAIhD,SAAgB,EAAa,GAAiC;AAC5D,QAAO,EAAa,EAAS;;AAkB/B,IAAa,IAAQ;CACnB,QAAQ;CACR,OAAO;CACP,SAAS;CACT,eAAe;CACf,OAAO;CACP,UAAU;CACV,SAAS;CACV"}
@@ -112,7 +112,7 @@ var S = a, C = n, w = p.forwardRef(({ className: e, align: t = "center", sideOff
112
112
  ...l.snappy,
113
113
  opacity: u.fade
114
114
  },
115
- className: d("z-popover w-72 rounded-overlay border border-surface-border-strong bg-surface-overlay p-ds-05 text-surface-fg shadow-floating outline-hidden", e),
115
+ className: d("z-popover w-72 rounded-overlay bg-surface-overlay p-ds-05 text-surface-fg shadow-floating outline-hidden", e),
116
116
  children: r
117
117
  })
118
118
  })
@@ -1 +1 @@
1
- {"version":3,"file":"popover.js","names":[],"sources":["../../src/ui/lib/bottom-sheet.tsx","../../src/ui/popover.tsx"],"sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@primitives/react-dialog'\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { springs, tweens } from './motion'\nimport { cn } from './utils'\n\nexport interface BottomSheetProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n children: React.ReactNode\n className?: string\n /** Show drag handle bar at top. @default true */\n dragHandle?: boolean\n /** Allow swipe-to-dismiss. @default true */\n swipeable?: boolean\n /** Title for accessibility (sets aria-label on the dialog). */\n title?: string\n}\n\nexport function BottomSheet({\n open,\n onOpenChange,\n children,\n className,\n dragHandle = true,\n swipeable = true,\n title,\n}: BottomSheetProps) {\n const isReduced = useReducedMotion()\n const sheetRef = React.useRef<HTMLDivElement>(null)\n\n const handleDragEnd = React.useCallback(\n (_: unknown, info: { offset: { y: number }; velocity: { y: number } }) => {\n const sheetHeight = sheetRef.current?.getBoundingClientRect().height ?? 300\n if (info.offset.y > sheetHeight * 0.3 || info.velocity.y > 500) {\n onOpenChange(false)\n }\n },\n [onOpenChange],\n )\n\n return (\n <DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>\n <AnimatePresence>\n {open && (\n <DialogPrimitive.Portal forceMount>\n <DialogPrimitive.Overlay forceMount asChild>\n <motion.div\n className=\"fixed inset-0 z-modal bg-overlay\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n />\n </DialogPrimitive.Overlay>\n\n <DialogPrimitive.Content\n forceMount\n asChild\n aria-label={title}\n >\n <motion.div\n ref={sheetRef}\n className={cn(\n 'fixed inset-x-0 bottom-0 z-modal max-h-[85vh] overflow-y-auto rounded-t-overlay-lg border-t border-surface-border-strong bg-surface-overlay shadow-overlay outline-hidden',\n className,\n )}\n initial={{ y: '100%' }}\n animate={{ y: 0 }}\n exit={{ y: '100%' }}\n transition={isReduced ? { duration: 0 } : springs.smooth}\n drag={swipeable && !isReduced ? 'y' : false}\n dragConstraints={{ top: 0 }}\n dragElastic={0.2}\n onDragEnd={handleDragEnd}\n >\n {dragHandle && (\n <div className=\"flex justify-center pt-ds-03 pb-ds-02\">\n <div className=\"h-1 w-8 rounded-pill bg-surface-border\" />\n </div>\n )}\n <div className=\"px-ds-05 pb-ds-06\">\n {children}\n </div>\n </motion.div>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n )}\n </AnimatePresence>\n </DialogPrimitive.Root>\n )\n}\n","'use client'\n\nimport * as PopoverPrimitive from '@primitives/react-popover'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useIsMobile } from '../hooks/use-mobile'\nimport { BottomSheet } from './lib/bottom-sheet'\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\n\n// ── Internal context to thread `open` state to animated children ──\n\nconst PopoverOpenContext = React.createContext<{ open: boolean; onClose: () => void }>({ open: false, onClose: () => {} })\n\nconst Popover: React.FC<React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Root>> = ({\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n ...props\n}) => {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)\n const isControlled = controlledOpen !== undefined\n const open = isControlled ? controlledOpen : uncontrolledOpen\n\n const handleOpenChange = React.useCallback(\n (value: boolean) => {\n if (!isControlled) setUncontrolledOpen(value)\n onOpenChange?.(value)\n },\n [isControlled, onOpenChange],\n )\n\n const contextValue = React.useMemo(\n () => ({ open, onClose: () => handleOpenChange(false) }),\n [open, handleOpenChange],\n )\n\n return (\n <PopoverOpenContext.Provider value={contextValue}>\n <PopoverPrimitive.Root open={open} onOpenChange={handleOpenChange} {...props} />\n </PopoverOpenContext.Provider>\n )\n}\nPopover.displayName = 'Popover'\n\nconst PopoverTrigger = PopoverPrimitive.Trigger\n\nconst PopoverAnchor = PopoverPrimitive.Anchor\n\nconst PopoverContent = React.forwardRef<\n React.ElementRef<typeof PopoverPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = 'center', sideOffset = 4, children, ...props }, ref) => {\n const isMobileRaw = useIsMobile()\n const { open, onClose } = React.useContext(PopoverOpenContext)\n\n if (isMobileRaw) {\n return (\n <BottomSheet\n open={open}\n onOpenChange={(v) => { if (!v) onClose() }}\n title=\"Options\"\n className={className}\n >\n {children}\n </BottomSheet>\n )\n }\n\n return (\n <AnimatePresence>\n {open && (\n <PopoverPrimitive.Portal forceMount>\n <PopoverPrimitive.Content\n ref={ref}\n align={align}\n sideOffset={sideOffset}\n forceMount\n asChild\n {...props}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ ...springs.snappy, opacity: tweens.fade }}\n className={cn(\n 'z-popover w-72 rounded-overlay border border-surface-border-strong bg-surface-overlay p-ds-05 text-surface-fg shadow-floating outline-hidden',\n className,\n )}\n >\n {children}\n </motion.div>\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n )}\n </AnimatePresence>\n )\n})\nPopoverContent.displayName = PopoverPrimitive.Content.displayName\n\nexport type PopoverContentProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n\nexport { Popover, PopoverAnchor,PopoverContent, PopoverTrigger }\n"],"mappings":"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"}
1
+ {"version":3,"file":"popover.js","names":[],"sources":["../../src/ui/lib/bottom-sheet.tsx","../../src/ui/popover.tsx"],"sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@primitives/react-dialog'\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { springs, tweens } from './motion'\nimport { cn } from './utils'\n\nexport interface BottomSheetProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n children: React.ReactNode\n className?: string\n /** Show drag handle bar at top. @default true */\n dragHandle?: boolean\n /** Allow swipe-to-dismiss. @default true */\n swipeable?: boolean\n /** Title for accessibility (sets aria-label on the dialog). */\n title?: string\n}\n\nexport function BottomSheet({\n open,\n onOpenChange,\n children,\n className,\n dragHandle = true,\n swipeable = true,\n title,\n}: BottomSheetProps) {\n const isReduced = useReducedMotion()\n const sheetRef = React.useRef<HTMLDivElement>(null)\n\n const handleDragEnd = React.useCallback(\n (_: unknown, info: { offset: { y: number }; velocity: { y: number } }) => {\n const sheetHeight = sheetRef.current?.getBoundingClientRect().height ?? 300\n if (info.offset.y > sheetHeight * 0.3 || info.velocity.y > 500) {\n onOpenChange(false)\n }\n },\n [onOpenChange],\n )\n\n return (\n <DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>\n <AnimatePresence>\n {open && (\n <DialogPrimitive.Portal forceMount>\n <DialogPrimitive.Overlay forceMount asChild>\n <motion.div\n className=\"fixed inset-0 z-modal bg-overlay\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n />\n </DialogPrimitive.Overlay>\n\n <DialogPrimitive.Content\n forceMount\n asChild\n aria-label={title}\n >\n <motion.div\n ref={sheetRef}\n className={cn(\n 'fixed inset-x-0 bottom-0 z-modal max-h-[85vh] overflow-y-auto rounded-t-overlay-lg border-t border-surface-border-strong bg-surface-overlay shadow-overlay outline-hidden',\n className,\n )}\n initial={{ y: '100%' }}\n animate={{ y: 0 }}\n exit={{ y: '100%' }}\n transition={isReduced ? { duration: 0 } : springs.smooth}\n drag={swipeable && !isReduced ? 'y' : false}\n dragConstraints={{ top: 0 }}\n dragElastic={0.2}\n onDragEnd={handleDragEnd}\n >\n {dragHandle && (\n <div className=\"flex justify-center pt-ds-03 pb-ds-02\">\n <div className=\"h-1 w-8 rounded-pill bg-surface-border\" />\n </div>\n )}\n <div className=\"px-ds-05 pb-ds-06\">\n {children}\n </div>\n </motion.div>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n )}\n </AnimatePresence>\n </DialogPrimitive.Root>\n )\n}\n","'use client'\n\nimport * as PopoverPrimitive from '@primitives/react-popover'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useIsMobile } from '../hooks/use-mobile'\nimport { BottomSheet } from './lib/bottom-sheet'\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\n\n// ── Internal context to thread `open` state to animated children ──\n\nconst PopoverOpenContext = React.createContext<{ open: boolean; onClose: () => void }>({ open: false, onClose: () => {} })\n\nconst Popover: React.FC<React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Root>> = ({\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n ...props\n}) => {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)\n const isControlled = controlledOpen !== undefined\n const open = isControlled ? controlledOpen : uncontrolledOpen\n\n const handleOpenChange = React.useCallback(\n (value: boolean) => {\n if (!isControlled) setUncontrolledOpen(value)\n onOpenChange?.(value)\n },\n [isControlled, onOpenChange],\n )\n\n const contextValue = React.useMemo(\n () => ({ open, onClose: () => handleOpenChange(false) }),\n [open, handleOpenChange],\n )\n\n return (\n <PopoverOpenContext.Provider value={contextValue}>\n <PopoverPrimitive.Root open={open} onOpenChange={handleOpenChange} {...props} />\n </PopoverOpenContext.Provider>\n )\n}\nPopover.displayName = 'Popover'\n\nconst PopoverTrigger = PopoverPrimitive.Trigger\n\nconst PopoverAnchor = PopoverPrimitive.Anchor\n\nconst PopoverContent = React.forwardRef<\n React.ElementRef<typeof PopoverPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = 'center', sideOffset = 4, children, ...props }, ref) => {\n const isMobileRaw = useIsMobile()\n const { open, onClose } = React.useContext(PopoverOpenContext)\n\n if (isMobileRaw) {\n return (\n <BottomSheet\n open={open}\n onOpenChange={(v) => { if (!v) onClose() }}\n title=\"Options\"\n className={className}\n >\n {children}\n </BottomSheet>\n )\n }\n\n return (\n <AnimatePresence>\n {open && (\n <PopoverPrimitive.Portal forceMount>\n <PopoverPrimitive.Content\n ref={ref}\n align={align}\n sideOffset={sideOffset}\n forceMount\n asChild\n {...props}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ ...springs.snappy, opacity: tweens.fade }}\n className={cn(\n 'z-popover w-72 rounded-overlay bg-surface-overlay p-ds-05 text-surface-fg shadow-floating outline-hidden',\n className,\n )}\n >\n {children}\n </motion.div>\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n )}\n </AnimatePresence>\n )\n})\nPopoverContent.displayName = PopoverPrimitive.Content.displayName\n\nexport type PopoverContentProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n\nexport { Popover, PopoverAnchor,PopoverContent, PopoverTrigger }\n"],"mappings":"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"}
@@ -51,7 +51,7 @@ function p({ children: n, className: r }) {
51
51
  y: 0
52
52
  },
53
53
  transition: e.fade,
54
- className: t("flex items-center gap-ds-01 rounded-control border border-surface-border bg-surface-overlay/95 px-ds-02 py-ds-01 shadow-floating backdrop-blur-xs", r),
54
+ className: t("flex items-center gap-ds-01 rounded-control bg-surface-overlay/95 px-ds-02 py-ds-01 shadow-floating backdrop-blur-xs", r),
55
55
  children: n
56
56
  });
57
57
  }
@@ -1 +1 @@
1
- {"version":3,"file":"shared.js","names":[],"sources":["../../src/composed/file-preview/shared.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconAlertTriangle,\n IconDownload,\n IconVolume,\n IconVolumeOff,\n} from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Button } from '../../ui/button'\nimport { Icon } from '../../ui/icon'\nimport { tweens } from '../../ui/lib/motion'\nimport { cn } from '../../ui/lib/utils'\n\n// ============================================================\n// Shared: Error Fallback\n// ============================================================\n\nexport function ErrorFallback({ message, url }: { message: string; url: string }) {\n return (\n <div className=\"flex flex-col items-center justify-center gap-ds-04 rounded-control border border-surface-border bg-surface-sunken p-ds-08 text-center\">\n <Icon icon={IconAlertTriangle} size=\"2xl\" className=\"text-warning-9\" />\n <div>\n <p className=\"text-ds-md font-semibold text-surface-fg\">Preview unavailable</p>\n <p className=\"text-ds-sm text-surface-fg-muted mt-ds-01\">{message}</p>\n </div>\n <Button variant=\"outline\" size=\"xs\" startIcon={<Icon icon={IconDownload} />} asChild>\n <a href={url} download target=\"_blank\" rel=\"noopener noreferrer\">Download file</a>\n </Button>\n </div>\n )\n}\n\n// ============================================================\n// Shared: Floating Toolbar\n// ============================================================\n\nexport function Toolbar({ children, className }: { children: React.ReactNode; className?: string }) {\n return (\n <motion.div\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={tweens.fade}\n className={cn(\n 'flex items-center gap-ds-01 rounded-control border border-surface-border bg-surface-overlay/95 px-ds-02 py-ds-01 shadow-floating backdrop-blur-xs',\n className,\n )}\n >\n {children}\n </motion.div>\n )\n}\n\nexport function ToolbarDivider() {\n return <div className=\"mx-ds-01 h-4 w-px bg-surface-border-subtle\" />\n}\n\n// ============================================================\n// Shared: Volume Slider (Spotify/YouTube style)\n// Custom-built — no native input range. Consistent across browsers.\n// ============================================================\n\nexport function VolumeControl({\n volume,\n muted,\n onVolumeChange,\n onMuteToggle,\n variant = 'light',\n}: {\n volume: number\n muted: boolean\n onVolumeChange: (v: number) => void\n onMuteToggle: () => void\n variant?: 'light' | 'dark'\n}) {\n const trackRef = React.useRef<HTMLDivElement>(null)\n const [dragging, setDragging] = React.useState(false)\n const displayVolume = muted ? 0 : volume\n\n function getVolumeFromEvent(e: MouseEvent | React.MouseEvent) {\n if (!trackRef.current) return volume\n const rect = trackRef.current.getBoundingClientRect()\n return Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))\n }\n\n function handlePointerDown(e: React.PointerEvent) {\n e.preventDefault()\n setDragging(true)\n const v = getVolumeFromEvent(e)\n onVolumeChange(v)\n ;(e.target as HTMLElement).setPointerCapture(e.pointerId)\n }\n\n function handlePointerMove(e: React.PointerEvent) {\n if (!dragging) return\n onVolumeChange(getVolumeFromEvent(e))\n }\n\n function handlePointerUp() {\n setDragging(false)\n }\n\n const isDark = variant === 'dark'\n const trackBg = isDark ? 'bg-white/30' : 'bg-surface-sunken'\n const fillBg = isDark ? 'bg-white' : 'bg-accent-9'\n const thumbBg = isDark ? 'bg-white' : 'bg-accent-9'\n const iconClass = isDark ? 'text-white hover:text-white/80' : 'text-surface-fg-muted hover:text-surface-fg'\n\n return (\n <div className=\"group/vol flex items-center gap-ds-02 shrink-0\">\n <button onClick={onMuteToggle} className={cn('transition-colors', iconClass)} aria-label={muted ? 'Unmute (M)' : 'Mute (M)'} title={muted ? 'Unmute' : 'Mute'}>\n {muted || volume === 0 ? <Icon icon={IconVolumeOff} size=\"sm\" /> : <Icon icon={IconVolume} size=\"sm\" />}\n </button>\n <div className=\"w-0 overflow-hidden group-hover/vol:w-20 transition-[width] duration-200 ease-productive-standard flex items-center\">\n <div\n ref={trackRef}\n className={cn('relative w-full h-1 rounded-pill cursor-pointer', trackBg)}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n role=\"slider\"\n aria-label=\"Volume\"\n aria-valuenow={Math.round(displayVolume * 100)}\n aria-valuemin={0}\n aria-valuemax={100}\n tabIndex={0}\n >\n {/* Fill */}\n <div\n className={cn('absolute left-0 top-0 h-full rounded-pill transition-[width] duration-75', fillBg)}\n style={{ width: `${displayVolume * 100}%` }}\n />\n {/* Thumb */}\n <div\n className={cn(\n 'absolute top-1/2 -translate-y-1/2 h-2.5 w-2.5 rounded-pill shadow-raised transition-opacity',\n thumbBg,\n dragging ? 'opacity-100 scale-110' : 'opacity-0 group-hover/vol:opacity-100',\n )}\n style={{ left: `${displayVolume * 100}%`, marginLeft: '-5px' }}\n />\n </div>\n </div>\n </div>\n )\n}\n\n// ============================================================\n// Shared: Time Formatting\n// ============================================================\n\nexport function formatTime(s: number): string {\n if (!isFinite(s) || s < 0) return '0:00'\n const h = Math.floor(s / 3600)\n const m = Math.floor((s % 3600) / 60)\n const sec = Math.floor(s % 60)\n if (h > 0) return `${h}:${m.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`\n return `${m}:${sec.toString().padStart(2, '0')}`\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared.js","names":[],"sources":["../../src/composed/file-preview/shared.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconAlertTriangle,\n IconDownload,\n IconVolume,\n IconVolumeOff,\n} from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Button } from '../../ui/button'\nimport { Icon } from '../../ui/icon'\nimport { tweens } from '../../ui/lib/motion'\nimport { cn } from '../../ui/lib/utils'\n\n// ============================================================\n// Shared: Error Fallback\n// ============================================================\n\nexport function ErrorFallback({ message, url }: { message: string; url: string }) {\n return (\n <div className=\"flex flex-col items-center justify-center gap-ds-04 rounded-control border border-surface-border bg-surface-sunken p-ds-08 text-center\">\n <Icon icon={IconAlertTriangle} size=\"2xl\" className=\"text-warning-9\" />\n <div>\n <p className=\"text-ds-md font-semibold text-surface-fg\">Preview unavailable</p>\n <p className=\"text-ds-sm text-surface-fg-muted mt-ds-01\">{message}</p>\n </div>\n <Button variant=\"outline\" size=\"xs\" startIcon={<Icon icon={IconDownload} />} asChild>\n <a href={url} download target=\"_blank\" rel=\"noopener noreferrer\">Download file</a>\n </Button>\n </div>\n )\n}\n\n// ============================================================\n// Shared: Floating Toolbar\n// ============================================================\n\nexport function Toolbar({ children, className }: { children: React.ReactNode; className?: string }) {\n return (\n <motion.div\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={tweens.fade}\n className={cn(\n 'flex items-center gap-ds-01 rounded-control bg-surface-overlay/95 px-ds-02 py-ds-01 shadow-floating backdrop-blur-xs',\n className,\n )}\n >\n {children}\n </motion.div>\n )\n}\n\nexport function ToolbarDivider() {\n return <div className=\"mx-ds-01 h-4 w-px bg-surface-border-subtle\" />\n}\n\n// ============================================================\n// Shared: Volume Slider (Spotify/YouTube style)\n// Custom-built — no native input range. Consistent across browsers.\n// ============================================================\n\nexport function VolumeControl({\n volume,\n muted,\n onVolumeChange,\n onMuteToggle,\n variant = 'light',\n}: {\n volume: number\n muted: boolean\n onVolumeChange: (v: number) => void\n onMuteToggle: () => void\n variant?: 'light' | 'dark'\n}) {\n const trackRef = React.useRef<HTMLDivElement>(null)\n const [dragging, setDragging] = React.useState(false)\n const displayVolume = muted ? 0 : volume\n\n function getVolumeFromEvent(e: MouseEvent | React.MouseEvent) {\n if (!trackRef.current) return volume\n const rect = trackRef.current.getBoundingClientRect()\n return Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))\n }\n\n function handlePointerDown(e: React.PointerEvent) {\n e.preventDefault()\n setDragging(true)\n const v = getVolumeFromEvent(e)\n onVolumeChange(v)\n ;(e.target as HTMLElement).setPointerCapture(e.pointerId)\n }\n\n function handlePointerMove(e: React.PointerEvent) {\n if (!dragging) return\n onVolumeChange(getVolumeFromEvent(e))\n }\n\n function handlePointerUp() {\n setDragging(false)\n }\n\n const isDark = variant === 'dark'\n const trackBg = isDark ? 'bg-white/30' : 'bg-surface-sunken'\n const fillBg = isDark ? 'bg-white' : 'bg-accent-9'\n const thumbBg = isDark ? 'bg-white' : 'bg-accent-9'\n const iconClass = isDark ? 'text-white hover:text-white/80' : 'text-surface-fg-muted hover:text-surface-fg'\n\n return (\n <div className=\"group/vol flex items-center gap-ds-02 shrink-0\">\n <button onClick={onMuteToggle} className={cn('transition-colors', iconClass)} aria-label={muted ? 'Unmute (M)' : 'Mute (M)'} title={muted ? 'Unmute' : 'Mute'}>\n {muted || volume === 0 ? <Icon icon={IconVolumeOff} size=\"sm\" /> : <Icon icon={IconVolume} size=\"sm\" />}\n </button>\n <div className=\"w-0 overflow-hidden group-hover/vol:w-20 transition-[width] duration-200 ease-productive-standard flex items-center\">\n <div\n ref={trackRef}\n className={cn('relative w-full h-1 rounded-pill cursor-pointer', trackBg)}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n role=\"slider\"\n aria-label=\"Volume\"\n aria-valuenow={Math.round(displayVolume * 100)}\n aria-valuemin={0}\n aria-valuemax={100}\n tabIndex={0}\n >\n {/* Fill */}\n <div\n className={cn('absolute left-0 top-0 h-full rounded-pill transition-[width] duration-75', fillBg)}\n style={{ width: `${displayVolume * 100}%` }}\n />\n {/* Thumb */}\n <div\n className={cn(\n 'absolute top-1/2 -translate-y-1/2 h-2.5 w-2.5 rounded-pill shadow-raised transition-opacity',\n thumbBg,\n dragging ? 'opacity-100 scale-110' : 'opacity-0 group-hover/vol:opacity-100',\n )}\n style={{ left: `${displayVolume * 100}%`, marginLeft: '-5px' }}\n />\n </div>\n </div>\n </div>\n )\n}\n\n// ============================================================\n// Shared: Time Formatting\n// ============================================================\n\nexport function formatTime(s: number): string {\n if (!isFinite(s) || s < 0) return '0:00'\n const h = Math.floor(s / 3600)\n const m = Math.floor((s % 3600) / 60)\n const sec = Math.floor(s % 60)\n if (h > 0) return `${h}:${m.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`\n return `${m}:${sec.toString().padStart(2, '0')}`\n}\n"],"mappings":"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"}
@@ -211,7 +211,7 @@ var _ = a.forwardRef(({ itemId: o, label: s, icon: d, secondaryLabel: f, actions
211
211
  })
212
212
  }),
213
213
  /* @__PURE__ */ c("span", {
214
- className: "truncate text-ds-sm",
214
+ className: "min-w-0 truncate text-ds-sm",
215
215
  children: s
216
216
  }),
217
217
  f && /* @__PURE__ */ c("span", {