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

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