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

624 lines 14.7 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: if(length($colors) >= 5, nth($colors, 5), nth($colors, 4));
287 $text-color-border-ghost: if(length($colors) >= 6, nth($colors, 6), nth($colors, 2));
288
289 // Priority
290 &__priority__#{$priority} {
291 color: $text-color;
292 --spinner-line-color: #{$text-color};
293 @if $priority == 'shadow-white' {
294 box-shadow: 0px 4px 15px 0px rgba(0, 0, 0, 0.05);
295 }
296
297 // Icon
298 #{$icon-start},
299 #{$icon-end} {
300 fill: $text-color;
301 stroke: $text-color;
302 }
303
304 // Focus
305 &:focus:not(:active) {
306 & .ivyforms-button-option__transition {
307 box-shadow: 0 0 0 4px $border-focus-color;
308 }
309 }
310
311 // Fill
312 &.ivyforms-button-option__type__fill {
313 background-color: $fill-color;
314
315 // Hover
316 @media (hover: hover) and (pointer: fine) {
317 &:hover:not(:focus):not(:disabled):not(:active) {
318 & .ivyforms-button-option__transition {
319 fill: $hover-fill-fill;
320 }
321 }
322 }
323 }
324
325 // Border & Ghost
326 &.ivyforms-button-option__type__border,
327 &.ivyforms-button-option__type__ghost {
328 color: $text-color-border-ghost;
329 --spinner-line-color: #{$text-color-border-ghost};
330
331 @if $priority == 'shadow-white' {
332 box-shadow: none;
333 }
334
335 // Hover
336 @media (hover: hover) and (pointer: fine) {
337 &:hover:not(:focus):not(:disabled):not(:active) {
338 & .ivyforms-button-option__transition {
339 fill: $hover-fill-border-ghost;
340 }
341 }
342 }
343
344 // Primary
345 &.ivyforms-button-option__priority__primary {
346 // Icon
347 #{$icon-start},
348 #{$icon-end} {
349 fill: var(--map-base-brand-symbol-0);
350 }
351 }
352
353 // Secondary
354 &.ivyforms-button-option__priority__secondary {
355 // Icon
356 #{$icon-start},
357 #{$icon-end} {
358 fill: var(--map-base-purple-symbol-0);
359 }
360 }
361 // Success
362 &.ivyforms-button-option__priority__success {
363 // Icon
364 #{$icon-start},
365 #{$icon-end} {
366 fill: var(--map-base-brand-symbol-0);
367 }
368 }
369 // Warning
370 &.ivyforms-button-option__priority__warning {
371 // Icon
372 #{$icon-start},
373 #{$icon-end} {
374 fill: var(--map-status-warning-symbol-0);
375 }
376 }
377 // Danger
378 &.ivyforms-button-option__priority__danger {
379 // Icon
380 #{$icon-start},
381 #{$icon-end} {
382 fill: var(--map-status-error-symbol-0);
383 }
384 }
385 // White
386 &.ivyforms-button-option__priority__white {
387 color: var(--primitive-white);
388 // Icon
389 #{$icon-start},
390 #{$icon-end} {
391 fill: var(--map-base-dusk-symbol-2);
392 }
393 }
394 // Shadow White
395 &.ivyforms-button-option__priority__shadow-white {
396 color: var(--primitive-white);
397 // Icon
398 #{$icon-start},
399 #{$icon-end} {
400 fill: var(--primitive-white);
401 }
402 }
403
404 // Border
405 &.ivyforms-button-option__type__border {
406 border: 1px solid $border-focus-color;
407
408 // Focus
409 &:focus:not(:active) {
410 // Transition
411 & #{$transition} {
412 box-shadow: 0 0 0 5px $border-focus-color;
413 }
414 }
415 }
416 }
417 }
418 }
419
420 @each $priority, $colors in $priorities {
421 @include priorityStyles($priority, $colors);
422 }
423
424 // Type
425 &__type {
426 // Border
427 &__border {
428 background-color: transparent;
429 }
430
431 // Ghost
432 &__ghost {
433 background-color: transparent;
434 }
435 }
436
437 // Define sizes in a map
438 $sizes: (
439 'l': (
440 48px,
441 16px,
442 normal,
443 (10px, 24px, 10px, 24px),
444 // padding
445 (10px, 24px, 10px, 18px),
446 // padding with icon on start
447 (10px, 18px, 10px, 24px),
448 //padding with icon on end
449 8px,
450 2px,
451 ),
452 'd': (
453 40px,
454 16px,
455 normal,
456 (8px, 16px, 8px, 16px),
457 // padding
458 (8px, 16px, 8px, 10px),
459 // padding with icon on start
460 (8px, 10px, 8px, 16px),
461 //padding with icon on end
462 8px,
463 2px,
464 ),
465 's': (
466 36px,
467 14px,
468 normal,
469 (4px, 12px, 4px, 12px),
470 // padding
471 (4px, 12px, 4px, 6px),
472 // padding with icon on start
473 (4px, 6px, 4px, 12px),
474 //padding with icon on end
475 8px,
476 2px,
477 ),
478 'xs': (
479 32px,
480 12px,
481 normal,
482 (0, 8px, 0, 8px),
483 // padding
484 (0, 8px, 0, 4px),
485 // padding with icon on start
486 (0, 4px, 0, 8px),
487 //padding with icon on end
488 4px,
489 4px,
490 ),
491 );
492
493 // Function to process each size value if is array
494 @function process-values($values) {
495 $processed: ();
496 @each $value in $values {
497 // Check if the item is a list (this is where padding array could be)
498 @if type-of($value) == 'list' {
499 $processed: append($processed, join($value, ', ')); // Convert list to string
500 } @else {
501 $processed: append($processed, $value);
502 }
503 }
504 @return $processed;
505 }
506
507 // Iterate over each size to apply styles
508 @each $size, $values in $sizes {
509 &__size__#{$size} {
510 height: nth($values, 1);
511 font-size: nth($values, 2);
512 line-height: nth($values, 3);
513 gap: nth($values, 7);
514 padding: process-values(nth($values, 4));
515
516 // For icon only buttons
517 &.is-icon-start {
518 padding: process-values(nth($values, 5));
519 }
520
521 // For icon only buttons
522 &.is-icon-end {
523 padding: process-values(nth($values, 6));
524 }
525
526 // For icon only buttons
527 &.is-icon-only {
528 width: nth($values, 1);
529 padding: 0;
530
531 // Adjust spinner container for icon only buttons
532 .ivyforms-button-option__spinner-container {
533 margin: 0;
534 }
535 }
536
537 // Spinner
538 .ivyforms-button-option__spinner {
539 padding: nth($values, 8);
540 }
541 }
542 }
543
544 // Icon Containers
545 #{$icon-start},
546 #{$icon-end} {
547 display: flex;
548 align-items: center;
549 justify-content: center;
550 flex-shrink: 0;
551 }
552
553 // Disabled & Loading
554 &:disabled,
555 &.is-loading {
556 cursor: not-allowed;
557 }
558
559 // Text
560 span {
561 display: inline-block;
562 text-overflow: ellipsis;
563 overflow: hidden;
564 white-space: nowrap;
565 }
566
567 // Icon
568 i {
569 display: inline-block;
570 }
571
572 // Spinner Container
573 &__spinner-container {
574 width: 24px;
575 height: 24px;
576 display: flex;
577 align-items: center;
578 justify-content: center;
579 }
580
581 // Spinner
582 &__spinner {
583 --spinner-size: 5;
584 --spinner-ring-size: 5;
585
586 display: block;
587 font-size: calc(var(--spinner-size) * 1em);
588 border-radius: 50%;
589
590 // Spinner Line
591 &__line {
592 fill: none;
593 stroke: var(--spinner-line-color);
594 stroke-width: var(--spinner-ring-size);
595 stroke-linecap: round;
596 transform-origin: 50% 50%;
597 transform: rotate3d(0, 0, 1, 0deg);
598 animation:
599 2156ms spinner-arc ease-in-out infinite,
600 1829ms spinner-rotate linear infinite;
601 }
602 }
603
604 // Loader Animation Rotate
605 @keyframes spinner-rotate {
606 to {
607 transform: rotate3d(0, 0, 1, 360deg);
608 }
609 }
610
611 // Loader Animation Arc
612 @keyframes spinner-arc {
613 from {
614 stroke-dasharray: 0 150;
615 stroke-dashoffset: 0;
616 }
617 to {
618 stroke-dasharray: 100 150;
619 stroke-dashoffset: -140;
620 }
621 }
622 }
623 </style>
624