@livestorm/ui-vue 2.0.4 → 3.0.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 (89) hide show
  1. package/README.md +8 -5
  2. package/dist/UiTimelineItem.vue_vue_type_style_index_0_lang-LpdvGf5X.mjs +6093 -0
  3. package/dist/a11y-BhF_nl7L.mjs +159 -0
  4. package/dist/components.mjs +105 -96
  5. package/dist/index.mjs +68 -64
  6. package/dist/layouts.mjs +16 -16
  7. package/dist/types/index.d.ts +5 -1
  8. package/dist/types/src/components/UiActionBar/UiActionBar.vue.d.ts +3 -1
  9. package/dist/types/src/components/UiActionBar/UiActionBarItem.vue.d.ts +18 -3
  10. package/dist/types/src/components/UiActionBar/types.d.ts +3 -1
  11. package/dist/types/src/components/UiButton/UiButton.vue.d.ts +17 -13
  12. package/dist/types/src/components/UiButtonGroup/UiButtonGroupItem.vue.d.ts +3 -0
  13. package/dist/types/src/components/UiButtonGroup/types.d.ts +1 -1
  14. package/dist/types/src/components/UiDropdown/UiDropdownItem.vue.d.ts +6 -4
  15. package/dist/types/src/components/UiInputEmoji/UiInputEmoji.vue.d.ts +45 -0
  16. package/dist/types/src/components/UiInputEmoji/index.d.ts +1 -0
  17. package/dist/types/src/components/UiInputRadio/UiInputRadio.vue.d.ts +67 -0
  18. package/dist/types/src/components/UiInputRadio/UiInputRadioGroup.vue.d.ts +81 -0
  19. package/dist/types/src/components/UiInputRadio/index.d.ts +3 -0
  20. package/dist/types/src/components/UiInputRadio/types.d.ts +19 -0
  21. package/dist/types/src/components/UiLoader/UiLoader.vue.d.ts +6 -5
  22. package/dist/types/src/components/UiLogo/UiLogo.vue.d.ts +9 -3
  23. package/dist/types/src/components/UiPage/UiPage.vue.d.ts +34 -0
  24. package/dist/types/src/components/UiProgress/UiProgress.vue.d.ts +1 -1
  25. package/dist/types/src/components/UiSwitch/UiSwitch.vue.d.ts +109 -0
  26. package/dist/types/src/components/UiSwitch/index.d.ts +2 -0
  27. package/dist/types/src/components/UiSwitch/types.d.ts +3 -0
  28. package/dist/types/src/components/UiTabs/UiTabs.vue.d.ts +5 -2
  29. package/dist/types/src/composables/useContentMode.d.ts +25 -0
  30. package/dist/types/src/composables/useIconSize.d.ts +11 -0
  31. package/dist/types/src/constants/sizes.d.ts +1 -1
  32. package/dist/ui-vue.css +1 -1
  33. package/dist/{useThemeAndModes-CUK56j0z.mjs → useThemeAndModes-BzYwSOEv.mjs} +1 -1
  34. package/dist/utils.mjs +154 -99
  35. package/index.d.ts +4 -0
  36. package/index.js +8 -0
  37. package/package.json +11 -3
  38. package/src/components/Toast/ToastMessage.vue +6 -6
  39. package/src/components/UiActionBar/UiActionBar.vue +11 -2
  40. package/src/components/UiActionBar/UiActionBarItem.vue +56 -32
  41. package/src/components/UiActionBar/types.ts +3 -1
  42. package/src/components/UiAvatar/UiAvatar.vue +1 -1
  43. package/src/components/UiBanner/UiBanner.vue +7 -7
  44. package/src/components/UiButton/UiButton.vue +9 -9
  45. package/src/components/UiButtonGroup/UiButtonGroup.vue +4 -0
  46. package/src/components/UiButtonGroup/UiButtonGroupItem.vue +14 -0
  47. package/src/components/UiButtonGroup/types.ts +1 -1
  48. package/src/components/UiCarousel/UiCarousel.vue +2 -2
  49. package/src/components/UiColorPicker/UiColorPicker.vue +5 -5
  50. package/src/components/UiDialog/MarketplaceHeaderExample.vue +2 -2
  51. package/src/components/UiDialog/UiDialogClose.vue +1 -1
  52. package/src/components/UiDropdown/UiDropdownContent.vue +2 -2
  53. package/src/components/UiDropdown/UiDropdownItem.vue +8 -1
  54. package/src/components/UiDropdown/UiDropdownTrigger.vue +6 -7
  55. package/src/components/UiFieldWrapper/UiFieldWrapper.vue +1 -1
  56. package/src/components/UiInputCheckbox/UiInputCheckbox.vue +2 -2
  57. package/src/components/UiInputEmoji/UiInputEmoji.vue +173 -0
  58. package/src/components/UiInputEmoji/index.ts +1 -0
  59. package/src/components/UiInputRadio/UiInputRadio.vue +374 -0
  60. package/src/components/UiInputRadio/UiInputRadioGroup.vue +156 -0
  61. package/src/components/UiInputRadio/index.ts +3 -0
  62. package/src/components/UiInputRadio/types.ts +25 -0
  63. package/src/components/UiLoader/UiLoader.vue +8 -10
  64. package/src/components/UiLogo/UiLogo.vue +41 -7
  65. package/src/components/UiMenu/UiMenuContent.vue +1 -1
  66. package/src/components/UiMenu/UiMenuGroup.vue +1 -1
  67. package/src/components/UiMenu/UiMenuItem.vue +1 -1
  68. package/src/components/UiMenu/UiMenuSubContent.vue +1 -1
  69. package/src/components/UiMenu/UiMenuSubTrigger.vue +2 -2
  70. package/src/components/UiPage/UiPage.vue +102 -29
  71. package/src/components/UiPageHeader/UiPageHeader.vue +29 -9
  72. package/src/components/UiRoomButton/UiRoomButton.vue +4 -4
  73. package/src/components/UiShell/UiShell.vue +1 -1
  74. package/src/components/UiSideMenu/UiSideMenu.vue +13 -10
  75. package/src/components/UiSideMenu/UiSideMenuItemContent.vue +2 -2
  76. package/src/components/UiStepper/UiStepperIndicator.vue +8 -11
  77. package/src/components/UiSwitch/UiSwitch.vue +359 -0
  78. package/src/components/UiSwitch/index.ts +2 -0
  79. package/src/components/UiSwitch/types.ts +5 -0
  80. package/src/components/UiTabs/UiTabs.vue +7 -4
  81. package/src/components/UiTextField/UiTextField.vue +6 -11
  82. package/src/components/UiTimeline/README.md +6 -6
  83. package/src/components/UiTimeline/UiTimelineItem.vue +3 -3
  84. package/src/composables/useContentMode.ts +53 -0
  85. package/src/composables/useIconSize.ts +28 -0
  86. package/src/constants/sizes.ts +1 -1
  87. package/src/global.d.ts +8 -0
  88. package/dist/UiTimelineItem.vue_vue_type_style_index_0_lang-BtG5oABt.mjs +0 -5454
  89. package/dist/a11y--y7uaY4j.mjs +0 -217
