PluginProbe
The Innovative Form Builder – IvyForms / 0.9.1
The Innovative Form Builder – IvyForms v0.9.1
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.9.1, at frontend/src/views/_components/button/IvyButtonAction.vue

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