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

637 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 const activeElement = document.activeElement
106 if (activeElement instanceof HTMLElement) {
107 activeElement.blur()
108 }
109 }
110
111 const handleMouseDown = (event: Event) => {
112 event.preventDefault()
113 }
114 </script>
115
116 <style lang="scss">
117 @use 'sass:list' as *;
118 @use 'sass:meta' as *;
119
120 $icon-start: '.ivyforms-button-option__icon-start';
121 $icon-end: '.ivyforms-button-option__icon-end';
122 $transition: '.ivyforms-button-option__transition';
123 $type-fill: '.ivyforms-button-option__type__fill';
124 $type-border: '.ivyforms-button-option__type__border';
125 $type-ghost: '.ivyforms-button-option__type__ghost';
126
127 // Button Option
128 .ivyforms-button-option {
129 position: relative;
130 border-radius: 8px;
131 display: inline-flex;
132 align-items: center;
133 white-space: nowrap;
134 justify-content: center;
135 flex-shrink: 0;
136 -webkit-tap-highlight-color: transparent;
137 user-select: none;
138 outline: none;
139 border: none;
140 cursor: pointer;
141 font-weight: 500;
142 max-width: 100%;
143
144 // Everything except transition
145 > *:not(#{$transition}) {
146 z-index: 1;
147 }
148
149 // Transition
150 & #{$transition} {
151 position: absolute;
152 top: 0;
153 left: 0;
154 width: 100%;
155 height: 100%;
156 border-radius: 8px;
157 fill: transparent;
158 opacity: 0;
159 }
160
161 // Hover
162 @media (hover: hover) and (pointer: fine) {
163 &:hover:not(:focus):not(:disabled):not(:active) {
164 // Transition
165 & #{$transition} {
166 opacity: 0.05;
167 }
168
169 // Ghost and Border types for all priorities except secondary
170 &:not(.ivyforms-button-option__priority__secondary) {
171 &#{$type-ghost},
172 &#{$type-border} {
173 & #{$transition} {
174 opacity: 0.08;
175 }
176 }
177 }
178 }
179 }
180
181 // Active
182 &.is-active:not(:disabled),
183 &:active:not(:disabled),
184 &[aria-expanded='true']='true'] {
185 // Transition
186 & #{$transition} {
187 opacity: 0.16;
188 fill: var(--map-ground-level-2-background);
189 }
190 }
191
192 &.is-active {
193 .ivyforms-icon {
194 svg {
195 fill: var(--map-base-purple-symbol-0);
196 stroke: var(--map-base-purple-symbol-0);
197 }
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: null;
290 @if length($colors) >= 5 {
291 $hover-fill-border-ghost: nth($colors, 5);
292 } @else {
293 $hover-fill-border-ghost: nth($colors, 4);
294 }
295 $text-color-border-ghost: null;
296 @if length($colors) >= 6 {
297 $text-color-border-ghost: nth($colors, 6);
298 } @else {
299 $text-color-border-ghost: nth($colors, 2);
300 }
301
302 // Priority
303 &__priority__#{$priority} {
304 color: $text-color;
305 --spinner-line-color: #{$text-color};
306 @if $priority == 'shadow-white' {
307 box-shadow: 0px 4px 15px 0px rgba(0, 0, 0, 0.05);
308 }
309
310 // Icon
311 #{$icon-start},
312 #{$icon-end} {
313 fill: $text-color;
314 stroke: $text-color;
315 }
316
317 // Focus
318 &:focus:not(:active) {
319 & .ivyforms-button-option__transition {
320 box-shadow: 0 0 0 4px $border-focus-color;
321 }
322 }
323
324 // Fill
325 &.ivyforms-button-option__type__fill {
326 background-color: $fill-color;
327
328 // Hover
329 @media (hover: hover) and (pointer: fine) {
330 &:hover:not(:focus):not(:disabled):not(:active) {
331 & .ivyforms-button-option__transition {
332 fill: $hover-fill-fill;
333 }
334 }
335 }
336 }
337
338 // Border & Ghost
339 &.ivyforms-button-option__type__border,
340 &.ivyforms-button-option__type__ghost {
341 color: $text-color-border-ghost;
342 --spinner-line-color: #{$text-color-border-ghost};
343
344 @if $priority == 'shadow-white' {
345 box-shadow: none;
346 }
347
348 // Hover
349 @media (hover: hover) and (pointer: fine) {
350 &:hover:not(:focus):not(:disabled):not(:active) {
351 & .ivyforms-button-option__transition {
352 fill: $hover-fill-border-ghost;
353 }
354 }
355 }
356
357 // Primary
358 &.ivyforms-button-option__priority__primary {
359 // Icon
360 #{$icon-start},
361 #{$icon-end} {
362 fill: var(--map-base-brand-symbol-0);
363 }
364 }
365
366 // Secondary
367 &.ivyforms-button-option__priority__secondary {
368 // Icon
369 #{$icon-start},
370 #{$icon-end} {
371 fill: var(--map-base-purple-symbol-0);
372 }
373 }
374 // Success
375 &.ivyforms-button-option__priority__success {
376 // Icon
377 #{$icon-start},
378 #{$icon-end} {
379 fill: var(--map-base-brand-symbol-0);
380 }
381 }
382 // Warning
383 &.ivyforms-button-option__priority__warning {
384 // Icon
385 #{$icon-start},
386 #{$icon-end} {
387 fill: var(--map-status-warning-symbol-0);
388 }
389 }
390 // Danger
391 &.ivyforms-button-option__priority__danger {
392 // Icon
393 #{$icon-start},
394 #{$icon-end} {
395 fill: var(--map-status-error-symbol-0);
396 }
397 }
398 // White
399 &.ivyforms-button-option__priority__white {
400 color: var(--primitive-white);
401 // Icon
402 #{$icon-start},
403 #{$icon-end} {
404 fill: var(--map-base-dusk-symbol-2);
405 }
406 }
407 // Shadow White
408 &.ivyforms-button-option__priority__shadow-white {
409 color: var(--primitive-white);
410 // Icon
411 #{$icon-start},
412 #{$icon-end} {
413 fill: var(--primitive-white);
414 }
415 }
416
417 // Border
418 &.ivyforms-button-option__type__border {
419 border: 1px solid $border-focus-color;
420
421 // Focus
422 &:focus:not(:active) {
423 // Transition
424 & #{$transition} {
425 box-shadow: 0 0 0 5px $border-focus-color;
426 }
427 }
428 }
429 }
430 }
431 }
432
433 @each $priority, $colors in $priorities {
434 @include priorityStyles($priority, $colors);
435 }
436
437 // Type
438 &__type {
439 // Border
440 &__border {
441 background-color: transparent;
442 }
443
444 // Ghost
445 &__ghost {
446 background-color: transparent;
447 }
448 }
449
450 // Define sizes in a map
451 $sizes: (
452 'l': (
453 48px,
454 16px,
455 normal,
456 (10px, 24px, 10px, 24px),
457 // padding
458 (10px, 24px, 10px, 18px),
459 // padding with icon on start
460 (10px, 18px, 10px, 24px),
461 //padding with icon on end
462 8px,
463 2px,
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 ),
478 's': (
479 36px,
480 14px,
481 normal,
482 (4px, 12px, 4px, 12px),
483 // padding
484 (4px, 12px, 4px, 6px),
485 // padding with icon on start
486 (4px, 6px, 4px, 12px),
487 //padding with icon on end
488 8px,
489 2px,
490 ),
491 'xs': (
492 32px,
493 12px,
494 normal,
495 (0, 8px, 0, 8px),
496 // padding
497 (0, 8px, 0, 4px),
498 // padding with icon on start
499 (0, 4px, 0, 8px),
500 //padding with icon on end
501 4px,
502 4px,
503 ),
504 );
505
506 // Function to process each size value if is array
507 @function process-values($values) {
508 $processed: ();
509 @each $value in $values {
510 // Check if the item is a list (this is where padding array could be)
511 @if type-of($value) == 'list' {
512 $processed: append($processed, join($value, ', ')); // Convert list to string
513 } @else {
514 $processed: append($processed, $value);
515 }
516 }
517 @return $processed;
518 }
519
520 // Iterate over each size to apply styles
521 @each $size, $values in $sizes {
522 &__size__#{$size} {
523 height: nth($values, 1);
524 font-size: nth($values, 2);
525 line-height: nth($values, 3);
526 gap: nth($values, 7);
527 padding: process-values(nth($values, 4));
528
529 // For icon only buttons
530 &.is-icon-start {
531 padding: process-values(nth($values, 5));
532 }
533
534 // For icon only buttons
535 &.is-icon-end {
536 padding: process-values(nth($values, 6));
537 }
538
539 // For icon only buttons
540 &.is-icon-only {
541 width: nth($values, 1);
542 padding: 0;
543
544 // Adjust spinner container for icon only buttons
545 .ivyforms-button-option__spinner-container {
546 margin: 0;
547 }
548 }
549
550 // Spinner
551 .ivyforms-button-option__spinner {
552 padding: nth($values, 8);
553 }
554 }
555 }
556
557 // Icon Containers
558 #{$icon-start},
559 #{$icon-end} {
560 display: flex;
561 align-items: center;
562 justify-content: center;
563 flex-shrink: 0;
564 }
565
566 // Disabled & Loading
567 &:disabled,
568 &.is-loading {
569 cursor: not-allowed;
570 }
571
572 // Text
573 span {
574 display: inline-block;
575 text-overflow: ellipsis;
576 overflow: hidden;
577 white-space: nowrap;
578 }
579
580 // Icon
581 i {
582 display: inline-block;
583 }
584
585 // Spinner Container
586 &__spinner-container {
587 width: 24px;
588 height: 24px;
589 display: flex;
590 align-items: center;
591 justify-content: center;
592 }
593
594 // Spinner
595 &__spinner {
596 --spinner-size: 5;
597 --spinner-ring-size: 5;
598
599 display: block;
600 font-size: calc(var(--spinner-size) * 1em);
601 border-radius: 50%;
602
603 // Spinner Line
604 &__line {
605 fill: none;
606 stroke: var(--spinner-line-color);
607 stroke-width: var(--spinner-ring-size);
608 stroke-linecap: round;
609 transform-origin: 50% 50%;
610 transform: rotate3d(0, 0, 1, 0deg);
611 animation:
612 2156ms spinner-arc ease-in-out infinite,
613 1829ms spinner-rotate linear infinite;
614 }
615 }
616
617 // Loader Animation Rotate
618 @keyframes spinner-rotate {
619 to {
620 transform: rotate3d(0, 0, 1, 360deg);
621 }
622 }
623
624 // Loader Animation Arc
625 @keyframes spinner-arc {
626 from {
627 stroke-dasharray: 0 150;
628 stroke-dashoffset: 0;
629 }
630 to {
631 stroke-dasharray: 100 150;
632 stroke-dashoffset: -140;
633 }
634 }
635 }
636 </style>
637