@flux-ui/components 3.1.2 → 3.1.4

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 (72) hide show
  1. package/dist/component/FluxAvatarGroup.vue.d.ts +17 -0
  2. package/dist/component/FluxButton.vue.d.ts +2 -0
  3. package/dist/component/FluxContextMenu.vue.d.ts +26 -0
  4. package/dist/component/FluxDataTable.vue.d.ts +20 -10
  5. package/dist/component/FluxDescriptionItem.vue.d.ts +19 -0
  6. package/dist/component/FluxDescriptionList.vue.d.ts +17 -0
  7. package/dist/component/FluxFlyout.vue.d.ts +9 -2
  8. package/dist/component/FluxFormCombobox.vue.d.ts +20 -0
  9. package/dist/component/FluxFormRating.vue.d.ts +21 -0
  10. package/dist/component/FluxFormTagsInput.vue.d.ts +27 -0
  11. package/dist/component/FluxFormTextArea.vue.d.ts +6 -1
  12. package/dist/component/FluxInlineEdit.vue.d.ts +41 -0
  13. package/dist/component/FluxMenu.vue.d.ts +1 -0
  14. package/dist/component/FluxMenuFlyout.vue.d.ts +22 -0
  15. package/dist/component/FluxTableCell.vue.d.ts +1 -0
  16. package/dist/component/FluxTour.vue.d.ts +35 -0
  17. package/dist/component/FluxTourItem.vue.d.ts +18 -0
  18. package/dist/component/FluxVirtualScroller.vue.d.ts +27 -0
  19. package/dist/component/index.d.ts +12 -0
  20. package/dist/component/primitive/AnchorPopup.vue.d.ts +7 -1
  21. package/dist/component/primitive/SelectBase.vue.d.ts +3 -0
  22. package/dist/composable/private/index.d.ts +1 -0
  23. package/dist/composable/private/useMenuFlyout.d.ts +42 -0
  24. package/dist/data/di.d.ts +35 -0
  25. package/dist/data/i18n.d.ts +7 -0
  26. package/dist/index.css +449 -5
  27. package/dist/index.js +2156 -408
  28. package/dist/index.js.map +1 -1
  29. package/package.json +7 -7
  30. package/src/component/FluxAvatarGroup.vue +52 -0
  31. package/src/component/FluxButton.vue +3 -0
  32. package/src/component/FluxContextMenu.vue +134 -0
  33. package/src/component/FluxDataTable.vue +113 -32
  34. package/src/component/FluxDescriptionItem.vue +43 -0
  35. package/src/component/FluxDescriptionList.vue +37 -0
  36. package/src/component/FluxDestructiveButton.vue +2 -1
  37. package/src/component/FluxFlyout.vue +16 -3
  38. package/src/component/FluxFormCombobox.vue +98 -0
  39. package/src/component/FluxFormRating.vue +172 -0
  40. package/src/component/FluxFormTagsInput.vue +249 -0
  41. package/src/component/FluxFormTextArea.vue +16 -1
  42. package/src/component/FluxInlineEdit.vue +176 -0
  43. package/src/component/FluxMenu.vue +13 -3
  44. package/src/component/FluxMenuFlyout.vue +118 -0
  45. package/src/component/FluxPrimaryButton.vue +2 -1
  46. package/src/component/FluxPrimaryLinkButton.vue +2 -1
  47. package/src/component/FluxPublishButton.vue +2 -1
  48. package/src/component/FluxSecondaryButton.vue +2 -1
  49. package/src/component/FluxSecondaryLinkButton.vue +2 -1
  50. package/src/component/FluxTableCell.vue +2 -0
  51. package/src/component/FluxTour.vue +332 -0
  52. package/src/component/FluxTourItem.vue +27 -0
  53. package/src/component/FluxVirtualScroller.vue +96 -0
  54. package/src/component/index.ts +12 -0
  55. package/src/component/primitive/AnchorPopup.vue +27 -0
  56. package/src/component/primitive/SelectBase.vue +37 -2
  57. package/src/composable/private/index.ts +1 -0
  58. package/src/composable/private/useMenuFlyout.ts +417 -0
  59. package/src/css/component/AvatarGroup.module.scss +22 -0
  60. package/src/css/component/ContextMenu.module.scss +17 -0
  61. package/src/css/component/DescriptionList.module.scss +98 -0
  62. package/src/css/component/Form.module.scss +51 -0
  63. package/src/css/component/FormRating.module.scss +47 -0
  64. package/src/css/component/InlineEdit.module.scss +45 -0
  65. package/src/css/component/Menu.module.scss +4 -1
  66. package/src/css/component/MenuFlyout.module.scss +38 -0
  67. package/src/css/component/Table.module.scss +16 -0
  68. package/src/css/component/Tour.module.scss +108 -0
  69. package/src/css/component/VirtualScroller.module.scss +17 -0
  70. package/src/css/mixin/button-active.scss +3 -1
  71. package/src/data/di.ts +40 -0
  72. package/src/data/i18n.ts +7 -0
