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

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