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/LICENSE +21 -0
- package/README.md +194 -2
- package/dist/index.d.mts +188 -0
- package/dist/index.d.ts +188 -0
- package/dist/index.js +487 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +441 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +125 -0
- package/package.json +55 -4
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"]}
|
package/dist/styles.css
ADDED
|
@@ -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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
}
|