@@ -0,0 +1,417 @@
1
+ import { animationFrameDebounce, isSSR } from '@flux-ui/internals';
2
+ import { inject, nextTick, onMounted, onUnmounted, provide, ref, type ComponentPublicInstance, type Ref, watch } from 'vue';
3
+ import { FluxMenuFlyoutInjectionKey, type FluxMenuFlyoutCone, type FluxMenuFlyoutEntry, type FluxMenuFlyoutInjection, type FluxMenuFlyoutPointer } from '~flux/components/data';
4
+
5
+ let flyoutId = 0;
6
+
7
+ export type UseMenuFlyoutProviderOptions = {
8
+ readonly debugCone: Ref<boolean>;
9
+ readonly onCloseAll?: () => void;
10
+ };
11
+
12
+ export type UseMenuFlyoutOptions = {
13
+ readonly triggerRef: Ref<ComponentPublicInstance | HTMLElement | null>;
14
+ readonly popupRef: Ref<ComponentPublicInstance | HTMLElement | null>;
15
+ readonly disabled?: Ref<boolean>;
16
+ };
17
+
18
+ export type UseMenuFlyoutReturn = {
19
+ readonly context: FluxMenuFlyoutInjection | null;
20
+ readonly cone: Ref<FluxMenuFlyoutCone | null>;
21
+ readonly isOpen: Ref<boolean>;
22
+ closeAll(): void;
23
+ focusTrigger(): void;
24
+ onPopupKeydown(evt: KeyboardEvent): void;
25
+ onTriggerClick(evt: MouseEvent): void;
26
+ onTriggerKeydown(evt: KeyboardEvent): void;
27
+ };
28
+
29
+ /**
30
+ * Creates and provides the shared menu-flyout context for an entire open menu tree. Used by the
31
+ * outermost menu surface (FluxContextMenu, or a standalone root FluxMenu). Tracks the pointer, the
32
+ * active prediction cone, the keyboard-trap stack and the set of teleported flyout popups so that
33
+ * click-outside and close-all can reason about the whole tree.
34
+ */
35
+ export function useMenuFlyoutProvider(options: UseMenuFlyoutProviderOptions): FluxMenuFlyoutInjection {
36
+ const {debugCone, onCloseAll} = options;
37
+ const entries = new Set<FluxMenuFlyoutEntry>();
38
+
39
+ const pointer = ref<FluxMenuFlyoutPointer>({x: 0, y: 0, px: 0, py: 0});
40
+ const activeCone = ref<FluxMenuFlyoutCone | null>(null);
41
+ const keyboardStack = ref<number[]>([]);
42
+
43
+ let pointerX = 0;
44
+ let pointerY = 0;
45
+
46
+ // pointermove can fire several times per frame (high-poll-rate mice), and every flyout entry
47
+ // reacts to the pointer ref with synchronous getBoundingClientRect reads. Coalesce updates to
48
+ // one per animation frame so those layout reads happen at most once per frame instead of per event.
49
+ const flushPointer = animationFrameDebounce(() => {
50
+ const previous = pointer.value;
51
+ pointer.value = {x: pointerX, y: pointerY, px: previous.x, py: previous.y};
52
+ });
53
+
54
+ function onPointerMove(evt: PointerEvent): void {
55
+ pointerX = evt.clientX;
56
+ pointerY = evt.clientY;
57
+ flushPointer();
58
+ }
59
+
60
+ function startTracking(): void {
61
+ if (isSSR) {
62
+ return;
63
+ }
64
+
65
+ window.addEventListener('pointermove', onPointerMove, {capture: true, passive: true});
66
+ }
67
+
68
+ function stopTracking(): void {
69
+ if (isSSR) {
70
+ return;
71
+ }
72
+
73
+ window.removeEventListener('pointermove', onPointerMove, {capture: true});
74
+ }
75
+
76
+ const context: FluxMenuFlyoutInjection = {
77
+ debugCone,
78
+ pointer,
79
+ activeCone,
80
+ keyboardStack,
81
+
82
+ register(entry: FluxMenuFlyoutEntry): void {
83
+ if (entries.size === 0) {
84
+ startTracking();
85
+ }
86
+
87
+ entries.add(entry);
88
+ },
89
+
90
+ unregister(entry: FluxMenuFlyoutEntry): void {
91
+ entries.delete(entry);
92
+
93
+ if (entries.size === 0) {
94
+ stopTracking();
95
+ activeCone.value = null;
96
+ }
97
+ },
98
+
99
+ closeOthers(self: FluxMenuFlyoutEntry): void {
100
+ const trigger = self.getTrigger();
101
+
102
+ for (const entry of entries) {
103
+ if (entry === self || !entry.isOpen.value) {
104
+ continue;
105
+ }
106
+
107
+ const popup = entry.getPopup();
108
+
109
+ if (popup && trigger && popup.contains(trigger)) {
110
+ continue;
111
+ }
112
+
113
+ entry.close();
114
+ }
115
+ },
116
+
117
+ hasOpenDescendant(self: FluxMenuFlyoutEntry): boolean {
118
+ const popup = self.getPopup();
119
+
120
+ if (!popup) {
121
+ return false;
122
+ }
123
+
124
+ for (const entry of entries) {
125
+ if (entry === self || !entry.isOpen.value) {
126
+ continue;
127
+ }
128
+
129
+ const trigger = entry.getTrigger();
130
+
131
+ if (trigger && popup.contains(trigger)) {
132
+ return true;
133
+ }
134
+ }
135
+
136
+ return false;
137
+ },
138
+
139
+ isAimingAtOpenSubmenu(): boolean {
140
+ const {x, y, px, py} = pointer.value;
141
+
142
+ for (const entry of entries) {
143
+ if (!entry.isOpen.value) {
144
+ continue;
145
+ }
146
+
147
+ const trigger = entry.getTrigger();
148
+ const popup = entry.getPopup();
149
+
150
+ if (!trigger || !popup) {
151
+ continue;
152
+ }
153
+
154
+ const t = trigger.getBoundingClientRect();
155
+ const r = popup.getBoundingClientRect();
156
+ const edgeX = r.left >= t.right ? r.left : (r.right <= t.left ? r.right : r.left);
157
+
158
+ if (pointInTriangle(x, y, px, py, edgeX, r.top, edgeX, r.bottom)) {
159
+ return true;
160
+ }
161
+ }
162
+
163
+ return false;
164
+ },
165
+
166
+ isInsidePopups(target: Node | null): boolean {
167
+ if (!target) {
168
+ return false;
169
+ }
170
+
171
+ for (const entry of entries) {
172
+ const popup = entry.getPopup();
173
+
174
+ if (popup && popup.contains(target)) {
175
+ return true;
176
+ }
177
+ }
178
+
179
+ return false;
180
+ },
181
+
182
+ closeAll(): void {
183
+ if (onCloseAll) {
184
+ onCloseAll();
185
+ return;
186
+ }
187
+
188
+ for (const entry of entries) {
189
+ entry.close();
190
+ }
191
+ }
192
+ };
193
+
194
+ onUnmounted(stopTracking);
195
+
196
+ provide(FluxMenuFlyoutInjectionKey, context);
197
+
198
+ return context;
199
+ }
200
+
201
+ /**
202
+ * Returns the shared menu-flyout context, creating (and providing) a new root context when none
203
+ * exists higher up the tree. Used by FluxMenu so that a standalone menu becomes the root while a
204
+ * menu nested inside a FluxContextMenu or another flyout inherits the existing context.
205
+ */
206
+ export function useMenuFlyoutContext(options: UseMenuFlyoutProviderOptions): FluxMenuFlyoutInjection {
207
+ const parent = inject(FluxMenuFlyoutInjectionKey, null);
208
+
209
+ if (parent) {
210
+ return parent;
211
+ }
212
+
213
+ return useMenuFlyoutProvider(options);
214
+ }
215
+
216
+ /**
217
+ * Per-flyout open/close and prediction-cone behaviour for FluxMenuFlyout. Submenus open instantly
218
+ * on hover and close instantly once the pointer is neither over the trigger/popup (or an open
219
+ * descendant) nor aiming at the submenu through the prediction cone (the safe triangle). There are
220
+ * no open/close delays — the cone is the only thing that keeps a submenu open during a diagonal
221
+ * move, which is also why the debug cone only shows while it actually applies.
222
+ */
223
+ export default function useMenuFlyout(options: UseMenuFlyoutOptions): UseMenuFlyoutReturn {
224
+ const {triggerRef, popupRef, disabled} = options;
225
+ const context = inject(FluxMenuFlyoutInjectionKey, null);
226
+
227
+ const id = ++flyoutId;
228
+ const isOpen = ref(false);
229
+ const openSource = ref<'pointer' | 'keyboard'>('pointer');
230
+ const cone = ref<FluxMenuFlyoutCone | null>(null);
231
+
232
+ const entry: FluxMenuFlyoutEntry = {
233
+ getTrigger: () => elementOf(triggerRef.value),
234
+ getPopup: () => elementOf(popupRef.value),
235
+ isOpen,
236
+ close: () => doClose()
237
+ };
238
+
239
+ function open(source: 'pointer' | 'keyboard'): void {
240
+ if (disabled?.value) {
241
+ return;
242
+ }
243
+
244
+ openSource.value = source;
245
+
246
+ if (context && source === 'keyboard' && !context.keyboardStack.value.includes(id)) {
247
+ context.keyboardStack.value = [...context.keyboardStack.value, id];
248
+ }
249
+
250
+ if (!isOpen.value) {
251
+ isOpen.value = true;
252
+ context?.closeOthers(entry);
253
+ }
254
+
255
+ if (source === 'keyboard') {
256
+ nextTick(focusFirstItem);
257
+ }
258
+ }
259
+
260
+ function doClose(): void {
261
+ if (!isOpen.value) {
262
+ return;
263
+ }
264
+
265
+ isOpen.value = false;
266
+ cone.value = null;
267
+
268
+ if (context) {
269
+ if (context.activeCone.value?.id === id) {
270
+ context.activeCone.value = null;
271
+ }
272
+
273
+ context.keyboardStack.value = context.keyboardStack.value.filter(value => value !== id);
274
+ }
275
+ }
276
+
277
+ function focusTrigger(): void {
278
+ elementOf(triggerRef.value)?.focus();
279
+ }
280
+
281
+ function focusFirstItem(): void {
282
+ const popup = elementOf(popupRef.value);
283
+
284
+ if (!popup) {
285
+ return;
286
+ }
287
+
288
+ const focusable = popup.querySelector<HTMLElement>('[tabindex="0"]') ?? popup.querySelector<HTMLElement>('a[href], button:not([disabled])');
289
+ focusable?.focus();
290
+ }
291
+
292
+ function onTriggerClick(evt: MouseEvent): void {
293
+ evt.stopPropagation();
294
+
295
+ // evt.detail === 0 means the click came from the keyboard (Enter/Space), in which case we
296
+ // move focus into the submenu. A real pointer click keeps focus where it is.
297
+ open(evt.detail === 0 ? 'keyboard' : 'pointer');
298
+ }
299
+
300
+ function onTriggerKeydown(evt: KeyboardEvent): void {
301
+ if (evt.key === 'ArrowRight') {
302
+ evt.preventDefault();
303
+ evt.stopPropagation();
304
+ open('keyboard');
305
+ }
306
+ }
307
+
308
+ function onPopupKeydown(evt: KeyboardEvent): void {
309
+ if (evt.key === 'ArrowLeft' || evt.key === 'Escape') {
310
+ evt.preventDefault();
311
+ evt.stopPropagation();
312
+ doClose();
313
+ focusTrigger();
314
+ }
315
+ }
316
+
317
+ if (context && !isSSR) {
318
+ watch(context.pointer, () => {
319
+ const trigger = elementOf(triggerRef.value);
320
+
321
+ if (!trigger) {
322
+ return;
323
+ }
324
+
325
+ const {x, y, px, py} = context.pointer.value;
326
+ const t = trigger.getBoundingClientRect();
327
+ const overTrigger = x >= t.left && x <= t.right && y >= t.top && y <= t.bottom;
328
+
329
+ if (!isOpen.value) {
330
+ if (overTrigger && !context.isAimingAtOpenSubmenu()) {
331
+ open('pointer');
332
+ }
333
+
334
+ return;
335
+ }
336
+
337
+ const popup = elementOf(popupRef.value);
338
+
339
+ if (!popup) {
340
+ return;
341
+ }
342
+
343
+ const r = popup.getBoundingClientRect();
344
+ const overPopup = x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
345
+
346
+ if (overTrigger || overPopup || context.hasOpenDescendant(entry)) {
347
+ cone.value = null;
348
+
349
+ if (context.activeCone.value?.id === id) {
350
+ context.activeCone.value = null;
351
+ }
352
+
353
+ return;
354
+ }
355
+
356
+ const edgeX = r.left >= t.right ? r.left : (r.right <= t.left ? r.right : r.left);
357
+ const candidate: FluxMenuFlyoutCone = {id, ax: x, ay: y, bx: edgeX, by: r.top, cx: edgeX, cy: r.bottom};
358
+
359
+ if (pointInTriangle(x, y, px, py, edgeX, r.top, edgeX, r.bottom)) {
360
+ cone.value = candidate;
361
+ context.activeCone.value = candidate;
362
+ } else {
363
+ cone.value = null;
364
+
365
+ if (context.activeCone.value?.id === id) {
366
+ context.activeCone.value = null;
367
+ }
368
+
369
+ doClose();
370
+ }
371
+ });
372
+ }
373
+
374
+ onMounted(() => context?.register(entry));
375
+
376
+ onUnmounted(() => {
377
+ doClose();
378
+ context?.unregister(entry);
379
+ });
380
+
381
+ return {
382
+ context,
383
+ cone,
384
+ isOpen,
385
+ closeAll: () => context?.closeAll(),
386
+ focusTrigger,
387
+ onPopupKeydown,
388
+ onTriggerClick,
389
+ onTriggerKeydown
390
+ };
391
+ }
392
+
393
+ function elementOf(value: ComponentPublicInstance | HTMLElement | null | undefined): HTMLElement | null {
394
+ if (!value) {
395
+ return null;
396
+ }
397
+
398
+ if (value instanceof HTMLElement) {
399
+ return value;
400
+ }
401
+
402
+ return (value.$el as HTMLElement | null) ?? null;
403
+ }
404
+
405
+ function pointInTriangle(px: number, py: number, ax: number, ay: number, bx: number, by: number, cx: number, cy: number): boolean {
406
+ const d1 = sign(px, py, ax, ay, bx, by);
407
+ const d2 = sign(px, py, bx, by, cx, cy);
408
+ const d3 = sign(px, py, cx, cy, ax, ay);
409
+ const hasNegative = d1 < 0 || d2 < 0 || d3 < 0;
410
+ const hasPositive = d1 > 0 || d2 > 0 || d3 > 0;
411
+
412
+ return !(hasNegative && hasPositive);
413
+ }
414
+
415
+ function sign(px: number, py: number, ax: number, ay: number, bx: number, by: number): number {
416
+ return (px - bx) * (ay - by) - (ax - bx) * (py - by);
417
+ }
@@ -0,0 +1,22 @@
1
+ .avatarGroup {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ flex-flow: row nowrap;
5
+ }
6
+
7
+ .avatarGroupItem {
8
+ position: relative;
9
+ display: inline-flex;
10
+ margin-left: calc(var(--overlap, .3) * -1em);
11
+ border-radius: .3em;
12
+ box-shadow: 0 0 0 .125em var(--gray-25);
13
+ transition: translate .2s var(--swift-out);
14
+
15
+ &:first-child {
16
+ margin-left: 0;
17
+ }
18
+
19
+ &:hover {
20
+ z-index: 1;
21
+ }
22
+ }
@@ -0,0 +1,17 @@
1
+ .contextMenu {
2
+ display: contents;
3
+ }
4
+
5
+ .contextMenuPopup {
6
+ composes: basePane from './base/Pane.module.scss';
7
+
8
+ position: fixed;
9
+ top: 0;
10
+ left: 0;
11
+ min-width: 270px;
12
+ max-height: max(330px, 50dvh);
13
+ overflow: auto;
14
+ box-shadow: var(--shadow-md);
15
+ translate: var(--x) var(--y);
16
+ z-index: 10000;
17
+ }
@@ -0,0 +1,98 @@
1
+ .descriptionList {
2
+ display: flex;
3
+ flex-flow: column;
4
+ gap: 12px;
5
+ }
6
+
7
+ .descriptionListHeader {
8
+ color: var(--foreground-secondary);
9
+ font-size: 14px;
10
+ font-weight: 500;
11
+ }
12
+
13
+ .descriptionListItems {
14
+ display: flex;
15
+ margin: 0;
16
+ flex-flow: column;
17
+ gap: 12px;
18
+ }
19
+
20
+ .descriptionItem {
21
+ display: flex;
22
+ gap: 12px;
23
+ align-items: center;
24
+ }
25
+
26
+ .descriptionItem.isStacked {
27
+ gap: 2px;
28
+ align-items: stretch;
29
+ flex-flow: column;
30
+ }
31
+
32
+ .descriptionItem.isStacked .descriptionItemValue {
33
+ margin-left: 0;
34
+ justify-content: flex-start;
35
+ text-align: left;
36
+ }
37
+
38
+ .descriptionItemTerm {
39
+ display: flex;
40
+ gap: 9px;
41
+ align-items: center;
42
+ color: var(--foreground);
43
+ font-size: 15px;
44
+ }
45
+
46
+ .descriptionItemIcon {
47
+ flex-shrink: 0;
48
+ color: var(--foreground);
49
+ font-size: 16px;
50
+ }
51
+
52
+ .descriptionItemLabel {
53
+ overflow-wrap: anywhere;
54
+ }
55
+
56
+ .descriptionItemValue {
57
+ display: flex;
58
+ margin: 0;
59
+ margin-left: auto;
60
+ gap: 6px;
61
+ align-items: center;
62
+ color: var(--foreground-prominent);
63
+ font-size: 15px;
64
+ font-weight: 600;
65
+ text-align: right;
66
+ overflow-wrap: anywhere;
67
+ }
68
+
69
+ .descriptionListItems.isHorizontal {
70
+ gap: 0;
71
+ flex-flow: row;
72
+
73
+ .descriptionItem {
74
+ gap: 6px;
75
+ align-items: stretch;
76
+ flex: 1 1 0;
77
+ flex-flow: column;
78
+ padding-right: 24px;
79
+ padding-left: 24px;
80
+ border-left: 1px solid var(--gray-100);
81
+ }
82
+
83
+ .descriptionItem:first-child {
84
+ padding-left: 0;
85
+ border-left: 0;
86
+ }
87
+
88
+ .descriptionItemTerm {
89
+ font-size: 14px;
90
+ }
91
+
92
+ .descriptionItemValue {
93
+ margin-left: 0;
94
+ justify-content: flex-start;
95
+ font-weight: 500;
96
+ text-align: left;
97
+ }
98
+ }
@@ -1134,3 +1134,54 @@
1134
1134
  transition: 210ms var(--swift-out);
