PluginProbe
The Innovative Form Builder – IvyForms / 0.9
The Innovative Form Builder – IvyForms v0.9
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 / IvyButtonOption.vue

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

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