package/dist/utils.mjs CHANGED
@@ -1,36 +1,36 @@
1
- import { BREAKPOINT_2XL as F, BREAKPOINT_LG as N, BREAKPOINT_MD as X, BREAKPOINT_SM as D, BREAKPOINT_XL as Q, BREAKPOINT_XL_PLUS as U } from "./breakpoints.mjs";
1
+ import { BREAKPOINT_2XL as Y, BREAKPOINT_LG as Z, BREAKPOINT_MD as ee, BREAKPOINT_SM as te, BREAKPOINT_XL as ne, BREAKPOINT_XL_PLUS as oe } from "./breakpoints.mjs";
2
2
  import "./bus.mjs";
3
- import { computed as h, createApp as _, h as f } from "vue";
4
- import { useClipboard as g, usePermission as x } from "@vueuse/core";
5
- import { u as R } from "./i18n-Cktw9hIu.mjs";
6
- import { b as A, _ as C, c as T, a as P } from "./a11y--y7uaY4j.mjs";
7
- import { f as j, h as G, d as z, u as H } from "./a11y--y7uaY4j.mjs";
8
- import { useResponsive as Y } from "./responsive.mjs";
9
- import { useRoute as $, useRouter as S } from "vue-router";
10
- import { RenderSlot as ee, getSlotAsComponent as te, unwrapFragments as oe, useSlotsAsComponents as ne } from "./slot.mjs";
11
- let d = null;
12
- function k(n, c) {
13
- d && d();
14
- const t = n.getBoundingClientRect(), s = document.createElement("div");
15
- document.body.appendChild(s);
16
- const a = _({
17
- render: () => f(A, null, () => f(C, { open: !0, delayDuration: 0 }, () => [
18
- f(T, { asChild: !0 }, () => f("span", {
3
+ import { computed as l, createApp as g, h as y, ref as T, watch as x, onUnmounted as k, getCurrentInstance as S, onMounted as _, nextTick as w } from "vue";
4
+ import { useClipboard as b, usePermission as A, onKeyDown as C } from "@vueuse/core";
5
+ import { u as E } from "./i18n-Cktw9hIu.mjs";
6
+ import { b as I, _ as R, c as B, a as P } from "./a11y-BhF_nl7L.mjs";
7
+ import { f as ie } from "./a11y-BhF_nl7L.mjs";
8
+ import { useResponsive as ue } from "./responsive.mjs";
9
+ import { useRoute as $, useRouter as q } from "vue-router";
10
+ import { RenderSlot as ae, getSlotAsComponent as de, unwrapFragments as le, useSlotsAsComponents as fe } from "./slot.mjs";
11
+ let v = null;
12
+ function L(s, i) {
13
+ v && v();
14
+ const e = s.getBoundingClientRect(), t = document.createElement("div");
15
+ document.body.appendChild(t);
16
+ const f = g({
17
+ render: () => y(I, null, () => y(R, { open: !0, delayDuration: 0 }, () => [
18
+ y(B, { asChild: !0 }, () => y("span", {
19
19
  "aria-hidden": "true",
20
20
  style: {
21
21
  position: "fixed",
22
- left: `${t.left}px`,
23
- top: `${t.top}px`,
24
- width: `${t.width}px`,
25
- height: `${t.height}px`,
22
+ left: `${e.left}px`,
23
+ top: `${e.top}px`,
24
+ width: `${e.width}px`,
25
+ height: `${e.height}px`,
26
26
  pointerEvents: "none"
27
27
  }
28
28
  })),
29
- f(P, { side: "top" }, () => c),
29
+ y(P, { side: "top" }, () => i),
30
30
  // The bubble lives in reka's portal where attrs like role are not
31
31
  // forwardable; announce the feedback from a visually hidden live
32
32
  // region instead so screen readers hear the copy confirmation.
33
- f("span", {
33
+ y("span", {
34
34
  role: "status",
35
35
  style: {
36
36
  position: "absolute",
@@ -39,108 +39,163 @@ function k(n, c) {
39
39
  overflow: "hidden",
40
40
  clipPath: "inset(50%)"
41
41
  }
42
- }, c)
42
+ }, i)
43
43
  ]))
44
44
  });
45
- a.mount(s);
46
- let p = !1;
47
- const r = () => {
48
- p || (p = !0, d === r && (d = null), a.unmount(), s.remove());
45
+ f.mount(t);
46
+ let a = !1;
47
+ const u = () => {
48
+ a || (a = !0, v === u && (v = null), f.unmount(), t.remove());
49
49
  };
50
- return d = r, r;
50
+ return v = u, u;
51
51
  }
52
- function O() {
53
- const { isSupported: n, copy: c } = g(), t = x("clipboard-write"), s = R(), a = h(() => n.value && t.value);
54
- async function p(r, i, m = s("general_misc_copied-text")) {
55
- if (await c(r), i) {
56
- i.classList.add("copied");
57
- const e = k(i, m);
52
+ function H() {
53
+ const { isSupported: s, copy: i } = b(), e = A("clipboard-write"), t = E(), f = l(() => s.value && e.value);
54
+ async function a(u, d, m = t("general_misc_copied-text")) {
55
+ if (await i(u), d) {
56
+ d.classList.add("copied");
57
+ const n = L(d, m);
58
58
  setTimeout(() => {
59
- i.classList.remove("copied"), e();
59
+ d.classList.remove("copied"), n();
60
60
  }, 600);
61
61
  }
62
62
  }
63
63
  return {
64
- canCopy: a,
65
- copy: p
64
+ canCopy: f,
65
+ copy: a
66
66
  };
67
67
  }
68
- function q(n, c = !1) {
69
- const t = $(), s = S(), a = `${n}-`;
70
- function p() {
71
- const e = [];
72
- for (const o in t.query)
73
- o.startsWith(a) && e.push(o);
74
- return e;
68
+ function Q(s, i = !1) {
69
+ const e = $(), t = q(), f = `${s}-`;
70
+ function a() {
71
+ const n = [];
72
+ for (const r in e.query)
73
+ r.startsWith(f) && n.push(r);
74
+ return n;
75
75
  }
76
- function r(e) {
77
- return e === "true" ? !0 : e === "false" ? !1 : e ?? !1;
76
+ function u(n) {
77
+ return n === "true" ? !0 : n === "false" ? !1 : n ?? !1;
78
78
  }
79
- const i = h(() => {
80
- if (c) {
81
- const e = p();
82
- if (e.length === 0)
79
+ const d = l(() => {
80
+ if (i) {
81
+ const n = a();
82
+ if (n.length === 0)
83
83
  return !1;
84
- const o = {};
85
- for (const u of e)
86
- o[u.substring(a.length)] = r(t.query[u]);
87
- return o;
84
+ const r = {};
85
+ for (const c of n)
86
+ r[c.substring(f.length)] = u(e.query[c]);
87
+ return r;
88
88
  } else
89
- return r(t.query[n]);
90
- }), m = !!i.value;
91
- return h({
92
- get: () => i.value,
93
- set: (e) => {
94
- if (e === i.value) return;
95
- const o = e == null || e === !1;
96
- if (o && !m)
97
- s.back();
89
+ return u(e.query[s]);
90
+ }), m = !!d.value;
91
+ return l({
92
+ get: () => d.value,
93
+ set: (n) => {
94
+ if (n === d.value) return;
95
+ const r = n == null || n === !1;
96
+ if (r && !m)
97
+ t.back();
98
98
  else {
99
- let u;
100
- if (c)
101
- if (u = {}, o) {
102
- const l = p();
103
- for (const y of l)
104
- u[y] = void 0;
99
+ let c;
100
+ if (i)
101
+ if (c = {}, r) {
102
+ const h = a();
103
+ for (const o of h)
104
+ c[o] = void 0;
105
105
  } else
106
- for (const l in e)
107
- u[`${n}-${l}`] = e[l];
106
+ for (const h in n)
107
+ c[`${s}-${h}`] = n[h];
108
108
  else
109
- u = {
110
- [n]: o ? void 0 : e
109
+ c = {
110
+ [s]: r ? void 0 : n
111
111
  };
112
- s[o ? "replace" : "push"]({
113
- ...t,
112
+ t[r ? "replace" : "push"]({
113
+ ...e,
114
114
  query: {
115
- ...t.query,
116
- ...u
115
+ ...e.query,
116
+ ...c
117
117
  }
118
118
  });
119
119
  }
120
120
  }
121
121
  });
122
122
  }
123
- let E = 0;
124
- function M() {
125
- return ++E;
123
+ let K = 0;
124
+ const p = T([]), O = l(() => p.value.length > 0);
125
+ function W(s, i) {
126
+ const e = K++, t = l(() => p.value.findIndex((o) => o.id === e));
127
+ function f() {
128
+ p.value.some((o) => o.id === e) || p.value.push({
129
+ id: e,
130
+ type: i
131
+ });
132
+ }
133
+ function a() {
134
+ t.value !== -1 && p.value.splice(t.value, 1);
135
+ }
136
+ x(s, (o) => {
137
+ o ? f() : a();
138
+ }, {
139
+ immediate: !0
140
+ }), k(() => {
141
+ a();
142
+ });
143
+ const u = l(() => e === p.value[p.value.length - 1]?.id), d = l(() => t.value > 0), m = l(() => p.value.filter((o) => o.type === i)), n = l(() => m.value.findIndex((o) => o.id === e) < m.value.length - 1), r = l(() => m.value.findIndex((o) => o.id === e) > 0), c = [];
144
+ function h(o) {
145
+ c.push(o);
146
+ }
147
+ return C("Escape", () => {
148
+ u.value && c.forEach((o) => o());
149
+ }), {
150
+ isActiveStacked: u,
151
+ hasStackedBehind: d,
152
+ isBehindSameType: n,
153
+ hasStackedSameTypeBehind: r,
154
+ stackIndex: t,
155
+ onClose: h
156
+ };
157
+ }
158
+ typeof document < "u" && x(O, (s) => {
159
+ s ? document.documentElement.classList.add("overflow-hidden") : document.documentElement.classList.remove("overflow-hidden");
160
+ }, {
161
+ immediate: !0
162
+ });
163
+ function j() {
164
+ const s = S();
165
+ let i = null;
166
+ _(async () => {
167
+ await w();
168
+ const e = s.proxy.$el;
169
+ if (!e?.querySelector) return;
170
+ let t = e.querySelector("[data-stackview-autofocus]");
171
+ t ? ["INPUT", "A", "BUTTON", "TEXTAREA"].includes(t.tagName) || (t = t.querySelector("input, a, button, textarea") || t) : (t = e.querySelector('button[type="submit"]'), console.warn(
172
+ "%cDEV",
173
+ "background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;",
174
+ "A element should have the data-stackview-autofocus attribute in this component. Fallback to submit button."
175
+ )), clearTimeout(i), i = setTimeout(() => t?.focus(), 150);
176
+ });
177
+ }
178
+ let U = 0;
179
+ function G() {
180
+ return ++U;
126
181
  }
127
182
  export {
128
- F as BREAKPOINT_2XL,
129
- N as BREAKPOINT_LG,
130
- X as BREAKPOINT_MD,
131
- D as BREAKPOINT_SM,
132
- Q as BREAKPOINT_XL,
133
- U as BREAKPOINT_XL_PLUS,
134
- ee as RenderSlot,
135
- j as focusFirstMenuItem,
136
- te as getSlotAsComponent,
137
- M as getUid,
138
- G as hasStackedComponents,
139
- oe as unwrapFragments,
140
- O as useCopyToClipboard,
141
- Y as useResponsive,
142
- q as useRouteQueryModel,
143
- ne as useSlotsAsComponents,
144
- z as useStackAutofocus,
145
- H as useStackedComponent
183
+ Y as BREAKPOINT_2XL,
184
+ Z as BREAKPOINT_LG,
185
+ ee as BREAKPOINT_MD,
186
+ te as BREAKPOINT_SM,
187
+ ne as BREAKPOINT_XL,
188
+ oe as BREAKPOINT_XL_PLUS,
189
+ ae as RenderSlot,
190
+ ie as focusFirstMenuItem,
191
+ de as getSlotAsComponent,
192
+ G as getUid,
193
+ O as hasStackedComponents,
194
+ le as unwrapFragments,
195
+ H as useCopyToClipboard,
196
+ ue as useResponsive,
197
+ Q as useRouteQueryModel,
198
+ fe as useSlotsAsComponents,
199
+ j as useStackAutofocus,
200
+ W as useStackedComponent
146
201
  };
package/index.d.ts CHANGED
@@ -35,6 +35,9 @@ export { default as UiFieldWrapper } from './src/components/UiFieldWrapper/UiFie
35
35
  export { default as UiFormField } from './src/components/UiForm/UiFormField.vue'
36
36
  export { default as UiHorizontalSeparator } from './src/components/UiHorizontalSeparator/UiHorizontalSeparator.vue'
37
37
  export { default as UiInputCheckbox } from './src/components/UiInputCheckbox/UiInputCheckbox.vue'
38
+ export { default as UiInputEmoji } from './src/components/UiInputEmoji/UiInputEmoji.vue'
39
+ export { default as UiInputRadio } from './src/components/UiInputRadio/UiInputRadio.vue'
40
+ export { default as UiInputRadioGroup } from './src/components/UiInputRadio/UiInputRadioGroup.vue'
38
41
  export { default as UiInputSelect } from './src/components/UiInputSelect/UiInputSelect.vue'
39
42
  export { default as UiInputTextarea } from './src/components/UiInputTextarea/UiInputTextarea.vue'
40
43
  export { default as UiLabel } from './src/components/UiLabel/UiLabel.vue'
@@ -72,6 +75,7 @@ export { default as UiStepperIndicator } from './src/components/UiStepper/UiStep
72
75
  export { default as UiStepperItem } from './src/components/UiStepper/UiStepperItem.vue'
73
76
  export { default as UiStepperSeparator } from './src/components/UiStepper/UiStepperSeparator.vue'
74
77
  export { default as UiStepperTitle } from './src/components/UiStepper/UiStepperTitle.vue'
78
+ export { default as UiSwitch } from './src/components/UiSwitch/UiSwitch.vue'
75
79
  export { default as UiTable } from './src/components/UiTable/UiTable.vue'
76
80
  export { default as UiTableBody } from './src/components/UiTable/UiTableBody.vue'
77
81
  export { default as UiTableCell } from './src/components/UiTable/UiTableCell.vue'
package/index.js CHANGED
@@ -37,6 +37,9 @@ import UiFieldWrapper from './src/components/UiFieldWrapper/UiFieldWrapper.vue'
37
37
  import UiFormField from './src/components/UiForm/UiFormField.vue'
38
38
  import UiHorizontalSeparator from './src/components/UiHorizontalSeparator/UiHorizontalSeparator.vue'
39
39
  import UiInputCheckbox from './src/components/UiInputCheckbox/UiInputCheckbox.vue'
40
+ import UiInputEmoji from './src/components/UiInputEmoji/UiInputEmoji.vue'
41
+ import UiInputRadio from './src/components/UiInputRadio/UiInputRadio.vue'
42
+ import UiInputRadioGroup from './src/components/UiInputRadio/UiInputRadioGroup.vue'
40
43
  import UiInputSelect from './src/components/UiInputSelect/UiInputSelect.vue'
41
44
  import UiInputTextarea from './src/components/UiInputTextarea/UiInputTextarea.vue'
42
45
  import UiLabel from './src/components/UiLabel/UiLabel.vue'
@@ -74,6 +77,7 @@ import UiStepperIndicator from './src/components/UiStepper/UiStepperIndicator.vu
74
77
  import UiStepperItem from './src/components/UiStepper/UiStepperItem.vue'
75
78
  import UiStepperSeparator from './src/components/UiStepper/UiStepperSeparator.vue'
76
79
  import UiStepperTitle from './src/components/UiStepper/UiStepperTitle.vue'
80
+ import UiSwitch from './src/components/UiSwitch/UiSwitch.vue'
77
81
  import UiTable from './src/components/UiTable/UiTable.vue'
78
82
  import UiTableBody from './src/components/UiTable/UiTableBody.vue'
79
83
  import UiTableCell from './src/components/UiTable/UiTableCell.vue'
@@ -122,6 +126,9 @@ export {
122
126
  UiFormField,
123
127
  UiHorizontalSeparator,
124
128
  UiInputCheckbox,
129
+ UiInputEmoji,
130
+ UiInputRadio,
131
+ UiInputRadioGroup,
125
132
  UiInputSelect,
126
133
  UiInputTextarea,
127
134
  UiLabel,
@@ -159,6 +166,7 @@ export {
159
166
  UiStepperItem,
160
167
  UiStepperSeparator,
161
168
  UiStepperTitle,
169
+ UiSwitch,
162
170
  UiTable,
163
171
  UiTableBody,
164
172
  UiTableCell,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livestorm/ui-vue",
3
- "version": "2.0.4",
3
+ "version": "3.0.0",
4
4
  "description": "Livestorm UI components for Vue 3",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -11,6 +11,11 @@
11
11
  "main": "./dist/index.mjs",
12
12
  "module": "./dist/index.mjs",
13
13
  "types": "./dist/types/index.d.ts",
14
+ "sideEffects": [
15
+ "**/*.css",
16
+ "./index.js",
17
+ "./style.js"
18
+ ],
14
19
  "files": [
15
20
  "dist",
16
21
  "src",
@@ -38,11 +43,14 @@
38
43
  "i18next": "^26.3.4",
39
44
  "mitt": "^3.0.1",
40
45
  "reka-ui": "^2.9.10",
41
- "@livestorm/ui-tokens": "^2.0.4",
42
- "@livestorm/ui-icons": "^2.0.4"
46
+ "vue-frimousse": "^0.1.4",
47
+ "@livestorm/ui-tokens": "^3.0.0",
48
+ "@livestorm/ui-icons": "^3.0.0"
43
49
  },
44
50
  "devDependencies": {
45
51
  "@chromatic-com/storybook": "^5.2.1",
52
+ "@iconify-json/ph": "^1.2.2",
53
+ "@iconify/utils": "^3.1.4",
46
54
  "@storybook/addon-a11y": "^10.5.5",
47
55
  "@storybook/addon-coverage": "^3.0.2",
48
56
  "@storybook/addon-designs": "^11.1.4",
@@ -31,13 +31,13 @@ const modalIcon = computed(() => {
31
31
 
32
32
  switch (props.type) {
33
33
  case 'alert':
34
- return 'i-ls-x-circle !text-text-danger-base'
34
+ return 'i-ph-x-circle !text-text-danger-base'
35
35
  case 'warning':
36
- return 'i-ls-alert-triangle !text-text-warning-base'
36
+ return 'i-ph-warning !text-text-warning-base'
37
37
  case 'info':
38
- return 'i-ls-info text-text-primary-base'
38
+ return 'i-ph-info text-text-primary-base'
39
39
  case 'success':
40
- return 'i-ls-check-circle text-text-success-base'
40
+ return 'i-ph-check-circle text-text-success-base'
41
41
  default:
42
42
  return undefined
43
43
  }
@@ -137,7 +137,7 @@ onMounted(() => {
137
137
  <div class="shrink-0">
138
138
  <div
139
139
  v-if="modalIcon"
140
- class="w-4 h-4 mt-0.5 mr-3 shrink-0 mb-2 sm:mb-0"
140
+ class="size-5 mr-3 shrink-0 mb-2 sm:mb-0"
141
141
  :class="modalIcon"
142
142
  />
143
143
  </div>
@@ -167,7 +167,7 @@ onMounted(() => {
167
167
  :data-testid="`toast-close-${String(props.id)}`"
168
168
  color="neutral"
169
169
  variant="tertiary"
170
- icon="i-ls-x"
170
+ icon="i-ph-x"
171
171
  size="md"
172
172
  aria-label="close"
173
173
  class="-top-[10px] -right-[2px] sm:row-start-1"
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, provide } from 'vue'
3
- import type { ActionBarOrientation, ActionBarPosition, ActionBarSize } from './types'
3
+ import type { ActionBarOrientation, ActionBarPosition, ActionBarSize, ActionBarTooltipSide } from './types'
4
4
  import { ACTION_BAR_CONTEXT_KEY } from './types'
5
5
 
6
6
  interface UiActionBarProps {
@@ -26,6 +26,7 @@ interface UiActionBarProps {
26
26
  * Accessible label for the toolbar landmark. Defaults to "Actions".
27
27
  */
28
28
  ariaLabel?: string
29
+ tooltipSide?: ActionBarTooltipSide
29
30
  }
30
31
 
31
32
  const props = withDefaults(defineProps<UiActionBarProps>(), {
@@ -34,17 +35,25 @@ const props = withDefaults(defineProps<UiActionBarProps>(), {
34
35
  position: undefined,
35
36
  offset: 3,
36
37
  ariaLabel: 'Actions',
38
+ tooltipSide: undefined,
37
39
  })
38
40
 
39
41
  provide(ACTION_BAR_CONTEXT_KEY, {
40
42
  orientation: computed<ActionBarOrientation>(() => props.orientation),
41
43
  size: computed<ActionBarSize>(() => props.size),
44
+ tooltipSide: computed<ActionBarTooltipSide | undefined>(() => props.tooltipSide),
42
45
  })
43
46
 
44
47
  const isVertical = computed(() => props.orientation === 'vertical')
45
48
 
46
49
  // Inner padding follows the design tokens (space-0-5 = 2px).
47
- const sizeClasses = computed(() => (props.size === 'sm' ? 'p-0.5 gap-0.5' : 'p-1 gap-0.5'))
50
+ const SIZE_CLASSES: Record<ActionBarSize, string> = {
51
+ xs: 'p-0.5 gap-0.5',
52
+ sm: 'p-0.5 gap-0.5',
53
+ md: 'p-1 gap-0.5',
54
+ }
55
+
56
+ const sizeClasses = computed(() => SIZE_CLASSES[props.size])
48
57
 
49
58
  // When `position` is set, anchor the bar to an edge of its positioned
50
59
  // ancestor. Offsets are inline styles (rem) so any `offset` value works
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { computed, inject } from 'vue'
2
+ import { computed, inject, useAttrs } from 'vue'
3
3
  import {
4
4
  TooltipProvider,
5
5
  TooltipRoot,
@@ -9,16 +9,21 @@ import {
9
9
  TooltipArrow,
10
10
  } from 'reka-ui'
11
11
  import type { RouteLocationRaw } from 'vue-router'
12
+ import { useIconSize } from '../../composables/useIconSize'
13
+ import type { ActionBarSize } from './types'
12
14
  import { ACTION_BAR_CONTEXT_KEY } from './types'
13
15
 
14
16
  interface UiActionBarItemProps {
15
- /** Icon class, e.g. `i-ls-trash`. */
17
+ /**
18
+ * Icon class, e.g. `i-ph-trash`. Ignored when the `icon` slot is filled —
19
+ * use the slot for glyphs no icon class covers (emoji, an avatar, a dot).
20
+ */
16
21
  icon?: string
17
22
  /** Visible/accessible label. Required so icon-only items stay accessible. */
18
23
  label: string
19
24
  /**
20
25
  * Optional icon class rendered next to the label inside the hover tooltip —
21
- * e.g. `i-ls-external-link` to flag that the action opens in a new tab.
26
+ * e.g. `i-ph-arrow-square-out` to flag that the action opens in a new tab.
22
27
  * Only shown for icon-only items (the tooltip is suppressed when
23
28
  * `showLabel` is set).
24
29
  */
@@ -55,13 +60,20 @@ const props = withDefaults(defineProps<UiActionBarItemProps>(), {
55
60
 
56
61
  const emit = defineEmits<{ click: [event: MouseEvent] }>()
57
62
 
63
+ // The trigger is rendered through `TooltipTrigger as-child`, which merges its
64
+ // own props over the child vnode — so normal attribute fallthrough never
65
+ // reaches the button. Read the consumer's `data-testid` explicitly, otherwise
66
+ // every item in a wrapped bar would share one indistinguishable test id.
67
+ const attrs = useAttrs()
68
+ const testId = computed(() => (attrs['data-testid'] as string | undefined) ?? 'ui-action-bar-item')
69
+
58
70
  const ctx = inject(ACTION_BAR_CONTEXT_KEY, null)
59
71
  const size = computed(() => ctx?.size.value ?? 'md')
60
72
  const orientation = computed(() => ctx?.orientation.value ?? 'horizontal')
61
73
 
62
74
  // Icon-only items get a tooltip; labeled items don't need one.
63
75
  const showTooltip = computed(() => !props.showLabel)
64
- const tooltipSide = computed(() => (orientation.value === 'vertical' ? 'right' : 'bottom'))
76
+ const tooltipSide = computed(() => ctx?.tooltipSide.value ?? (orientation.value === 'vertical' ? 'right' : 'bottom'))
65
77
 
66
78
  const component = computed(() => {
67
79
  if (props.disabled) return { is: 'button', props: { type: 'button', disabled: true } }
@@ -70,22 +82,30 @@ const component = computed(() => {
70
82
  return { is: 'button', props: { type: 'button' } }
71
83
  })
72
84
 
73
- const sizeClasses = computed(() => {
74
- const square = size.value === 'sm' ? 'size-8' : 'size-10'
75
- // Labeled items grow with their text instead of staying square.
76
- const padded = size.value === 'sm' ? 'h-8 px-2.5 gap-1' : 'h-10 px-3 gap-1.5'
77
- return props.showLabel ? padded : square
78
- })
85
+ // Per-size layout classes. `square` sizes icon-only items; labeled items use
86
+ // `padded` and grow with their text instead of staying square. xs reuses the
87
+ // sm text style — the design system defines no xs one.
88
+ const SIZE_CLASSES: Record<ActionBarSize, { square: string, padded: string, text: string }> = {
89
+ xs: { square: 'size-6', padded: 'h-6 px-2 gap-1', text: 'text-action-button-sm' },
90
+ sm: { square: 'size-8', padded: 'h-8 px-2.5 gap-1', text: 'text-action-button-sm' },
91
+ md: { square: 'size-10', padded: 'h-10 px-3 gap-1.5', text: 'text-action-button-md' },
92
+ }
79
93
 
80
- const iconSizeClass = computed(() => (size.value === 'sm' ? 'size-4' : 'size-5'))
81
- const textClass = computed(() => (size.value === 'sm' ? 'text-action-button-sm' : 'text-action-button-md'))
94
+ const sizeConfig = computed(() => SIZE_CLASSES[size.value])
95
+ const iconSizeClass = useIconSize(size)
82
96
 
83
- // Persistent pressed background when the item is an active toggle. Uses the
84
- // dedicated neutral "active" token so an active-but-not-hovered toggle stays
85
- // visually distinct from a merely hovered item.
86
- const stateClasses = computed(() => ({
87
- 'bg-actions-neutral-active-overlay': props.active === true,
88
- }))
97
+ // Everything bound on the item root: per-size layout and text classes, plus
98
+ // the persistent pressed background when the item is an active toggle (the
99
+ // dedicated neutral "active" token keeps an active-but-not-hovered toggle
100
+ // visually distinct from a merely hovered item).
101
+ const itemClasses = computed(() => [
102
+ props.showLabel ? sizeConfig.value.padded : sizeConfig.value.square,
103
+ sizeConfig.value.text,
104
+ {
105
+ 'bg-actions-neutral-active-overlay': props.active === true,
106
+ 'cursor-pointer': !props.disabled,
107
+ },
108
+ ])
89
109
 
90
110
  function onClick (event: MouseEvent) {
91
111
  if (props.disabled) {
@@ -113,20 +133,22 @@ function onClick (event: MouseEvent) {
113
133
  v-bind="component.props"
114
134
  :aria-label="label"
115
135
  :aria-pressed="active"
116
- data-testid="ui-action-bar-item"
136
+ :data-testid="testId"
117
137
  class="group inline-flex items-center justify-center rounded-lg shrink-0 outline-none transition-colors
118
138
  text-actions-neutral-idle
119
139
  hover:bg-actions-neutral-hover-overlay
120
140
  focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus-ring
121
141
  disabled:opacity-50 disabled:cursor-not-allowed"
122
- :class="[sizeClasses, textClass, stateClasses, { 'cursor-pointer': !disabled }]"
142
+ :class="itemClasses"
123
143
  @click="onClick"
124
144
  >
125
- <span
126
- v-if="icon"
127
- :class="[icon, iconSizeClass]"
128
- class="shrink-0"
129
- />
145
+ <slot name="icon">
146
+ <span
147
+ v-if="icon"
148
+ :class="[icon, iconSizeClass]"
149
+ class="shrink-0"
150
+ />
151
+ </slot>
130
152
  </component>
131
153
  </TooltipTrigger>
132
154
  <TooltipPortal>
@@ -160,20 +182,22 @@ function onClick (event: MouseEvent) {
160
182
  v-bind="component.props"
161
183
  :aria-label="label"
162
184
  :aria-pressed="active"
163
- data-testid="ui-action-bar-item"
185
+ :data-testid="testId"
164
186
  class="group inline-flex items-center justify-center rounded-lg shrink-0 outline-none transition-colors
165
187
  text-actions-neutral-idle
166
188
  hover:bg-actions-neutral-hover-overlay
167
189
  focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus-ring
168
190
  disabled:opacity-50 disabled:cursor-not-allowed"
169
- :class="[sizeClasses, textClass, stateClasses, { 'cursor-pointer': !disabled }]"
191
+ :class="itemClasses"
170
192
  @click="onClick"
171
193
  >
172
- <span
173
- v-if="icon"
174
- :class="[icon, iconSizeClass]"
175
- class="shrink-0"
176
- />
194
+ <slot name="icon">
195
+ <span
196
+ v-if="icon"
197
+ :class="[icon, iconSizeClass]"
198
+ class="shrink-0"
199
+ />
200
+ </slot>
177
201
  <span class="whitespace-nowrap">{{ label }}</span>
178
202
  </component>
179
203
  </template>
@@ -1,7 +1,7 @@
1
1
  import type { ComputedRef, InjectionKey } from 'vue'
2
2
 
3
3
  export type ActionBarOrientation = 'horizontal' | 'vertical'
4
- export type ActionBarSize = 'sm' | 'md'
4
+ export type ActionBarSize = 'xs' | 'sm' | 'md'
5
5
  export type ActionBarPosition =
6
6
  | 'top-left'
7
7
  | 'top-right'
@@ -9,10 +9,12 @@ export type ActionBarPosition =
9
9
  | 'center-right'
10
10
  | 'bottom-left'
11
11
  | 'bottom-right'
12
+ export type ActionBarTooltipSide = 'top' | 'right' | 'bottom' | 'left'
12
13
 
13
14
  export interface ActionBarContext {
14
15
  orientation: ComputedRef<ActionBarOrientation>
15
16
  size: ComputedRef<ActionBarSize>
17
+ tooltipSide: ComputedRef<ActionBarTooltipSide | undefined>
16
18
  }
17
19
 
18
20
  /**