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

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