1135
1135
  transition-property: background, border-color, color, opacity, scale, translate, mixin.focus-ring-transition-properties();
1136
1136
  }
1137
+
1138
+ .formTagsInput {
1139
+ composes: formInput;
1140
+
1141
+ display: flex;
1142
+ height: unset;
1143
+ min-height: 42px;
1144
+ padding: 4px 6px;
1145
+ align-items: center;
1146
+ flex-wrap: wrap;
1147
+ gap: 6px;
1148
+ cursor: text;
1149
+
1150
+ &:not(.formTagsInputDisabled) {
1151
+ @include mixin.focus-ring(-1px, true);
1152
+ }
1153
+ }
1154
+
1155
+ .formTagsInputEnabled {
1156
+ composes: formTagsInput;
1157
+ }
1158
+
1159
+ .formTagsInputDisabled {
1160
+ composes: formTagsInput;
1161
+
1162
+ background: var(--gray-100);
1163
+ cursor: not-allowed;
1164
+ }
1165
+
1166
+ .formTagsInputField {
1167
+ height: 30px;
1168
+ min-width: 60px;
1169
+ flex: 1 1 60px;
1170
+ background: none;
1171
+ border: 0;
1172
+ color: inherit;
1173
+ font: inherit;
1174
+ outline: 0;
1175
+
1176
+ &::placeholder {
1177
+ color: var(--foreground-secondary);
1178
+ }
1179
+
1180
+ &:disabled {
1181
+ cursor: not-allowed;
1182
+ }
1183
+ }
1184
+
1185
+ .formTagsInputPopup {
1186
+ composes: formSelectPopup;
1187
+ }
@@ -0,0 +1,47 @@
1
+ @use '~flux/components/css/mixin';
2
+
3
+ .formRating {
4
+ display: inline-flex;
5
+ gap: .15em;
6
+ font-size: 24px;
7
+ line-height: 1;
8
+ border-radius: 4px;
9
+
10
+ @include mixin.focus-ring(4px);
11
+
12
+ &.isDisabled {
13
+ opacity: .6;
14
+ }
15
+ }
16
+
17
+ .formRatingStar {
18
+ position: relative;
19
+ display: inline-flex;
20
+ padding: 0;
21
+ border: 0;
22
+ background: none;
23
+ line-height: 0;
24
+ cursor: pointer;
25
+
26
+ &:disabled {
27
+ cursor: default;
28
+ }
29
+ }
30
+
31
+ .formRatingStarEmpty {
32
+ color: var(--gray-300);
33
+ }
34
+
35
+ .formRatingStarFull {
36
+ position: absolute;
37
+ top: 0;
38
+ bottom: 0;
39
+ left: 0;
40
+ color: var(--warning-400);
41
+ clip-path: inset(0 calc((1 - var(--fill, 0)) * 100%) 0 0);
42
+ pointer-events: none;
43
+ }
44
+
45
+ .isInvalid .formRatingStarEmpty {
46
+ color: var(--danger-200);
47
+ }
@@ -0,0 +1,45 @@
1
+ @use '~flux/components/css/mixin';
2
+
3
+ .inlineEdit {
4
+ display: flex;
5
+ gap: 6px;
6
+ align-items: flex-start;
7
+ }
8
+
9
+ .inlineEditField {
10
+ min-width: 0;
11
+ flex: 1 1 auto;
12
+ }
13
+
14
+ .inlineEditActions {
15
+ display: flex;
16
+ gap: 6px;
17
+ align-items: center;
18
+ flex-shrink: 0;
19
+ }
20
+
21
+ .inlineEditDisplay {
22
+ display: inline-flex;
23
+ min-height: 30px;
24
+ padding: 3px 6px;
25
+ align-items: center;
26
+ border-radius: 6px;
27
+ color: var(--foreground);
28
+ line-height: 21px;
29
+ white-space: pre-wrap;
30
+ overflow-wrap: anywhere;
31
+
32
+ &.isInteractive {
33
+ cursor: text;
34
+
35
+ @include mixin.focus-ring(2px);
36
+
37
+ @include mixin.hover {
38
+ background: var(--surface-hover);
39
+ }
40
+ }
41
+
42
+ &.isPlaceholder {
43
+ color: var(--foreground-secondary);
44
+ }
45
+ }