PluginProbe
The Innovative Form Builder – IvyForms / 0.3
The Innovative Form Builder – IvyForms v0.3
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / views / _components / button / IvyButtonAction.vue

IvyButtonAction.vue in The Innovative Form Builder – IvyForms 0.3, at frontend/src/views/_components/button/IvyButtonAction.vue

657 lines 16.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div class="ivyforms-button-wrapper">
3 <button
4 ref="menuButtonRef"
5 class="ivyforms-button-action"
6 :disabled="props.disabled || props.loading || props.comingSoon"
7 :class="[
8 'ivyforms-button-action__priority__' + props.priority,
9 'ivyforms-button-action__type__' + props.type,
10 'ivyforms-button-action__size__' + props.size,
11 {
12 'is-loading': props.loading,
13 'is-icon-start': props.iconStart && $slots.default && !props.iconOnly,
14 'is-icon-end': props.iconEnd && $slots.default && !props.iconOnly,
15 'is-icon-only': ((props.iconStart || props.iconEnd) && !$slots.default) || props.iconOnly,
16 },
17 ]"
18 :style="{ width: props.fullWidth ? '100%' : undefined }"
19 :aria-label="props.ariaLabel || getLabel('action_button')"
20 @click="handleClick"
21 @mousedown="handleMouseDown"
22 >
23 <span v-if="props.loading" class="ivyforms-button-action__spinner-container">
24 <svg viewBox="0 0 50 50" class="ivyforms-button-action__spinner">>
25 <circle class="ivyforms-button-action__spinner__line" cx="25" cy="25" r="22.5" />
26 </svg>
27 </span>
28 <IvyIcon
29 v-if="props.iconStart && !props.loading"
30 :name="props.iconStart"
31 :category="props.iconStartCategory"
32 :type="props.iconStartType"
33 :size="props.size"
34 class="ivyforms-button-action__icon-start"
35 />
36 <span v-if="$slots.default && !props.iconOnly && !props.loading">
37 <slot />
38 </span>
39 <IvyIcon
40 v-if="props.iconEnd"
41 :name="props.iconEnd"
42 :category="props.iconEndCategory"
43 :type="props.iconEndType"
44 :size="props.size"
45 class="ivyforms-button-action__icon-end"
46 />
47 <svg class="ivyforms-button-action__transition" width="100%" height="100%" fill="none">>
48 <g>
49 <rect width="100%" height="100%" />
50 </g>
51 </svg>
52 </button>
53 <ComingSoonBadge
54 v-if="props.comingSoon"
55 :size="props.size === 'xs' ? 's' : props.size"
56 class="ivyforms-button-wrapper__coming-soon"
57 />
58 </div>
59 </template>
60
61 <script setup lang="ts">
62 import type { IconCategory } from '@/types/icons/icon-category'
63 import type { IconType } from '@/types/icons/icon-type'
64 import { useLabels } from '@/composables/useLabels'
65 const { getLabel } = useLabels()
66
67 interface Props {
68 priority?:
69 | 'primary'
70 | 'secondary'
71 | 'tertiary'
72 | 'success'
73 | 'warning'
74 | 'danger'
75 | 'white'
76 | 'shadow-white'
77 type?: 'fill' | 'border' | 'ghost'
78 size?: 'l' | 'd' | 's' | 'xs'
79 disabled?: boolean
80 loading?: boolean
81 iconStart?: string
82 iconEnd?: string
83 iconStartCategory?: IconCategory
84 iconEndCategory?: IconCategory
85 iconStartType?: IconType
86 iconEndType?: IconType
87 fullWidth?: boolean
88 iconOnly?: boolean
89 comingSoon?: boolean
90 ariaLabel?: string
91 }
92
93 const props = withDefaults(defineProps<Props>(), {
94 priority: 'primary',
95 type: 'fill',
96 size: 'd',
97 disabled: false,
98 loading: false,
99 iconStart: '', // Default value for iconStart
100 iconEnd: '', // Default value for iconEnd
101 iconStartCategory: 'global',
102 iconEndCategory: 'global',
103 iconStartType: 'fill',
104 iconEndType: 'fill',
105 fullWidth: false,
106 iconOnly: false,
107 ariaLabel: '',
108 })
109
110 const emit = defineEmits<{
111 (e: 'click', event: Event): void
112 }>()
113
114 const handleClick = (event: Event) => {
115 ;(document.activeElement as HTMLElement).blur()
116 emit('click', event)
117 }
118
119 const handleMouseDown = (event: Event) => {
120 event.preventDefault()
121 }
122 </script>
123
124 <style lang="scss">
125 @use 'sass:list' as *;
126 @use 'sass:meta' as *;
127
128 $icon-start: '.ivyforms-button-action__icon-start';
129 $icon-end: '.ivyforms-button-action__icon-end';
130 $transition: '.ivyforms-button-action__transition';
131 $type-fill: '.ivyforms-button-action__type__fill';
132 $type-border: '.ivyforms-button-action__type__border';
133 $type-ghost: '.ivyforms-button-action__type__ghost';
134
135 .ivyforms-button-wrapper {
136 position: relative;
137
138 &__coming-soon {
139 position: absolute;
140 }
141
142 // Button Option
143 .ivyforms-button-action {
144 position: relative;
145 border-radius: 8px;
146 display: inline-flex;
147 align-items: center;
148 white-space: nowrap;
149 justify-content: center;
150 flex-shrink: 0;
151 -webkit-tap-highlight-color: transparent;
152 user-select: none;
153 outline: none;
154 border: none;
155 cursor: pointer;
156 font-weight: 500;
157 max-width: 100%;
158
159 // Everything except transition
160 > *:not(#{$transition}) {
161 z-index: 1;
162 }
163
164 // Transition
165 & #{$transition} {
166 position: absolute;
167 top: 0;
168 left: 0;
169 width: 100%;
170 height: 100%;
171 border-radius: 8px;
172 fill: transparent;
173 opacity: 0;
174 }
175
176 // Hover
177 @media (hover: hover) and (pointer: fine) {
178 &:hover:not(:focus):not(:disabled):not(:active) {
179 // Transition
180 & #{$transition} {
181 opacity: 0.05;
182 }
183
184 // Ghost and Border types for all priorities except secondary
185 &:not(.ivyforms-button-action__priority__secondary) {
186 &#{$type-ghost},
187 &#{$type-border} {
188 & #{$transition} {
189 opacity: 0.08;
190 }
191 }
192 }
193 }
194 }
195
196 // Active
197 &:active:not(:disabled),
198 &[aria-expanded='true']='true'] {
199 // Transition
200 & #{$transition} {
201 opacity: 0.16;
202 fill: var(--map-ground-level-2-background);
203 }
204 }
205
206 // Focus
207 &:focus:not(:active) {
208 // Transition
209 & #{$transition} {
210 opacity: 0.4;
211 }
212 }
213
214 // Disabled & Loading
215 &:disabled,
216 &.is-loading {
217 opacity: 0.5;
218 }
219
220 // Priorities
221 $priorities: (
222 'primary': (
223 var(--map-base-brand-fill-0),
224 var(--primitive-white),
225 var(--map-base-brand-stroke-0),
226 var(--map-base-brand-fill-4),
227 var(--map-base-brand-fill-0),
228 var(--map-base-brand-symbol-0),
229 ),
230 'secondary': (
231 var(--map-base-purple-fill-0),
232 var(--primitive-white),
233 var(--map-base-purple-stroke-0),
234 var(--map-base-purple-fill-4),
235 var(--map-base-purple-fill-0),
236 var(--map-base-purple-symbol-0),
237 ),
238 'tertiary': (
239 var(--map-base-dusk-fill--4),
240 var(--map-base-dusk-symbol-2),
241 var(--map-base-dusk-stroke-0),
242 var(--map-base-dusk-fill-0),
243 var(--map-base-dusk-fill-4),
244 var(--map-base-dusk-symbol-2),
245 ),
246 'success': (
247 var(--map-status-success-fill-0),
248 var(--primitive-white),
249 var(--map-status-success-stroke-0),
250 var(--map-status-success-fill-4),
251 var(--map-status-success-fill-0),
252 var(--map-status-success-symbol-0),
253 ),
254 'warning': (
255 var(--map-status-warning-fill-0),
256 var(--primitive-white),
257 var(--map-status-warning-stroke-0),
258 var(--map-status-warning-fill-4),
259 var(--map-status-warning-fill-0),
260 var(--map-status-warning-symbol-0),
261 ),
262 'danger': (
263 var(--map-status-error-fill-0),
264 var(--primitive-white),
265 var(--map-status-error-stroke-0),
266 var(--map-status-error-fill-4),
267 var(--map-status-error-fill-0),
268 var(--map-status-error-symbol-0),
269 ),
270 'white': (
271 var(--primitive-white),
272 var(--primitive-black),
273 var(--primitive-white),
274 var(--primitive-white),
275 var(--map-bw-o90),
276 var(--map-bw),
277 ),
278 'shadow-white': (
279 var(--map-ground-level-2-background),
280 var(--map-base-dusk-symbol-2),
281 var(--primitive-white),
282 var(--primitive-white),
283 var(--primitive-white),
284 var(--map-base-dusk-symbol-2),
285 ),
286 );
287
288 // Priority Styles
289 @mixin priorityStyles($priority, $colors: ()) {
290 $fill-color: nth($colors, 1);
291 $text-color: nth($colors, 2);
292 $border-focus-color: nth($colors, 3);
293 $hover-fill-fill: nth($colors, 4);
294 $hover-fill-border-ghost: if(length($colors) >= 5, nth($colors, 5), nth($colors, 4));
295 $text-color-border-ghost: if(length($colors) >= 6, nth($colors, 6), nth($colors, 2));
296
297 // Priority
298 &__priority__#{$priority} {
299 color: $text-color;
300 --spinner-line-color: #{$text-color};
301 @if $priority == 'shadow-white' {
302 box-shadow: 0px 4px 15px 0px rgba(0, 0, 0, 0.05);
303 }
304
305 // Icon
306 #{$icon-start},
307 #{$icon-end} {
308 fill: $text-color;
309 stroke: $text-color;
310 }
311
312 // Focus
313 &:focus:not(:active) {
314 & .ivyforms-button-action__transition {
315 box-shadow: 0 0 0 4px $border-focus-color;
316 }
317 }
318
319 // Fill
320 &.ivyforms-button-action__type__fill {
321 background-color: $fill-color;
322
323 // Hover
324 @media (hover: hover) and (pointer: fine) {
325 &:hover:not(:focus):not(:disabled):not(:active) {
326 & .ivyforms-button-action__transition {
327 fill: $hover-fill-fill;
328 }
329 }
330 }
331 }
332
333 // Border & Ghost
334 &.ivyforms-button-action__type__border,
335 &.ivyforms-button-action__type__ghost {
336 color: $text-color-border-ghost;
337 --spinner-line-color: #{$text-color-border-ghost};
338
339 @if $priority == 'shadow-white' {
340 box-shadow: none;
341 }
342
343 // Hover
344 @media (hover: hover) and (pointer: fine) {
345 &:hover:not(:focus):not(:disabled):not(:active) {
346 & .ivyforms-button-action__transition {
347 fill: $hover-fill-border-ghost;
348 }
349 }
350 }
351
352 // Primary
353 &.ivyforms-button-action__priority__primary {
354 // Icon
355 #{$icon-start},
356 #{$icon-end} {
357 fill: var(--map-base-brand-symbol-0);
358 }
359 }
360
361 // Secondary
362 &.ivyforms-button-action__priority__secondary {
363 // Icon
364 #{$icon-start},
365 #{$icon-end} {
366 fill: var(--map-base-purple-symbol-0);
367 }
368 }
369 // Success
370 &.ivyforms-button-action__priority__success {
371 // Icon
372 #{$icon-start},
373 #{$icon-end} {
374 fill: var(--map-base-brand-symbol-0);
375 }
376 }
377 // Warning
378 &.ivyforms-button-action__priority__warning {
379 // Icon
380 #{$icon-start},
381 #{$icon-end} {
382 fill: var(--map-status-warning-symbol-0);
383 }
384 }
385 // Danger
386 &.ivyforms-button-action__priority__danger {
387 // Icon
388 #{$icon-start},
389 #{$icon-end} {
390 fill: var(--map-status-error-symbol-0);
391 }
392 }
393 // White
394 &.ivyforms-button-action__priority__white {
395 color: var(--primitive-white);
396 // Icon
397 #{$icon-start},
398 #{$icon-end} {
399 fill: var(--primitive-white);
400 }
401 }
402 // Shadow White
403 &.ivyforms-button-action__priority__shadow-white {
404 color: var(--primitive-white);
405 // Icon
406 #{$icon-start},
407 #{$icon-end} {
408 fill: var(--primitive-white);
409 }
410 }
411
412 // Border
413 &.ivyforms-button-action__type__border {
414 border: 1px solid $border-focus-color;
415
416 // Focus
417 &:focus:not(:active) {
418 // Transition
419 & #{$transition} {
420 box-shadow: 0 0 0 5px $border-focus-color;
421 }
422 }
423 }
424 }
425 }
426 }
427
428 @each $priority, $colors in $priorities {
429 @include priorityStyles($priority, $colors);
430 }
431
432 // Type
433 &__type {
434 // Border
435 &__border {
436 background-color: transparent;
437 }
438
439 // Ghost
440 &__ghost {
441 background-color: transparent;
442 }
443 }
444
445 // Define sizes in a map
446 $sizes: (
447 'l': (
448 48px,
449 16px,
450 normal,
451 (10px, 24px, 10px, 24px),
452 // padding
453 (10px, 24px, 10px, 18px),
454 // padding with icon on start
455 (10px, 18px, 10px, 24px),
456 //padding with icon on end
457 8px,
458 2px,
459 -12px,
460 //top coming soon position
461 -9px,
462 //right coming soon position
463 ),
464 'd': (
465 40px,
466 16px,
467 normal,
468 (8px, 16px, 8px, 16px),
469 // padding
470 (8px, 16px, 8px, 10px),
471 // padding with icon on start
472 (8px, 10px, 8px, 16px),
473 //padding with icon on end
474 8px,
475 2px,
476 -13px,
477 //top coming soon position
478 -11px,
479 //right coming soon position
480 ),
481 's': (
482 36px,
483 14px,
484 normal,
485 (4px, 12px, 4px, 12px),
486 // padding
487 (4px, 12px, 4px, 6px),
488 // padding with icon on start
489 (4px, 6px, 4px, 12px),
490 //padding with icon on end
491 8px,
492 2px,
493 -8px,
494 //top coming soon position
495 -12px,
496 //right coming soon position
497 ),
498 'xs': (
499 32px,
500 12px,
501 normal,
502 (0, 8px, 0, 8px),
503 // padding
504 (0, 8px, 0, 4px),
505 // padding with icon on start
506 (0, 4px, 0, 8px),
507 //padding with icon on end
508 4px,
509 4px,
510 -8px,
511 //top coming soon position
512 -12px,
513 //right coming soon position
514 ),
515 );
516
517 // Coming Soon Badge positioning using the $sizes map
518 @each $size, $values in $sizes {
519 &__size__#{$size} ~ .ivyforms-button-wrapper__coming-soon {
520 top: nth($values, 9);
521 right: nth($values, 10);
522 }
523 }
524
525 // Function to process each size value if is array
526 @function process-values($values) {
527 $processed: ();
528 @each $value in $values {
529 // Check if the item is a list (this is where padding array could be)
530 @if type-of($value) == 'list' {
531 $processed: append($processed, join($value, ', ')); // Convert list to string
532 } @else {
533 $processed: append($processed, $value);
534 }
535 }
536 @return $processed;
537 }
538
539 // Iterate over each size to apply styles
540 @each $size, $values in $sizes {
541 &__size__#{$size} {
542 height: nth($values, 1);
543 font-size: nth($values, 2);
544 line-height: nth($values, 3);
545 gap: nth($values, 7);
546 padding: process-values(nth($values, 4));
547
548 // For icon only buttons
549 &.is-icon-start {
550 padding: process-values(nth($values, 5));
551 }
552
553 // For icon only buttons
554 &.is-icon-end {
555 padding: process-values(nth($values, 6));
556 }
557
558 // For icon only buttons
559 &.is-icon-only {
560 width: nth($values, 1);
561 padding: 0;
562
563 // Adjust spinner container for icon only buttons
564 .ivyforms-button-action__spinner-container {
565 margin: 0;
566 }
567 }
568
569 // Spinner
570 .ivyforms-button-action__spinner {
571 padding: nth($values, 8);
572 }
573 }
574 }
575
576 // Icon Containers
577 #{$icon-start},
578 #{$icon-end} {
579 display: flex;
580 align-items: center;
581 justify-content: center;
582 flex-shrink: 0;
583 }
584
585 // Disabled & Loading
586 &:disabled,
587 &.is-loading {
588 cursor: not-allowed;
589 }
590
591 // Text
592 span {
593 display: inline-block;
594 text-overflow: ellipsis;
595 overflow: hidden;
596 white-space: nowrap;
597 }
598
599 // Icon
600 i {
601 display: inline-block;
602 }
603
604 // Spinner Container
605 &__spinner-container {
606 width: 24px;
607 height: 24px;
608 display: flex;
609 align-items: center;
610 justify-content: center;
611 }
612
613 // Spinner
614 &__spinner {
615 --spinner-size: 5;
616 --spinner-ring-size: 5;
617
618 display: block;
619 font-size: calc(var(--spinner-size) * 1em);
620 border-radius: 50%;
621
622 // Spinner Line
623 &__line {
624 fill: none;
625 stroke: var(--spinner-line-color);
626 stroke-width: var(--spinner-ring-size);
627 stroke-linecap: round;
628 transform-origin: 50% 50%;
629 transform: rotate3d(0, 0, 1, 0deg);
630 animation:
631 2156ms spinner-arc ease-in-out infinite,
632 1829ms spinner-rotate linear infinite;
633 }
634 }
635
636 // Loader Animation Rotate
637 @keyframes spinner-rotate {
638 to {
639 transform: rotate3d(0, 0, 1, 360deg);
640 }
641 }
642
643 // Loader Animation Arc
644 @keyframes spinner-arc {
645 from {
646 stroke-dasharray: 0 150;
647 stroke-dashoffset: 0;
648 }
649 to {
650 stroke-dasharray: 100 150;
651 stroke-dashoffset: -140;
652 }
653 }
654 }
655 }
656 </style>
657