@dolanske/vui 1.4.1 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -2
- package/src/App.vue +0 -103
- package/src/components/Accordion/Accordion.vue +0 -98
- package/src/components/Accordion/AccordionGroup.vue +0 -49
- package/src/components/Accordion/accordion.scss +0 -97
- package/src/components/Alert/Alert.vue +0 -59
- package/src/components/Alert/alert.scss +0 -162
- package/src/components/Avatar/Avatar.vue +0 -53
- package/src/components/Avatar/avatar.scss +0 -52
- package/src/components/Badge/Badge.vue +0 -21
- package/src/components/Badge/badge.scss +0 -210
- package/src/components/Breadcrumbs/BreadcrumbItem.vue +0 -26
- package/src/components/Breadcrumbs/Breadcrumbs.vue +0 -29
- package/src/components/Breadcrumbs/breadcrumbs.scss +0 -31
- package/src/components/Button/Button.vue +0 -86
- package/src/components/Button/button.scss +0 -292
- package/src/components/ButtonGroup/ButtonGroup.vue +0 -28
- package/src/components/ButtonGroup/button-group.scss +0 -51
- package/src/components/Calendar/Calendar.vue +0 -66
- package/src/components/Calendar/calendar.scss +0 -88
- package/src/components/Card/Card.vue +0 -48
- package/src/components/Card/card.scss +0 -55
- package/src/components/Checkbox/Checkbox.vue +0 -54
- package/src/components/Checkbox/checkbox.scss +0 -80
- package/src/components/CopyClipboard/CopyClipboard.vue +0 -98
- package/src/components/CopyClipboard/copy-clipboard.scss +0 -25
- package/src/components/Divider/Divider.vue +0 -38
- package/src/components/Divider/divider.scss +0 -37
- package/src/components/Drawer/Drawer.vue +0 -102
- package/src/components/Drawer/drawer.scss +0 -37
- package/src/components/Dropdown/Dropdown.vue +0 -120
- package/src/components/Dropdown/DropdownItem.vue +0 -33
- package/src/components/Dropdown/DropdownTitle.vue +0 -14
- package/src/components/Dropdown/dropdown-item.scss +0 -84
- package/src/components/Dropdown/dropdown.scss +0 -53
- package/src/components/Flex/Flex.vue +0 -113
- package/src/components/Grid/Grid.vue +0 -87
- package/src/components/Input/Color.vue +0 -26
- package/src/components/Input/Counter.vue +0 -66
- package/src/components/Input/Dropzone.vue +0 -65
- package/src/components/Input/File.vue +0 -15
- package/src/components/Input/Input.vue +0 -123
- package/src/components/Input/Password.vue +0 -35
- package/src/components/Input/Textarea.vue +0 -78
- package/src/components/Input/input.scss +0 -302
- package/src/components/Kbd/Kbd.vue +0 -48
- package/src/components/Kbd/KbdGroup.vue +0 -28
- package/src/components/Kbd/kbd.scss +0 -19
- package/src/components/Modal/Confirm.vue +0 -56
- package/src/components/Modal/Modal.vue +0 -103
- package/src/components/Modal/modal.scss +0 -54
- package/src/components/OTP/OTP.vue +0 -133
- package/src/components/OTP/OTPItem.vue +0 -37
- package/src/components/OTP/otp.scss +0 -84
- package/src/components/Pagination/Pagination.vue +0 -92
- package/src/components/Pagination/pagination.ts +0 -78
- package/src/components/Popout/Popout.vue +0 -73
- package/src/components/Popout/popout.scss +0 -16
- package/src/components/Progress/Progress.vue +0 -103
- package/src/components/Progress/progress.scss +0 -47
- package/src/components/Radio/Radio.vue +0 -38
- package/src/components/Radio/RadioGroup.vue +0 -34
- package/src/components/Radio/radio.scss +0 -78
- package/src/components/Select/Select.vue +0 -212
- package/src/components/Select/select.scss +0 -82
- package/src/components/Sheet/Sheet.vue +0 -106
- package/src/components/Sheet/sheet.scss +0 -71
- package/src/components/Sidebar/Sidebar.vue +0 -116
- package/src/components/Sidebar/sidebar.scss +0 -124
- package/src/components/Skeleton/Skeleton.vue +0 -43
- package/src/components/Skeleton/skeleton.scss +0 -14
- package/src/components/Spinner/Spinner.vue +0 -42
- package/src/components/Spinner/spinner.scss +0 -47
- package/src/components/Switch/Switch.vue +0 -31
- package/src/components/Switch/switch.scss +0 -93
- package/src/components/Table/Cell.vue +0 -23
- package/src/components/Table/Head.vue +0 -66
- package/src/components/Table/Root.vue +0 -66
- package/src/components/Table/SelectAll.vue +0 -23
- package/src/components/Table/SelectRow.vue +0 -30
- package/src/components/Table/index.ts +0 -7
- package/src/components/Table/table.scss +0 -155
- package/src/components/Table/table.ts +0 -248
- package/src/components/Tabs/Tab.vue +0 -25
- package/src/components/Tabs/Tabs.vue +0 -90
- package/src/components/Tabs/tabs.scss +0 -87
- package/src/components/Toast/Toasts.vue +0 -52
- package/src/components/Toast/toast.scss +0 -45
- package/src/components/Toast/toast.ts +0 -75
- package/src/components/Tooltip/Tooltip.vue +0 -78
- package/src/components/Tooltip/tooltip.scss +0 -5
- package/src/examples/ExampleAccordions.vue +0 -69
- package/src/examples/ExampleAlerts.vue +0 -78
- package/src/examples/ExampleAvatars.vue +0 -44
- package/src/examples/ExampleBadges.vue +0 -48
- package/src/examples/ExampleBreadcrumbs.vue +0 -46
- package/src/examples/ExampleButtons.vue +0 -148
- package/src/examples/ExampleCalendars.vue +0 -40
- package/src/examples/ExampleCards.vue +0 -94
- package/src/examples/ExampleCheckboxes.vue +0 -123
- package/src/examples/ExampleCopyClipboard.vue +0 -47
- package/src/examples/ExampleDividers.vue +0 -39
- package/src/examples/ExampleDrawers.vue +0 -67
- package/src/examples/ExampleDropdowns.vue +0 -114
- package/src/examples/ExampleFlexGrid.vue +0 -124
- package/src/examples/ExampleInputs.vue +0 -236
- package/src/examples/ExampleKBD.vue +0 -65
- package/src/examples/ExampleModals.vue +0 -143
- package/src/examples/ExamplePalette.vue +0 -165
- package/src/examples/ExamplePopouts.vue +0 -41
- package/src/examples/ExampleSheets.vue +0 -77
- package/src/examples/ExampleSidebars.vue +0 -276
- package/src/examples/ExampleSkeletons.vue +0 -26
- package/src/examples/ExampleSpinners.vue +0 -80
- package/src/examples/ExampleTables.vue +0 -359
- package/src/examples/ExampleTabs.vue +0 -142
- package/src/examples/ExampleToasts.vue +0 -96
- package/src/examples/ExampleTooltips.vue +0 -70
- package/src/examples/shared/ExampleColor.vue +0 -28
- package/src/index.ts +0 -116
- package/src/internal/Backdrop/Backdrop.vue +0 -22
- package/src/internal/Backdrop/backdrop.scss +0 -34
- package/src/main.ts +0 -5
- package/src/shared/helpers.ts +0 -124
- package/src/shared/slots.ts +0 -61
- package/src/shared/theme.ts +0 -22
- package/src/shared/types.ts +0 -29
- package/src/style/animation.scss +0 -50
- package/src/style/core.scss +0 -133
- package/src/style/fonts.scss +0 -73
- package/src/style/layout.scss +0 -179
- package/src/style/media-query.scss +0 -29
- package/src/style/reset.scss +0 -135
- package/src/style/text.scss +0 -137
- package/src/style/theme.scss +0 -195
- package/src/style/tooltip.scss +0 -146
- package/src/style/typography.scss +0 -435
- package/src/style/utils.scss +0 -36
- package/src/style.scss +0 -1
- package/src/vite-env.d.ts +0 -1
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { createArray } from '../../shared/helpers'
|
|
2
|
-
|
|
3
|
-
export interface Pagination {
|
|
4
|
-
totalItems: number
|
|
5
|
-
currentPage: number
|
|
6
|
-
perPage: number
|
|
7
|
-
totalPages: number
|
|
8
|
-
startPage: number
|
|
9
|
-
endPage: number
|
|
10
|
-
startIndex: number
|
|
11
|
-
endIndex: number
|
|
12
|
-
pages: number[]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function paginate(
|
|
16
|
-
totalItems: number,
|
|
17
|
-
currentPage: number = 1,
|
|
18
|
-
perPage: number = 15,
|
|
19
|
-
maxPages: number = 5,
|
|
20
|
-
): Pagination {
|
|
21
|
-
// calculate total pages
|
|
22
|
-
const totalPages = Math.ceil(totalItems / perPage)
|
|
23
|
-
|
|
24
|
-
// ensure current page isn't out of range
|
|
25
|
-
if (currentPage < 1) {
|
|
26
|
-
currentPage = 1
|
|
27
|
-
}
|
|
28
|
-
else if (currentPage > totalPages) {
|
|
29
|
-
currentPage = totalPages
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
let startPage: number, endPage: number
|
|
33
|
-
if (totalPages <= maxPages) {
|
|
34
|
-
// total pages less than max so show all pages
|
|
35
|
-
startPage = 1
|
|
36
|
-
endPage = totalPages
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
// total pages more than max so calculate start and end pages
|
|
40
|
-
const maxPagesBeforeCurrentPage = Math.floor(maxPages / 2)
|
|
41
|
-
const maxPagesAfterCurrentPage = Math.ceil(maxPages / 2) - 1
|
|
42
|
-
if (currentPage <= maxPagesBeforeCurrentPage) {
|
|
43
|
-
// current page near the start
|
|
44
|
-
startPage = 1
|
|
45
|
-
endPage = maxPages
|
|
46
|
-
}
|
|
47
|
-
else if (currentPage + maxPagesAfterCurrentPage >= totalPages) {
|
|
48
|
-
// current page near the end
|
|
49
|
-
startPage = totalPages - maxPages + 1
|
|
50
|
-
endPage = totalPages
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
// current page somewhere in the middle
|
|
54
|
-
startPage = currentPage - maxPagesBeforeCurrentPage
|
|
55
|
-
endPage = currentPage + maxPagesAfterCurrentPage
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// calculate start and end item indexes
|
|
60
|
-
const startIndex = (currentPage - 1) * perPage
|
|
61
|
-
const endIndex = Math.min(startIndex + perPage - 1, totalItems - 1)
|
|
62
|
-
|
|
63
|
-
// create an array of pages to ng-repeat in the pager control
|
|
64
|
-
const pages = createArray((endPage + 1) - startPage).map(i => startPage + i)
|
|
65
|
-
|
|
66
|
-
// return object with all pager properties required by the view
|
|
67
|
-
return {
|
|
68
|
-
totalItems,
|
|
69
|
-
currentPage,
|
|
70
|
-
perPage,
|
|
71
|
-
totalPages,
|
|
72
|
-
startPage,
|
|
73
|
-
endPage,
|
|
74
|
-
startIndex,
|
|
75
|
-
endIndex,
|
|
76
|
-
pages,
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
<script setup lang='ts'>
|
|
2
|
-
import type { Placement, PopoutMaybeElement } from '../../shared/types'
|
|
3
|
-
import { autoUpdate, flip, offset, shift, useFloating } from '@floating-ui/vue'
|
|
4
|
-
import { onClickOutside } from '@vueuse/core'
|
|
5
|
-
import { toRef, useTemplateRef, watch } from 'vue'
|
|
6
|
-
import { getPlacementAnimationName } from '../../shared/helpers'
|
|
7
|
-
import './popout.scss'
|
|
8
|
-
|
|
9
|
-
export interface Props {
|
|
10
|
-
/**
|
|
11
|
-
* Reference to the HTML element the Popout is anchored to
|
|
12
|
-
*/
|
|
13
|
-
anchor: PopoutMaybeElement<HTMLElement>
|
|
14
|
-
/**
|
|
15
|
-
* Override the autoPlacement option
|
|
16
|
-
*/
|
|
17
|
-
placement?: Placement
|
|
18
|
-
/**
|
|
19
|
-
* Distance between the anchor and the rendered tooltip
|
|
20
|
-
*/
|
|
21
|
-
offset?: number
|
|
22
|
-
/**
|
|
23
|
-
* Set the visibility of the dropdown
|
|
24
|
-
*/
|
|
25
|
-
visible: boolean
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
29
|
-
offset: 8,
|
|
30
|
-
placement: 'top',
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
const emit = defineEmits<{
|
|
34
|
-
clickOutside: []
|
|
35
|
-
}>()
|
|
36
|
-
const popoutRef = useTemplateRef('popout')
|
|
37
|
-
const anchorRef = toRef(props.anchor)
|
|
38
|
-
|
|
39
|
-
const { floatingStyles, update } = useFloating(anchorRef, popoutRef, {
|
|
40
|
-
whileElementsMounted: autoUpdate,
|
|
41
|
-
strategy: 'fixed',
|
|
42
|
-
transform: false,
|
|
43
|
-
placement: props.placement,
|
|
44
|
-
middleware: [
|
|
45
|
-
shift({ padding: 8 }),
|
|
46
|
-
flip(),
|
|
47
|
-
offset(props.offset),
|
|
48
|
-
],
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
// Make sure to update the popout when the anchor is mounted
|
|
52
|
-
watch(() => props.anchor, (value) => {
|
|
53
|
-
if (value) {
|
|
54
|
-
anchorRef.value = value
|
|
55
|
-
update()
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
onClickOutside(popoutRef, () => emit('clickOutside'))
|
|
60
|
-
</script>
|
|
61
|
-
|
|
62
|
-
<template>
|
|
63
|
-
<Transition :name="getPlacementAnimationName(props.placement)">
|
|
64
|
-
<div
|
|
65
|
-
v-if="props.visible"
|
|
66
|
-
ref="popout"
|
|
67
|
-
:style="floatingStyles"
|
|
68
|
-
class="vui-popout"
|
|
69
|
-
>
|
|
70
|
-
<slot />
|
|
71
|
-
</div>
|
|
72
|
-
</Transition>
|
|
73
|
-
</template>
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
.vui-popout {
|
|
2
|
-
border-radius: var(--border-radius);
|
|
3
|
-
box-shadow: var(--box-shadow);
|
|
4
|
-
min-width: 80px;
|
|
5
|
-
background-color: var(--color-bg-medium);
|
|
6
|
-
border-radius: var(--border-radius-m);
|
|
7
|
-
z-index: var(--z-popout);
|
|
8
|
-
position: fixed;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
:root.light {
|
|
12
|
-
.vui-popout {
|
|
13
|
-
background-color: var(--color-bg);
|
|
14
|
-
box-shadow: var(--box-shadow-strong);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
<script setup lang='ts'>
|
|
2
|
-
import { whenever } from '@vueuse/core'
|
|
3
|
-
import { computed, onMounted, useTemplateRef, watch, watchEffect } from 'vue'
|
|
4
|
-
import { clamp, delay, formatUnitValue, isNil, randomMinMax } from '../../shared/helpers'
|
|
5
|
-
import './progress.scss'
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
/**
|
|
9
|
-
* Will randomly increment but never actually reach the end
|
|
10
|
-
*/
|
|
11
|
-
fake?: boolean
|
|
12
|
-
/**
|
|
13
|
-
* Indicator color. Use CSS color values or variables
|
|
14
|
-
*/
|
|
15
|
-
color?: string
|
|
16
|
-
/**
|
|
17
|
-
* Displays loader at the top of the page. It is only displayed when the
|
|
18
|
-
* progress is between 0 and 100 (exclusive).
|
|
19
|
-
*/
|
|
20
|
-
fixed?: boolean
|
|
21
|
-
/**
|
|
22
|
-
* Height
|
|
23
|
-
*/
|
|
24
|
-
height?: number | string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const {
|
|
28
|
-
fake,
|
|
29
|
-
color = 'var(--color-accent)',
|
|
30
|
-
fixed,
|
|
31
|
-
height,
|
|
32
|
-
} = defineProps<Props>()
|
|
33
|
-
|
|
34
|
-
const emit = defineEmits<{
|
|
35
|
-
done: []
|
|
36
|
-
}>()
|
|
37
|
-
|
|
38
|
-
const progressAmount = defineModel<number>({
|
|
39
|
-
default: 0,
|
|
40
|
-
set(value) {
|
|
41
|
-
return clamp(0, 100, value)
|
|
42
|
-
},
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
// Set height programatically
|
|
46
|
-
const progressRef = useTemplateRef('progress')
|
|
47
|
-
|
|
48
|
-
watchEffect(() => {
|
|
49
|
-
if (progressRef.value && !isNil(height)) {
|
|
50
|
-
progressRef.value.style.setProperty(
|
|
51
|
-
'--vui-progress-height',
|
|
52
|
-
formatUnitValue(height),
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
whenever(() => fake, fakeIncrement)
|
|
58
|
-
|
|
59
|
-
// Automatically / randomly increment but never reach 100% until
|
|
60
|
-
async function fakeIncrement() {
|
|
61
|
-
if (fake && progressAmount.value < 100) {
|
|
62
|
-
if (progressAmount.value > 90) {
|
|
63
|
-
// Only in crement by the fraction of the remaining amount
|
|
64
|
-
progressAmount.value += (100 - progressAmount.value) * 0.05
|
|
65
|
-
await delay(randomMinMax(500, 3000))
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
progressAmount.value += randomMinMax(1, 10)
|
|
69
|
-
await delay(randomMinMax(200, 12000))
|
|
70
|
-
}
|
|
71
|
-
fakeIncrement()
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
watch(progressAmount, (v) => {
|
|
76
|
-
if (v >= 100) {
|
|
77
|
-
emit('done')
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
onMounted(fakeIncrement)
|
|
82
|
-
|
|
83
|
-
const w = computed(() => `${clamp(0, 100, progressAmount.value)}%`)
|
|
84
|
-
const bC = computed(() => color)
|
|
85
|
-
</script>
|
|
86
|
-
|
|
87
|
-
<template>
|
|
88
|
-
<div
|
|
89
|
-
ref="progress"
|
|
90
|
-
class="vui-progress"
|
|
91
|
-
:class="{
|
|
92
|
-
fixed,
|
|
93
|
-
'fixed-active': progressAmount > 0 && progressAmount < 100,
|
|
94
|
-
}"
|
|
95
|
-
>
|
|
96
|
-
<div
|
|
97
|
-
class="vui-progress-indicator" :style="{
|
|
98
|
-
width: w,
|
|
99
|
-
backgroundColor: bC,
|
|
100
|
-
}"
|
|
101
|
-
/>
|
|
102
|
-
</div>
|
|
103
|
-
</template>
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
.vui-progress {
|
|
2
|
-
--vui-progress-height: 3px;
|
|
3
|
-
|
|
4
|
-
display: block;
|
|
5
|
-
width: 100%;
|
|
6
|
-
position: relative;
|
|
7
|
-
border-radius: 999px;
|
|
8
|
-
background-color: var(--color-bg-raised);
|
|
9
|
-
overflow: hidden;
|
|
10
|
-
height: var(--vui-progress-height);
|
|
11
|
-
|
|
12
|
-
&.fixed {
|
|
13
|
-
transition: var(--transition-slow);
|
|
14
|
-
transition-property: height;
|
|
15
|
-
position: fixed;
|
|
16
|
-
inset: 0;
|
|
17
|
-
bottom: unset;
|
|
18
|
-
height: 0;
|
|
19
|
-
border-radius: none !important;
|
|
20
|
-
|
|
21
|
-
&.fixed-active {
|
|
22
|
-
height: var(--vui-progress-height);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.vui-progress-indicator {
|
|
26
|
-
border-top-left-radius: unset !important;
|
|
27
|
-
border-bottom-left-radius: unset !important;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.vui-progress-indicator {
|
|
32
|
-
position: absolute;
|
|
33
|
-
z-index: var(--z-active);
|
|
34
|
-
inset: 0;
|
|
35
|
-
right: unset;
|
|
36
|
-
width: 0;
|
|
37
|
-
border-radius: 999px;
|
|
38
|
-
background-color: var(--color-text-lighter);
|
|
39
|
-
transition: var(--transition-slow);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
:root.light {
|
|
44
|
-
.vui-progress-indicator {
|
|
45
|
-
background-color: var(--);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
<script setup lang='ts'>
|
|
2
|
-
import { Icon } from '@iconify/vue'
|
|
3
|
-
import { computed, useId } from 'vue'
|
|
4
|
-
import './radio.scss'
|
|
5
|
-
|
|
6
|
-
export interface RadioProps {
|
|
7
|
-
label?: string
|
|
8
|
-
disabled?: boolean
|
|
9
|
-
value: any
|
|
10
|
-
accent?: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const {
|
|
14
|
-
label,
|
|
15
|
-
disabled,
|
|
16
|
-
value,
|
|
17
|
-
accent,
|
|
18
|
-
} = defineProps<RadioProps>()
|
|
19
|
-
const slots = defineSlots()
|
|
20
|
-
const checked = defineModel()
|
|
21
|
-
const id = useId()
|
|
22
|
-
const isChecked = computed(() => value === checked.value)
|
|
23
|
-
</script>
|
|
24
|
-
|
|
25
|
-
<template>
|
|
26
|
-
<div class="vui-radio" :class="{ disabled: !!disabled, checked: isChecked, accent }">
|
|
27
|
-
<input :id v-model="checked" type="radio" :value :checked="isChecked" :disabled>
|
|
28
|
-
<label :for="id">
|
|
29
|
-
<span class="vui-radio-icon">
|
|
30
|
-
<Icon :icon="isChecked ? 'ph:radio-button-fill' : 'ph:circle'" />
|
|
31
|
-
</span>
|
|
32
|
-
<p v-if="!slots.default" class="vui-radio-content">{{ label || value }}</p>
|
|
33
|
-
<div v-else class="vui-radio-content">
|
|
34
|
-
<slot />
|
|
35
|
-
</div>
|
|
36
|
-
</label>
|
|
37
|
-
</div>
|
|
38
|
-
</template>
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
<script setup lang='ts'>
|
|
2
|
-
import type { FlexProps } from '../Flex/Flex.vue'
|
|
3
|
-
import { enforceSlotType, useFlattenedSlot } from '../../shared/slots'
|
|
4
|
-
import Flex from '../Flex/Flex.vue'
|
|
5
|
-
|
|
6
|
-
interface Props extends FlexProps {
|
|
7
|
-
disabled?: boolean
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const {
|
|
11
|
-
disabled,
|
|
12
|
-
...flexProps
|
|
13
|
-
} = defineProps<Props>()
|
|
14
|
-
|
|
15
|
-
const slots = defineSlots()
|
|
16
|
-
|
|
17
|
-
const checked = defineModel()
|
|
18
|
-
|
|
19
|
-
const flattened = useFlattenedSlot(slots.default)
|
|
20
|
-
enforceSlotType(flattened, 'Radio')
|
|
21
|
-
</script>
|
|
22
|
-
|
|
23
|
-
<template>
|
|
24
|
-
<Flex v-bind="flexProps">
|
|
25
|
-
<Component
|
|
26
|
-
:is="vnode"
|
|
27
|
-
v-for="vnode of flattened"
|
|
28
|
-
:key="vnode.props.value"
|
|
29
|
-
v-bind="vnode.props"
|
|
30
|
-
v-model="checked"
|
|
31
|
-
:class="{ disabled: disabled || vnode.props.disabled }"
|
|
32
|
-
/>
|
|
33
|
-
</Flex>
|
|
34
|
-
</template>
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
.vui-radio {
|
|
2
|
-
--radio-size: 24px;
|
|
3
|
-
|
|
4
|
-
&.checked {
|
|
5
|
-
.vui-radio-icon svg {
|
|
6
|
-
color: var(--color-text);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
&.accent .vui-radio-icon svg {
|
|
10
|
-
color: var(--color-accent);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
&.disabled {
|
|
15
|
-
.vui-radio-icon {
|
|
16
|
-
opacity: 0.25;
|
|
17
|
-
cursor: not-allowed;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
input + label .vui-radio-content {
|
|
21
|
-
opacity: 0.4;
|
|
22
|
-
cursor: not-allowed;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.vui-radio-icon {
|
|
27
|
-
cursor: pointer;
|
|
28
|
-
width: var(--radio-size);
|
|
29
|
-
height: var(--radio-size);
|
|
30
|
-
|
|
31
|
-
svg {
|
|
32
|
-
width: 100%;
|
|
33
|
-
height: 100%;
|
|
34
|
-
color: var(--color-text);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
input {
|
|
39
|
-
width: 1px;
|
|
40
|
-
height: 1px;
|
|
41
|
-
position: absolute;
|
|
42
|
-
overflow: hidden;
|
|
43
|
-
outline: none !important;
|
|
44
|
-
opacity: 0;
|
|
45
|
-
|
|
46
|
-
&:focus-visible + label .vui-radio-icon {
|
|
47
|
-
outline: 2px solid var(--color-text);
|
|
48
|
-
border-radius: 999px;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
& + label {
|
|
52
|
-
display: grid;
|
|
53
|
-
grid-template-columns: 20px 1fr;
|
|
54
|
-
gap: 10px;
|
|
55
|
-
font-size: var(--font-size-s);
|
|
56
|
-
color: var(--color-text);
|
|
57
|
-
user-select: none;
|
|
58
|
-
|
|
59
|
-
.vui-radio-content {
|
|
60
|
-
display: block;
|
|
61
|
-
|
|
62
|
-
&:is(p) {
|
|
63
|
-
display: flex;
|
|
64
|
-
align-items: center;
|
|
65
|
-
min-height: var(--radio-size);
|
|
66
|
-
font-size: var(--font-size-m);
|
|
67
|
-
// line-height: var(--radio-size);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
:root.light {
|
|
75
|
-
.vui-radio.checked.accent .vui-radio-icon svg {
|
|
76
|
-
color: var(--color-accent);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
<!-- eslint-disable ts/consistent-type-definitions -->
|
|
2
|
-
<script setup lang='ts' generic="T">
|
|
3
|
-
import { Icon } from '@iconify/vue'
|
|
4
|
-
import { computed, onMounted, ref, useId, useTemplateRef } from 'vue'
|
|
5
|
-
import { searchString } from '../../shared/helpers'
|
|
6
|
-
import Button from '../Button/Button.vue'
|
|
7
|
-
import Dropdown from '../Dropdown/Dropdown.vue'
|
|
8
|
-
import DropdownItem from '../Dropdown/DropdownItem.vue'
|
|
9
|
-
import DropdownTitle from '../Dropdown/DropdownTitle.vue'
|
|
10
|
-
import Input from '../Input/Input.vue'
|
|
11
|
-
import '../Input/input.scss'
|
|
12
|
-
import './select.scss'
|
|
13
|
-
|
|
14
|
-
export type SelectOption = {
|
|
15
|
-
value: any
|
|
16
|
-
label: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type Props = {
|
|
20
|
-
single?: boolean
|
|
21
|
-
readonly?: boolean
|
|
22
|
-
options: SelectOption[]
|
|
23
|
-
label?: string
|
|
24
|
-
placeholder?: string
|
|
25
|
-
required?: boolean
|
|
26
|
-
expand?: boolean
|
|
27
|
-
hint?: string
|
|
28
|
-
search?: boolean
|
|
29
|
-
maxActiveOptions?: number
|
|
30
|
-
showClear?: boolean
|
|
31
|
-
disabled?: boolean
|
|
32
|
-
errors?: string[]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const {
|
|
36
|
-
expand,
|
|
37
|
-
readonly,
|
|
38
|
-
required,
|
|
39
|
-
hint,
|
|
40
|
-
placeholder,
|
|
41
|
-
options,
|
|
42
|
-
single = true,
|
|
43
|
-
search,
|
|
44
|
-
maxActiveOptions,
|
|
45
|
-
showClear,
|
|
46
|
-
disabled,
|
|
47
|
-
errors = [] as string[],
|
|
48
|
-
} = defineProps<Props>()
|
|
49
|
-
|
|
50
|
-
const selected = defineModel<SelectOption[] | undefined>()
|
|
51
|
-
const triggerRef = useTemplateRef('trigger')
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
function setValue(option: SelectOption) {
|
|
55
|
-
if (single) {
|
|
56
|
-
// Single
|
|
57
|
-
if (selected.value?.some(o => o.value === option.value) && !required) {
|
|
58
|
-
selected.value = undefined
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
selected.value = [option]
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
if (selected.value && selected.value?.some(o => o.value === option.value)) {
|
|
66
|
-
const values = selected.value.filter(o => o.value !== option.value)
|
|
67
|
-
// Cant remove the last value if it's required
|
|
68
|
-
if ((required && values.length > 0) || !required) {
|
|
69
|
-
selected.value = values.length > 0 ? values : undefined
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
if (!selected.value) {
|
|
74
|
-
selected.value = [option]
|
|
75
|
-
}
|
|
76
|
-
else if (!maxActiveOptions || (selected.value.length < maxActiveOptions)) {
|
|
77
|
-
selected.value?.push(option)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Search
|
|
84
|
-
const searchStr = ref()
|
|
85
|
-
const filteredOptions = computed(() => {
|
|
86
|
-
if (!searchStr.value || searchStr.value.length === 0)
|
|
87
|
-
return options
|
|
88
|
-
|
|
89
|
-
return options.filter((option) => {
|
|
90
|
-
return searchString(option.label, searchStr.value)
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
// Render text inside the button
|
|
95
|
-
const renderPlaceholder = computed(() => {
|
|
96
|
-
// Render placeholder or a default select option
|
|
97
|
-
if (!selected.value || selected.value.length === 0)
|
|
98
|
-
return placeholder ?? 'Select an option'
|
|
99
|
-
|
|
100
|
-
// Selected values
|
|
101
|
-
if (single)
|
|
102
|
-
return selected.value[0].label
|
|
103
|
-
|
|
104
|
-
// If amount of selected exceeds the active capacity
|
|
105
|
-
if (selected.value.length > 3) {
|
|
106
|
-
return `${selected.value.length} selected`
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Just list ALL selected optionsx
|
|
110
|
-
return selected.value.map(o => o.label).join(', ')
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
onMounted(() => {
|
|
114
|
-
if (readonly && showClear) {
|
|
115
|
-
console.warn('[Select] readonly and showClear are mutually exclusive props. The clear button will not show up if required is present.')
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
const dropdownRef = useTemplateRef('dropdown')
|
|
120
|
-
|
|
121
|
-
function clearValue() {
|
|
122
|
-
selected.value = undefined
|
|
123
|
-
dropdownRef.value?.close()
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const id = useId()
|
|
127
|
-
</script>
|
|
128
|
-
|
|
129
|
-
<template>
|
|
130
|
-
<div class="vui-input-container vui-select" :class="{ expand, required, readonly, disabled, 'has-errors': errors.length > 0 }">
|
|
131
|
-
<Dropdown ref="dropdown" :expand @close="triggerRef?.focus({ preventScroll: true })">
|
|
132
|
-
<template #trigger="{ toggle, isOpen }">
|
|
133
|
-
<div class="vui-input vui-select-trigger-content">
|
|
134
|
-
<label v-if="label" :for="id">{{ label }}</label>
|
|
135
|
-
<p v-if="hint" class="vui-input-hint">
|
|
136
|
-
{{ hint }}
|
|
137
|
-
</p>
|
|
138
|
-
|
|
139
|
-
<button
|
|
140
|
-
:id
|
|
141
|
-
ref="trigger"
|
|
142
|
-
class="vui-input-style vui-select-trigger-container"
|
|
143
|
-
:class="{ 'has-value': selected && selected.length > 0 }"
|
|
144
|
-
:disabled
|
|
145
|
-
@click="toggle"
|
|
146
|
-
>
|
|
147
|
-
<span>
|
|
148
|
-
{{ renderPlaceholder }}
|
|
149
|
-
</span>
|
|
150
|
-
<template v-if="showClear && !required && (selected && selected.length > 0)">
|
|
151
|
-
<div class="flex-1" />
|
|
152
|
-
<Button
|
|
153
|
-
plain
|
|
154
|
-
icon="ph:x"
|
|
155
|
-
square
|
|
156
|
-
size="s"
|
|
157
|
-
@click.stop="clearValue"
|
|
158
|
-
/>
|
|
159
|
-
</template>
|
|
160
|
-
<Icon :icon="isOpen ? 'ph:caret-up' : 'ph:caret-down'" />
|
|
161
|
-
</button>
|
|
162
|
-
</div>
|
|
163
|
-
</template>
|
|
164
|
-
|
|
165
|
-
<template #default="{ close, isOpen }">
|
|
166
|
-
<DropdownTitle v-if="search" sticky>
|
|
167
|
-
<Input
|
|
168
|
-
v-model="searchStr"
|
|
169
|
-
placeholder="Search..."
|
|
170
|
-
:focus="isOpen"
|
|
171
|
-
expand
|
|
172
|
-
>
|
|
173
|
-
<template #start>
|
|
174
|
-
<Icon icon="ph:magnifying-glass" />
|
|
175
|
-
</template>
|
|
176
|
-
</Input>
|
|
177
|
-
</DropdownTitle>
|
|
178
|
-
|
|
179
|
-
<p v-if="filteredOptions.length === 0" class="vue-select-no-results">
|
|
180
|
-
No results...
|
|
181
|
-
</p>
|
|
182
|
-
|
|
183
|
-
<DropdownItem
|
|
184
|
-
v-for="option in filteredOptions"
|
|
185
|
-
:key="option.value"
|
|
186
|
-
:class="{ selected: selected?.find(v => v.value === option.value) }"
|
|
187
|
-
:icon="selected?.find(v => v.value === option.value) ? 'ph:check-bold' : ''"
|
|
188
|
-
@click="() => {
|
|
189
|
-
if (readonly) return
|
|
190
|
-
|
|
191
|
-
setValue(option)
|
|
192
|
-
// In single mode, close modal after clicking
|
|
193
|
-
if (single && !(required && selected && selected[0].value === option.value)) {
|
|
194
|
-
close()
|
|
195
|
-
}
|
|
196
|
-
}"
|
|
197
|
-
>
|
|
198
|
-
{{ option.label }}
|
|
199
|
-
</DropdownItem>
|
|
200
|
-
</template>
|
|
201
|
-
</Dropdown>
|
|
202
|
-
|
|
203
|
-
<p v-if="maxActiveOptions && !single" class="vui-input-limit">
|
|
204
|
-
{{ `${selected ? selected.length : 0}/${maxActiveOptions}` }}
|
|
205
|
-
</p>
|
|
206
|
-
<ul v-if="errors.length > 0" class="vui-input-errors">
|
|
207
|
-
<li v-for="err in errors" :key="err">
|
|
208
|
-
{{ err }}
|
|
209
|
-
</li>
|
|
210
|
-
</ul>
|
|
211
|
-
</div>
|
|
212
|
-
</template>
|