bubble-toolbar 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,441 @@
1
+ // src/BubbleToolbar.tsx
2
+ import { forwardRef as forwardRef2, useMemo } from "react";
3
+
4
+ // src/context.ts
5
+ import { createContext, useContext } from "react";
6
+ var defaultContextValue = {
7
+ size: "md",
8
+ theme: "glass",
9
+ blurred: true,
10
+ blurAmount: 16,
11
+ itemGap: 8,
12
+ bordered: true,
13
+ shadow: true
14
+ };
15
+ var BubbleToolbarContext = createContext(defaultContextValue);
16
+ var useBubbleToolbar = () => {
17
+ return useContext(BubbleToolbarContext);
18
+ };
19
+
20
+ // src/BubbleGroup.tsx
21
+ import { forwardRef } from "react";
22
+
23
+ // src/utils/children.ts
24
+ import React from "react";
25
+ function getValidChildren(children) {
26
+ const result = [];
27
+ React.Children.forEach(children, (child) => {
28
+ if (child === null || child === void 0 || typeof child === "boolean") {
29
+ return;
30
+ }
31
+ if (React.isValidElement(child) && child.type === React.Fragment) {
32
+ const fragmentChildren = getValidChildren(
33
+ child.props.children
34
+ );
35
+ result.push(...fragmentChildren);
36
+ } else {
37
+ result.push(child);
38
+ }
39
+ });
40
+ return result;
41
+ }
42
+
43
+ // src/utils/styles.ts
44
+ function resolveSize(size) {
45
+ if (typeof size === "number") return size;
46
+ switch (size) {
47
+ case "sm":
48
+ return 40;
49
+ case "lg":
50
+ return 56;
51
+ case "md":
52
+ default:
53
+ return 48;
54
+ }
55
+ }
56
+ function formatPx(val) {
57
+ return typeof val === "number" ? `${val}px` : val;
58
+ }
59
+ function getGlassStyles(theme, blurred, blurAmount, bordered, shadow) {
60
+ const blurVal = formatPx(blurAmount);
61
+ let background;
62
+ let borderColor;
63
+ let color;
64
+ switch (theme) {
65
+ case "dark":
66
+ background = blurred ? "rgba(24, 24, 27, 0.72)" : "rgba(24, 24, 27, 0.95)";
67
+ borderColor = "rgba(255, 255, 255, 0.12)";
68
+ color = "#f4f4f5";
69
+ break;
70
+ case "light":
71
+ background = blurred ? "rgba(255, 255, 255, 0.75)" : "rgba(255, 255, 255, 0.95)";
72
+ borderColor = "rgba(0, 0, 0, 0.08)";
73
+ color = "#18181b";
74
+ break;
75
+ case "auto":
76
+ background = "var(--bubble-bg, rgba(255, 255, 255, 0.75))";
77
+ borderColor = "var(--bubble-border, rgba(0, 0, 0, 0.08))";
78
+ color = "var(--bubble-fg, #18181b)";
79
+ break;
80
+ case "glass":
81
+ default:
82
+ background = blurred ? "rgba(255, 255, 255, 0.65)" : "rgba(255, 255, 255, 0.92)";
83
+ borderColor = "rgba(255, 255, 255, 0.4)";
84
+ color = "#09090b";
85
+ break;
86
+ }
87
+ const boxShadow = typeof shadow === "string" ? shadow : shadow ? "0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.06)" : "none";
88
+ return {
89
+ backgroundColor: background,
90
+ backdropFilter: blurred ? `blur(${blurVal}) saturate(180%)` : void 0,
91
+ WebkitBackdropFilter: blurred ? `blur(${blurVal}) saturate(180%)` : void 0,
92
+ border: bordered ? `1px solid ${borderColor}` : "none",
93
+ boxShadow,
94
+ color,
95
+ transition: "all 0.25s cubic-bezier(0.16, 1, 0.3, 1)"
96
+ };
97
+ }
98
+
99
+ // src/BubbleGroup.tsx
100
+ import { jsx } from "react/jsx-runtime";
101
+ var BubbleGroup = forwardRef(
102
+ ({
103
+ children,
104
+ shape: explicitShape,
105
+ size: explicitSize,
106
+ itemGap: explicitItemGap,
107
+ className = "",
108
+ style,
109
+ ...rest
110
+ }, ref) => {
111
+ const context = useBubbleToolbar();
112
+ const validChildren = getValidChildren(children);
113
+ const count = validChildren.length;
114
+ const computedShape = explicitShape || (count <= 1 ? "circle" : "pill");
115
+ const effectiveSize = resolveSize(explicitSize || context.size);
116
+ const effectiveItemGap = explicitItemGap !== void 0 ? explicitItemGap : context.itemGap;
117
+ const glassStyles = getGlassStyles(
118
+ context.theme,
119
+ context.blurred,
120
+ context.blurAmount,
121
+ context.bordered,
122
+ context.shadow
123
+ );
124
+ const isCircle = computedShape === "circle";
125
+ const groupStyles = {
126
+ ...glassStyles,
127
+ height: `${effectiveSize}px`,
128
+ boxSizing: "border-box",
129
+ display: "inline-flex",
130
+ alignItems: "center",
131
+ flexShrink: 0,
132
+ userSelect: "none",
133
+ ...isCircle ? {
134
+ width: `${effectiveSize}px`,
135
+ minWidth: `${effectiveSize}px`,
136
+ borderRadius: "9999px",
137
+ justifyContent: "center",
138
+ padding: 0
139
+ } : {
140
+ width: "auto",
141
+ minWidth: `${effectiveSize}px`,
142
+ borderRadius: "9999px",
143
+ flexDirection: "row",
144
+ justifyContent: "flex-start",
145
+ paddingLeft: `${Math.max(12, Math.round(effectiveSize * 0.28))}px`,
146
+ paddingRight: `${Math.max(12, Math.round(effectiveSize * 0.28))}px`,
147
+ gap: formatPx(effectiveItemGap)
148
+ },
149
+ ...style
150
+ };
151
+ const combinedClassName = [
152
+ "bubble-group",
153
+ `bubble-group--${computedShape}`,
154
+ className
155
+ ].filter(Boolean).join(" ");
156
+ return /* @__PURE__ */ jsx(
157
+ "div",
158
+ {
159
+ ref,
160
+ role: "group",
161
+ className: combinedClassName,
162
+ style: groupStyles,
163
+ "data-shape": computedShape,
164
+ "data-items-count": count,
165
+ ...rest,
166
+ children: validChildren
167
+ }
168
+ );
169
+ }
170
+ );
171
+ BubbleGroup.displayName = "BubbleGroup";
172
+
173
+ // src/BubbleToolbar.tsx
174
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
175
+ var BubbleToolbar = forwardRef2(
176
+ ({
177
+ groups,
178
+ children,
179
+ position = "bottom",
180
+ theme = "glass",
181
+ size = "md",
182
+ blurred = true,
183
+ blurAmount = 16,
184
+ groupGap = 8,
185
+ itemGap = 8,
186
+ bordered = true,
187
+ shadow = true,
188
+ className = "",
189
+ style,
190
+ ariaLabel = "Toolbar",
191
+ ...rest
192
+ }, ref) => {
193
+ const contextValue = useMemo(
194
+ () => ({
195
+ size,
196
+ theme,
197
+ blurred,
198
+ blurAmount,
199
+ itemGap,
200
+ bordered,
201
+ shadow
202
+ }),
203
+ [size, theme, blurred, blurAmount, itemGap, bordered, shadow]
204
+ );
205
+ const getPositionStyles = () => {
206
+ switch (position) {
207
+ case "bottom":
208
+ return {
209
+ position: "fixed",
210
+ bottom: "24px",
211
+ left: "50%",
212
+ transform: "translateX(-50%)",
213
+ zIndex: 1e3
214
+ };
215
+ case "top":
216
+ return {
217
+ position: "fixed",
218
+ top: "24px",
219
+ left: "50%",
220
+ transform: "translateX(-50%)",
221
+ zIndex: 1e3
222
+ };
223
+ case "static":
224
+ return {
225
+ position: "relative"
226
+ };
227
+ case "custom":
228
+ default:
229
+ return {};
230
+ }
231
+ };
232
+ const containerStyles = {
233
+ ...getPositionStyles(),
234
+ display: "inline-flex",
235
+ alignItems: "center",
236
+ gap: formatPx(groupGap),
237
+ maxWidth: "calc(100vw - 32px)",
238
+ overflowX: "auto",
239
+ overflowY: "hidden",
240
+ padding: "4px",
241
+ boxSizing: "border-box",
242
+ scrollbarWidth: "none",
243
+ // Firefox
244
+ msOverflowStyle: "none",
245
+ // IE/Edge
246
+ WebkitOverflowScrolling: "touch",
247
+ pointerEvents: "auto",
248
+ ...style
249
+ };
250
+ const combinedClassName = [
251
+ "bubble-toolbar",
252
+ `bubble-toolbar--${position}`,
253
+ `bubble-toolbar--${theme}`,
254
+ className
255
+ ].filter(Boolean).join(" ");
256
+ return /* @__PURE__ */ jsx2(BubbleToolbarContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
257
+ "div",
258
+ {
259
+ ref,
260
+ role: "toolbar",
261
+ "aria-label": ariaLabel,
262
+ className: combinedClassName,
263
+ style: containerStyles,
264
+ ...rest,
265
+ children: [
266
+ groups && groups.length > 0 ? groups.map((groupItems, index) => /* @__PURE__ */ jsx2(BubbleGroup, { children: groupItems }, index)) : null,
267
+ children
268
+ ]
269
+ }
270
+ ) });
271
+ }
272
+ );
273
+ BubbleToolbar.displayName = "BubbleToolbar";
274
+ BubbleToolbar.Group = BubbleGroup;
275
+
276
+ // src/components/BubbleIconButton.tsx
277
+ import { forwardRef as forwardRef3, useState } from "react";
278
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
279
+ var ICONS = {
280
+ search: /* @__PURE__ */ jsxs2("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
281
+ /* @__PURE__ */ jsx3("circle", { cx: "11", cy: "11", r: "8" }),
282
+ /* @__PURE__ */ jsx3("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
283
+ ] }),
284
+ theme: /* @__PURE__ */ jsxs2("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
285
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "5" }),
286
+ /* @__PURE__ */ jsx3("line", { x1: "12", y1: "1", x2: "12", y2: "3" }),
287
+ /* @__PURE__ */ jsx3("line", { x1: "12", y1: "21", x2: "12", y2: "23" }),
288
+ /* @__PURE__ */ jsx3("line", { x1: "4.22", y1: "4.22", x2: "5.64", y2: "5.64" }),
289
+ /* @__PURE__ */ jsx3("line", { x1: "18.36", y1: "18.36", x2: "19.78", y2: "19.78" }),
290
+ /* @__PURE__ */ jsx3("line", { x1: "1", y1: "12", x2: "3", y2: "12" }),
291
+ /* @__PURE__ */ jsx3("line", { x1: "21", y1: "12", x2: "23", y2: "12" }),
292
+ /* @__PURE__ */ jsx3("line", { x1: "4.22", y1: "19.78", x2: "5.64", y2: "18.36" }),
293
+ /* @__PURE__ */ jsx3("line", { x1: "18.36", y1: "5.64", x2: "19.78", y2: "4.22" })
294
+ ] }),
295
+ chat: /* @__PURE__ */ jsx3("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx3("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) }),
296
+ notifications: /* @__PURE__ */ jsxs2("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
297
+ /* @__PURE__ */ jsx3("path", { d: "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" }),
298
+ /* @__PURE__ */ jsx3("path", { d: "M13.73 21a2 2 0 0 1-3.46 0" })
299
+ ] }),
300
+ logo: /* @__PURE__ */ jsxs2("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
301
+ /* @__PURE__ */ jsx3("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }),
302
+ /* @__PURE__ */ jsx3("polyline", { points: "2 17 12 22 22 17" }),
303
+ /* @__PURE__ */ jsx3("polyline", { points: "2 12 12 17 22 12" })
304
+ ] }),
305
+ close: /* @__PURE__ */ jsxs2("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
306
+ /* @__PURE__ */ jsx3("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
307
+ /* @__PURE__ */ jsx3("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
308
+ ] })
309
+ };
310
+ var BubbleIconButton = forwardRef3(
311
+ ({
312
+ name,
313
+ icon,
314
+ active = false,
315
+ badge,
316
+ tooltip,
317
+ children,
318
+ className = "",
319
+ style,
320
+ onMouseEnter,
321
+ onMouseLeave,
322
+ ariaLabel,
323
+ ...rest
324
+ }, ref) => {
325
+ const [isHovered, setIsHovered] = useState(false);
326
+ const iconContent = icon || (name ? ICONS[name] : null) || children;
327
+ const computedAriaLabel = ariaLabel || tooltip || (name ? `${name} button` : "button");
328
+ const buttonStyles = {
329
+ position: "relative",
330
+ display: "inline-flex",
331
+ alignItems: "center",
332
+ justifyContent: "center",
333
+ width: "36px",
334
+ height: "36px",
335
+ borderRadius: "50%",
336
+ border: "none",
337
+ background: active ? "rgba(0, 0, 0, 0.08)" : isHovered ? "rgba(0, 0, 0, 0.05)" : "transparent",
338
+ color: "inherit",
339
+ cursor: "pointer",
340
+ padding: 0,
341
+ margin: 0,
342
+ outline: "none",
343
+ transition: "background-color 0.15s ease, transform 0.15s ease",
344
+ transform: isHovered ? "scale(1.06)" : "scale(1)",
345
+ flexShrink: 0,
346
+ ...style
347
+ };
348
+ const badgeStyles = {
349
+ position: "absolute",
350
+ top: "4px",
351
+ right: "4px",
352
+ minWidth: "7px",
353
+ height: "7px",
354
+ padding: typeof badge === "number" || typeof badge === "string" ? "1px 3px" : 0,
355
+ borderRadius: "9999px",
356
+ backgroundColor: "#ef4444",
357
+ color: "#ffffff",
358
+ fontSize: "9px",
359
+ fontWeight: "bold",
360
+ lineHeight: "1",
361
+ display: "flex",
362
+ alignItems: "center",
363
+ justifyContent: "center",
364
+ boxShadow: "0 0 0 1.5px rgba(255, 255, 255, 0.9)"
365
+ };
366
+ return /* @__PURE__ */ jsxs2(
367
+ "button",
368
+ {
369
+ ref,
370
+ type: "button",
371
+ title: tooltip,
372
+ "aria-label": computedAriaLabel,
373
+ className: `bubble-icon-button ${active ? "bubble-icon-button--active" : ""} ${className}`.trim(),
374
+ style: buttonStyles,
375
+ onMouseEnter: (e) => {
376
+ setIsHovered(true);
377
+ onMouseEnter?.(e);
378
+ },
379
+ onMouseLeave: (e) => {
380
+ setIsHovered(false);
381
+ onMouseLeave?.(e);
382
+ },
383
+ ...rest,
384
+ children: [
385
+ iconContent,
386
+ badge ? /* @__PURE__ */ jsx3("span", { className: "bubble-icon-button__badge", style: badgeStyles, children: typeof badge !== "boolean" ? badge : null }) : null
387
+ ]
388
+ }
389
+ );
390
+ }
391
+ );
392
+ BubbleIconButton.displayName = "BubbleIconButton";
393
+
394
+ // src/components/BubbleArea.tsx
395
+ import { forwardRef as forwardRef4 } from "react";
396
+ import { jsx as jsx4 } from "react/jsx-runtime";
397
+ var BubbleArea = forwardRef4(
398
+ ({
399
+ children,
400
+ minWidth = 120,
401
+ maxWidth,
402
+ className = "",
403
+ style,
404
+ ...rest
405
+ }, ref) => {
406
+ const areaStyles = {
407
+ display: "inline-flex",
408
+ alignItems: "center",
409
+ minWidth: formatPx(minWidth),
410
+ maxWidth: maxWidth ? formatPx(maxWidth) : void 0,
411
+ flex: "1 1 auto",
412
+ fontSize: "14px",
413
+ color: "inherit",
414
+ ...style
415
+ };
416
+ return /* @__PURE__ */ jsx4(
417
+ "div",
418
+ {
419
+ ref,
420
+ className: `bubble-area ${className}`.trim(),
421
+ style: areaStyles,
422
+ ...rest,
423
+ children
424
+ }
425
+ );
426
+ }
427
+ );
428
+ BubbleArea.displayName = "BubbleArea";
429
+ export {
430
+ BubbleArea,
431
+ BubbleGroup,
432
+ BubbleIconButton,
433
+ BubbleToolbar,
434
+ BubbleToolbarContext,
435
+ formatPx,
436
+ getGlassStyles,
437
+ getValidChildren,
438
+ resolveSize,
439
+ useBubbleToolbar
440
+ };
441
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/BubbleToolbar.tsx","../src/context.ts","../src/BubbleGroup.tsx","../src/utils/children.ts","../src/utils/styles.ts","../src/components/BubbleIconButton.tsx","../src/components/BubbleArea.tsx"],"sourcesContent":["import React, { forwardRef, useMemo } from 'react';\nimport type { BubbleToolbarProps } from './types';\nimport { BubbleToolbarContext, BubbleToolbarContextValue } from './context';\nimport { BubbleGroup } from './BubbleGroup';\nimport { formatPx } from './utils/styles';\n\nexport interface BubbleToolbarComponent\n extends React.ForwardRefExoticComponent<\n BubbleToolbarProps & React.RefAttributes<HTMLDivElement>\n > {\n Group: typeof BubbleGroup;\n}\n\nexport const BubbleToolbar = forwardRef<HTMLDivElement, BubbleToolbarProps>(\n (\n {\n groups,\n children,\n position = 'bottom',\n theme = 'glass',\n size = 'md',\n blurred = true,\n blurAmount = 16,\n groupGap = 8,\n itemGap = 8,\n bordered = true,\n shadow = true,\n className = '',\n style,\n ariaLabel = 'Toolbar',\n ...rest\n },\n ref\n ) => {\n const contextValue = useMemo<BubbleToolbarContextValue>(\n () => ({\n size,\n theme,\n blurred,\n blurAmount,\n itemGap,\n bordered,\n shadow,\n }),\n [size, theme, blurred, blurAmount, itemGap, bordered, shadow]\n );\n\n // Compute positioning styles\n const getPositionStyles = (): React.CSSProperties => {\n switch (position) {\n case 'bottom':\n return {\n position: 'fixed',\n bottom: '24px',\n left: '50%',\n transform: 'translateX(-50%)',\n zIndex: 1000,\n };\n case 'top':\n return {\n position: 'fixed',\n top: '24px',\n left: '50%',\n transform: 'translateX(-50%)',\n zIndex: 1000,\n };\n case 'static':\n return {\n position: 'relative',\n };\n case 'custom':\n default:\n return {};\n }\n };\n\n const containerStyles: React.CSSProperties = {\n ...getPositionStyles(),\n display: 'inline-flex',\n alignItems: 'center',\n gap: formatPx(groupGap),\n maxWidth: 'calc(100vw - 32px)',\n overflowX: 'auto',\n overflowY: 'hidden',\n padding: '4px',\n boxSizing: 'border-box',\n scrollbarWidth: 'none', // Firefox\n msOverflowStyle: 'none', // IE/Edge\n WebkitOverflowScrolling: 'touch',\n pointerEvents: 'auto',\n ...style,\n };\n\n const combinedClassName = [\n 'bubble-toolbar',\n `bubble-toolbar--${position}`,\n `bubble-toolbar--${theme}`,\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <BubbleToolbarContext.Provider value={contextValue}>\n <div\n ref={ref}\n role=\"toolbar\"\n aria-label={ariaLabel}\n className={combinedClassName}\n style={containerStyles}\n {...rest}\n >\n {groups && groups.length > 0\n ? groups.map((groupItems, index) => (\n <BubbleGroup key={index}>\n {groupItems}\n </BubbleGroup>\n ))\n : null}\n {children}\n </div>\n </BubbleToolbarContext.Provider>\n );\n }\n) as BubbleToolbarComponent;\n\nBubbleToolbar.displayName = 'BubbleToolbar';\nBubbleToolbar.Group = BubbleGroup;\n\n","import { createContext, useContext } from 'react';\nimport type { BubbleToolbarTheme, BubbleToolbarSize } from './types';\n\nexport interface BubbleToolbarContextValue {\n size: BubbleToolbarSize;\n theme: BubbleToolbarTheme;\n blurred: boolean;\n blurAmount: number | string;\n itemGap: number | string;\n bordered: boolean;\n shadow: boolean | string;\n}\n\nexport const defaultContextValue: BubbleToolbarContextValue = {\n size: 'md',\n theme: 'glass',\n blurred: true,\n blurAmount: 16,\n itemGap: 8,\n bordered: true,\n shadow: true,\n};\n\nexport const BubbleToolbarContext = createContext<BubbleToolbarContextValue>(defaultContextValue);\n\nexport const useBubbleToolbar = (): BubbleToolbarContextValue => {\n return useContext(BubbleToolbarContext);\n};\n\n","import React, { forwardRef } from 'react';\nimport type { BubbleGroupProps, BubbleShape } from './types';\nimport { useBubbleToolbar } from './context';\nimport { getValidChildren } from './utils/children';\nimport { resolveSize, formatPx, getGlassStyles } from './utils/styles';\n\nexport const BubbleGroup = forwardRef<HTMLDivElement, BubbleGroupProps>(\n (\n {\n children,\n shape: explicitShape,\n size: explicitSize,\n itemGap: explicitItemGap,\n className = '',\n style,\n ...rest\n },\n ref\n ) => {\n const context = useBubbleToolbar();\n\n const validChildren = getValidChildren(children);\n const count = validChildren.length;\n\n // Morphological logic:\n // 1 node => circle\n // > 1 node => pill / rounded rectangle\n const computedShape: BubbleShape =\n explicitShape || (count <= 1 ? 'circle' : 'pill');\n\n const effectiveSize = resolveSize(explicitSize || context.size);\n const effectiveItemGap =\n explicitItemGap !== undefined ? explicitItemGap : context.itemGap;\n\n const glassStyles = getGlassStyles(\n context.theme,\n context.blurred,\n context.blurAmount,\n context.bordered,\n context.shadow\n );\n\n const isCircle = computedShape === 'circle';\n\n const groupStyles: React.CSSProperties = {\n ...glassStyles,\n height: `${effectiveSize}px`,\n boxSizing: 'border-box',\n display: 'inline-flex',\n alignItems: 'center',\n flexShrink: 0,\n userSelect: 'none',\n ...(isCircle\n ? {\n width: `${effectiveSize}px`,\n minWidth: `${effectiveSize}px`,\n borderRadius: '9999px',\n justifyContent: 'center',\n padding: 0,\n }\n : {\n width: 'auto',\n minWidth: `${effectiveSize}px`,\n borderRadius: '9999px',\n flexDirection: 'row',\n justifyContent: 'flex-start',\n paddingLeft: `${Math.max(12, Math.round(effectiveSize * 0.28))}px`,\n paddingRight: `${Math.max(12, Math.round(effectiveSize * 0.28))}px`,\n gap: formatPx(effectiveItemGap),\n }),\n ...style,\n };\n\n const combinedClassName = [\n 'bubble-group',\n `bubble-group--${computedShape}`,\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <div\n ref={ref}\n role=\"group\"\n className={combinedClassName}\n style={groupStyles}\n data-shape={computedShape}\n data-items-count={count}\n {...rest}\n >\n {validChildren}\n </div>\n );\n }\n);\n\nBubbleGroup.displayName = 'BubbleGroup';\n\n","import React, { ReactNode } from 'react';\n\n/**\n * Returns an array of non-null, non-undefined, non-boolean child elements.\n * Unpacks React.Fragment if encountered at the top level.\n */\nexport function getValidChildren(children: ReactNode): ReactNode[] {\n const result: ReactNode[] = [];\n\n React.Children.forEach(children, (child) => {\n if (child === null || child === undefined || typeof child === 'boolean') {\n return;\n }\n\n if (React.isValidElement(child) && child.type === React.Fragment) {\n const fragmentChildren = getValidChildren(\n (child.props as { children?: ReactNode }).children\n );\n result.push(...fragmentChildren);\n } else {\n result.push(child);\n }\n });\n\n return result;\n}\n\n","import type { CSSProperties } from 'react';\nimport type { BubbleToolbarTheme, BubbleToolbarSize } from '../types';\n\nexport function resolveSize(size: BubbleToolbarSize): number {\n if (typeof size === 'number') return size;\n switch (size) {\n case 'sm':\n return 40;\n case 'lg':\n return 56;\n case 'md':\n default:\n return 48;\n }\n}\n\nexport function formatPx(val: number | string): string {\n return typeof val === 'number' ? `${val}px` : val;\n}\n\nexport function getGlassStyles(\n theme: BubbleToolbarTheme,\n blurred: boolean,\n blurAmount: number | string,\n bordered: boolean,\n shadow: boolean | string\n): CSSProperties {\n const blurVal = formatPx(blurAmount);\n\n // Background and border tint based on theme\n let background: string;\n let borderColor: string;\n let color: string;\n\n switch (theme) {\n case 'dark':\n background = blurred ? 'rgba(24, 24, 27, 0.72)' : 'rgba(24, 24, 27, 0.95)';\n borderColor = 'rgba(255, 255, 255, 0.12)';\n color = '#f4f4f5';\n break;\n case 'light':\n background = blurred ? 'rgba(255, 255, 255, 0.75)' : 'rgba(255, 255, 255, 0.95)';\n borderColor = 'rgba(0, 0, 0, 0.08)';\n color = '#18181b';\n break;\n case 'auto':\n background = 'var(--bubble-bg, rgba(255, 255, 255, 0.75))';\n borderColor = 'var(--bubble-border, rgba(0, 0, 0, 0.08))';\n color = 'var(--bubble-fg, #18181b)';\n break;\n case 'glass':\n default:\n // Neutral luminous frosted glass\n background = blurred ? 'rgba(255, 255, 255, 0.65)' : 'rgba(255, 255, 255, 0.92)';\n borderColor = 'rgba(255, 255, 255, 0.4)';\n color = '#09090b';\n break;\n }\n\n const boxShadow =\n typeof shadow === 'string'\n ? shadow\n : shadow\n ? '0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.06)'\n : 'none';\n\n return {\n backgroundColor: background,\n backdropFilter: blurred ? `blur(${blurVal}) saturate(180%)` : undefined,\n WebkitBackdropFilter: blurred ? `blur(${blurVal}) saturate(180%)` : undefined,\n border: bordered ? `1px solid ${borderColor}` : 'none',\n boxShadow,\n color,\n transition: 'all 0.25s cubic-bezier(0.16, 1, 0.3, 1)',\n };\n}\n\n","import React, { forwardRef, useState } from 'react';\nimport type { BubbleIconButtonProps } from '../types';\n\n/**\n * Standard inline SVG icons for quick prototyping out-of-the-box\n */\nconst ICONS: Record<string, React.ReactNode> = {\n search: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\" />\n </svg>\n ),\n theme: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"5\" />\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\" />\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\" />\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\" />\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\" />\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\" />\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\" />\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\" />\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\" />\n </svg>\n ),\n chat: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n ),\n notifications: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\" />\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\" />\n </svg>\n ),\n logo: (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <polygon points=\"12 2 2 7 12 12 22 7 12 2\" />\n <polyline points=\"2 17 12 22 22 17\" />\n <polyline points=\"2 12 12 17 22 12\" />\n </svg>\n ),\n close: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n ),\n};\n\nexport const BubbleIconButton = forwardRef<HTMLButtonElement, BubbleIconButtonProps>(\n (\n {\n name,\n icon,\n active = false,\n badge,\n tooltip,\n children,\n className = '',\n style,\n onMouseEnter,\n onMouseLeave,\n ariaLabel,\n ...rest\n },\n ref\n ) => {\n const [isHovered, setIsHovered] = useState(false);\n\n const iconContent = icon || (name ? ICONS[name] : null) || children;\n const computedAriaLabel = ariaLabel || tooltip || (name ? `${name} button` : 'button');\n\n const buttonStyles: React.CSSProperties = {\n position: 'relative',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '36px',\n height: '36px',\n borderRadius: '50%',\n border: 'none',\n background: active\n ? 'rgba(0, 0, 0, 0.08)'\n : isHovered\n ? 'rgba(0, 0, 0, 0.05)'\n : 'transparent',\n color: 'inherit',\n cursor: 'pointer',\n padding: 0,\n margin: 0,\n outline: 'none',\n transition: 'background-color 0.15s ease, transform 0.15s ease',\n transform: isHovered ? 'scale(1.06)' : 'scale(1)',\n flexShrink: 0,\n ...style,\n };\n\n const badgeStyles: React.CSSProperties = {\n position: 'absolute',\n top: '4px',\n right: '4px',\n minWidth: '7px',\n height: '7px',\n padding: typeof badge === 'number' || typeof badge === 'string' ? '1px 3px' : 0,\n borderRadius: '9999px',\n backgroundColor: '#ef4444',\n color: '#ffffff',\n fontSize: '9px',\n fontWeight: 'bold',\n lineHeight: '1',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n boxShadow: '0 0 0 1.5px rgba(255, 255, 255, 0.9)',\n };\n\n return (\n <button\n ref={ref}\n type=\"button\"\n title={tooltip}\n aria-label={computedAriaLabel}\n className={`bubble-icon-button ${active ? 'bubble-icon-button--active' : ''} ${className}`.trim()}\n style={buttonStyles}\n onMouseEnter={(e) => {\n setIsHovered(true);\n onMouseEnter?.(e);\n }}\n onMouseLeave={(e) => {\n setIsHovered(false);\n onMouseLeave?.(e);\n }}\n {...rest}\n >\n {iconContent}\n {badge ? (\n <span className=\"bubble-icon-button__badge\" style={badgeStyles}>\n {typeof badge !== 'boolean' ? badge : null}\n </span>\n ) : null}\n </button>\n );\n }\n);\n\nBubbleIconButton.displayName = 'BubbleIconButton';\n\n","import React, { forwardRef } from 'react';\nimport type { BubbleAreaProps } from '../types';\nimport { formatPx } from '../utils/styles';\n\nexport const BubbleArea = forwardRef<HTMLDivElement, BubbleAreaProps>(\n (\n {\n children,\n minWidth = 120,\n maxWidth,\n className = '',\n style,\n ...rest\n },\n ref\n ) => {\n const areaStyles: React.CSSProperties = {\n display: 'inline-flex',\n alignItems: 'center',\n minWidth: formatPx(minWidth),\n maxWidth: maxWidth ? formatPx(maxWidth) : undefined,\n flex: '1 1 auto',\n fontSize: '14px',\n color: 'inherit',\n ...style,\n };\n\n return (\n <div\n ref={ref}\n className={`bubble-area ${className}`.trim()}\n style={areaStyles}\n {...rest}\n >\n {children}\n </div>\n );\n }\n);\n\nBubbleArea.displayName = 'BubbleArea';\n\n"],"mappings":"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","names":["forwardRef","jsx","forwardRef","forwardRef","jsx","jsxs","forwardRef","jsx","forwardRef"]}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Bubble Toolbar - Frosted Glass & Responsive Layout
3
+ */
4
+
5
+ :root {
6
+ --bubble-size-sm: 40px;
7
+ --bubble-size-md: 48px;
8
+ --bubble-size-lg: 56px;
9
+ --bubble-blur: 16px;
10
+ --bubble-glass-bg: rgba(255, 255, 255, 0.65);
11
+ --bubble-glass-border: rgba(255, 255, 255, 0.35);
12
+ --bubble-glass-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.05);
13
+ --bubble-dark-bg: rgba(24, 24, 27, 0.72);
14
+ --bubble-dark-border: rgba(255, 255, 255, 0.12);
15
+ }
16
+
17
+ /* Toolbar container */
18
+ .bubble-toolbar {
19
+ display: inline-flex;
20
+ align-items: center;
21
+ box-sizing: border-box;
22
+ user-select: none;
23
+ -webkit-user-select: none;
24
+ touch-action: pan-x;
25
+ }
26
+
27
+ /* Hide scrollbar for Chrome, Safari and Opera */
28
+ .bubble-toolbar::-webkit-scrollbar {
29
+ display: none;
30
+ width: 0;
31
+ height: 0;
32
+ }
33
+
34
+ /* Positions */
35
+ .bubble-toolbar--bottom {
36
+ position: fixed;
37
+ bottom: 24px;
38
+ left: 50%;
39
+ transform: translateX(-50%);
40
+ z-index: 1000;
41
+ }
42
+
43
+ .bubble-toolbar--top {
44
+ position: fixed;
45
+ top: 24px;
46
+ left: 50%;
47
+ transform: translateX(-50%);
48
+ z-index: 1000;
49
+ }
50
+
51
+ .bubble-toolbar--static {
52
+ position: relative;
53
+ }
54
+
55
+ /* Bubble Groups */
56
+ .bubble-group {
57
+ box-sizing: border-box;
58
+ display: inline-flex;
59
+ align-items: center;
60
+ transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s ease;
61
+ }
62
+
63
+ .bubble-group:hover {
64
+ transform: translateY(-2px);
65
+ }
66
+
67
+ /* Circle shape: 1 node */
68
+ .bubble-group--circle {
69
+ aspect-ratio: 1 / 1;
70
+ border-radius: 9999px;
71
+ justify-content: center;
72
+ padding: 0;
73
+ }
74
+
75
+ /* Pill shape: > 1 nodes */
76
+ .bubble-group--pill {
77
+ border-radius: 9999px;
78
+ flex-direction: row;
79
+ }
80
+
81
+ /* Icon Button */
82
+ .bubble-icon-button {
83
+ display: inline-flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ border: none;
87
+ background: transparent;
88
+ cursor: pointer;
89
+ border-radius: 50%;
90
+ transition: background-color 0.15s ease, transform 0.15s ease, color 0.15s ease;
91
+ }
92
+
93
+ .bubble-icon-button:hover {
94
+ background-color: rgba(0, 0, 0, 0.06);
95
+ }
96
+
97
+ .bubble-icon-button:active {
98
+ transform: scale(0.94);
99
+ }
100
+
101
+ .bubble-icon-button:focus-visible {
102
+ outline: 2px solid #3b82f6;
103
+ outline-offset: 2px;
104
+ }
105
+
106
+ /* Area */
107
+ .bubble-area {
108
+ display: inline-flex;
109
+ align-items: center;
110
+ flex: 1 1 auto;
111
+ }
112
+
113
+ /* Media Query: Auto Dark Theme */
114
+ @media (prefers-color-scheme: dark) {
115
+ .bubble-toolbar--auto .bubble-group {
116
+ background-color: var(--bubble-dark-bg) !important;
117
+ border-color: var(--bubble-dark-border) !important;
118
+ color: #f4f4f5 !important;
119
+ }
120
+
121
+ .bubble-toolbar--auto .bubble-icon-button:hover {
122
+ background-color: rgba(255, 255, 255, 0.1);
123
+ }
124
+ }
125
+
package/package.json CHANGED
@@ -1,6 +1,57 @@
1
1
  {
2
2
  "name": "bubble-toolbar",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Floating, responsive, blurred glass toolbar component for React 18 and 19 with group-based circular and pill layouts.",
5
+ "author": "",
6
+ "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.js"
15
+ },
16
+ "./styles.css": "./dist/styles.css"
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "test": "jest",
24
+ "test:watch": "jest --watch",
25
+ "test:coverage": "jest --coverage",
26
+ "typecheck": "tsc --noEmit",
27
+ "storybook": "storybook dev -p 6006 --disable-telemetry",
28
+ "build-storybook": "storybook build --disable-telemetry"
29
+ },
30
+ "peerDependencies": {
31
+ "react": "^18.0.0 || ^19.0.0",
32
+ "react-dom": "^18.0.0 || ^19.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@storybook/addon-essentials": "^8.6.14",
36
+ "@storybook/addon-interactions": "^8.6.14",
37
+ "@storybook/react": "^8.6.19",
38
+ "@storybook/react-vite": "^8.6.19",
39
+ "@testing-library/dom": "^10.4.2",
40
+ "@testing-library/jest-dom": "^6.6.3",
41
+ "@testing-library/react": "^16.2.0",
42
+ "@types/jest": "^29.5.14",
43
+ "@types/node": "^22.13.1",
44
+ "@types/react": "^19.0.8",
45
+ "@types/react-dom": "^19.0.3",
46
+ "@vitejs/plugin-react": "^6.1.2",
47
+ "jest": "^29.7.0",
48
+ "jest-environment-jsdom": "^29.7.0",
49
+ "react": "^19.0.0",
50
+ "react-dom": "^19.0.0",
51
+ "storybook": "^8.6.19",
52
+ "ts-jest": "^29.2.5",
53
+ "tsup": "^8.3.6",
54
+ "typescript": "^5.7.3",
55
+ "vite": "^8.3.4"
56
+ }
57
+ }