orio-ui 0.1.0 → 0.1.1
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 +11 -16
- package/dist/module.cjs +0 -5
- package/dist/module.d.mts +0 -3
- package/dist/module.d.ts +0 -3
- package/dist/module.json +0 -12
- package/dist/module.mjs +0 -16
- package/dist/runtime/assets/css/animation.css +0 -1
- package/dist/runtime/assets/css/colors.css +0 -1
- package/dist/runtime/assets/css/cool-gradient-hover.css +0 -23
- package/dist/runtime/assets/css/main.css +0 -1
- package/dist/runtime/assets/css/scroll.css +0 -1
- package/dist/runtime/components/Button.vue +0 -102
- package/dist/runtime/components/CheckBox.vue +0 -93
- package/dist/runtime/components/ControlElement.vue +0 -39
- package/dist/runtime/components/DashedContainer.vue +0 -59
- package/dist/runtime/components/DatePicker.vue +0 -30
- package/dist/runtime/components/DateRangePicker.vue +0 -73
- package/dist/runtime/components/EmptyState.vue +0 -81
- package/dist/runtime/components/Icon.vue +0 -40
- package/dist/runtime/components/Input.vue +0 -48
- package/dist/runtime/components/LoadingSpinner.vue +0 -6
- package/dist/runtime/components/Modal.vue +0 -69
- package/dist/runtime/components/Popover.vue +0 -249
- package/dist/runtime/components/Selector.vue +0 -208
- package/dist/runtime/components/Tag.vue +0 -21
- package/dist/runtime/components/Textarea.vue +0 -53
- package/dist/runtime/components/view/Dates.vue +0 -59
- package/dist/runtime/components/view/Separator.vue +0 -26
- package/dist/runtime/components/view/Text.vue +0 -79
- package/dist/runtime/composables/index.d.ts +0 -4
- package/dist/runtime/composables/index.js +0 -4
- package/dist/runtime/composables/useApi.d.ts +0 -10
- package/dist/runtime/composables/useApi.js +0 -9
- package/dist/runtime/composables/useFuzzySearch.d.ts +0 -10
- package/dist/runtime/composables/useFuzzySearch.js +0 -22
- package/dist/runtime/composables/useModal.d.ts +0 -15
- package/dist/runtime/composables/useModal.js +0 -28
- package/dist/runtime/composables/useTheme.d.ts +0 -6
- package/dist/runtime/composables/useTheme.js +0 -23
- package/dist/runtime/index.d.ts +0 -20
- package/dist/runtime/index.js +0 -20
- package/dist/runtime/utils/icon-registry.d.ts +0 -2
- package/dist/runtime/utils/icon-registry.js +0 -26
- package/dist/types.d.mts +0 -7
- package/dist/types.d.ts +0 -7
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
defineEmits<{
|
|
3
|
-
(e: 'input', value: string): void;
|
|
4
|
-
}>();
|
|
5
|
-
|
|
6
|
-
const text = defineModel<string>({ default: '' });
|
|
7
|
-
</script>
|
|
8
|
-
|
|
9
|
-
<template>
|
|
10
|
-
<orio-control-element v-bind="$attrs">
|
|
11
|
-
<input
|
|
12
|
-
v-bind="$attrs"
|
|
13
|
-
v-model="text"
|
|
14
|
-
type="text"
|
|
15
|
-
class="text-input"
|
|
16
|
-
>
|
|
17
|
-
</orio-control-element>
|
|
18
|
-
</template>
|
|
19
|
-
|
|
20
|
-
<style scoped>
|
|
21
|
-
.text-input {
|
|
22
|
-
width: 100%;
|
|
23
|
-
padding: 0.5rem 0.75rem;
|
|
24
|
-
border: 1px solid var(--color-border);
|
|
25
|
-
border-radius: 4px;
|
|
26
|
-
font-size: 1rem;
|
|
27
|
-
color: var(--color-text);
|
|
28
|
-
background-color: var(--color-bg);
|
|
29
|
-
box-sizing: border-box;
|
|
30
|
-
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
31
|
-
}
|
|
32
|
-
.text-input::placeholder {
|
|
33
|
-
color: var(--color-muted);
|
|
34
|
-
}
|
|
35
|
-
.text-input:hover {
|
|
36
|
-
border-color: var(--color-accent);
|
|
37
|
-
}
|
|
38
|
-
.text-input:focus {
|
|
39
|
-
border-color: var(--color-accent);
|
|
40
|
-
box-shadow: 0 0 0 2px var(--color-accent-soft);
|
|
41
|
-
outline: none;
|
|
42
|
-
}
|
|
43
|
-
.text-input:disabled {
|
|
44
|
-
background-color: var(--color-surface);
|
|
45
|
-
color: var(--color-muted);
|
|
46
|
-
cursor: not-allowed;
|
|
47
|
-
}
|
|
48
|
-
</style>
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { ref, nextTick, watch } from 'vue';
|
|
3
|
-
import { useWindowSize, useTimeoutFn } from '@vueuse/core';
|
|
4
|
-
|
|
5
|
-
export interface OriginRect {
|
|
6
|
-
x: number;
|
|
7
|
-
y: number;
|
|
8
|
-
width: number;
|
|
9
|
-
height: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const props = defineProps<{
|
|
13
|
-
origin: OriginRect | null;
|
|
14
|
-
}>();
|
|
15
|
-
|
|
16
|
-
const show = defineModel<Boolean>('show');
|
|
17
|
-
|
|
18
|
-
const wrapper = ref<HTMLDivElement | null>(null);
|
|
19
|
-
|
|
20
|
-
const { width: windowWidth, height: windowHeight } = useWindowSize();
|
|
21
|
-
|
|
22
|
-
function animateToCenter(el: HTMLDivElement) {
|
|
23
|
-
useTimeoutFn(() => {
|
|
24
|
-
requestAnimationFrame(() => {
|
|
25
|
-
el.style.transform = 'translate(0, 0) scale(1)';
|
|
26
|
-
el.style.opacity = '1';
|
|
27
|
-
});
|
|
28
|
-
}, 100);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function animateOpen() {
|
|
32
|
-
await nextTick();
|
|
33
|
-
|
|
34
|
-
const el = wrapper.value;
|
|
35
|
-
if (!el) return;
|
|
36
|
-
if (!props.origin) {
|
|
37
|
-
animateToCenter(el);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const { x, y, width, height } = props.origin;
|
|
42
|
-
|
|
43
|
-
el.style.transform = `
|
|
44
|
-
translate(${x - windowWidth.value / 2}px, ${y - windowHeight.value / 2}px)
|
|
45
|
-
scale(${width / el.offsetWidth}, ${height / el.offsetHeight})
|
|
46
|
-
`;
|
|
47
|
-
el.style.opacity = '0.5';
|
|
48
|
-
|
|
49
|
-
animateToCenter(el);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
watch(show, (open) => {
|
|
53
|
-
if (open) animateOpen();
|
|
54
|
-
});
|
|
55
|
-
</script>
|
|
56
|
-
|
|
57
|
-
<template>
|
|
58
|
-
<transition name="overlay-fade">
|
|
59
|
-
<div v-if="show" class="overlay" @click.self="show = false">
|
|
60
|
-
<div ref="wrapper" class="modal">
|
|
61
|
-
<slot />
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
</transition>
|
|
65
|
-
</template>
|
|
66
|
-
|
|
67
|
-
<style scoped>
|
|
68
|
-
.overlay{align-items:center;backdrop-filter:blur(6px);background:rgba(0,0,0,.45);display:flex;inset:0;justify-content:center;position:fixed;transition:opacity .25s ease}.modal{background:#fff;border-radius:1rem;box-shadow:0 25px 60px rgba(0,0,0,.25);color:var(--color-muted);max-height:90vh;max-width:90vw;padding:24px;position:absolute;transform-origin:top left;transition:transform .35s cubic-bezier(.2,.8,.2,1),opacity .3s ease;width:380px}.modal,.overlay-fade-enter-from,.overlay-fade-leave-to{opacity:0}.overlay-fade-enter-active,.overlay-fade-leave-active{transition:opacity .25s ease}
|
|
69
|
-
</style>
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<div ref="trigger" @click="togglePopover()">
|
|
4
|
-
<slot :toggle="togglePopover" />
|
|
5
|
-
</div>
|
|
6
|
-
|
|
7
|
-
<Teleport to="body">
|
|
8
|
-
<Transition name="animate-fade-slide" appear>
|
|
9
|
-
<div
|
|
10
|
-
v-if="showPopover"
|
|
11
|
-
ref="popover"
|
|
12
|
-
class="popover"
|
|
13
|
-
:style="popoverStyle"
|
|
14
|
-
>
|
|
15
|
-
<slot name="content" :toggle="togglePopover" />
|
|
16
|
-
</div>
|
|
17
|
-
</Transition>
|
|
18
|
-
</Teleport>
|
|
19
|
-
</div>
|
|
20
|
-
</template>
|
|
21
|
-
|
|
22
|
-
<script setup lang="ts">
|
|
23
|
-
import { ref, computed, nextTick, onMounted, onBeforeUnmount, watch } from 'vue';
|
|
24
|
-
import { useElementBounding } from '@vueuse/core';
|
|
25
|
-
|
|
26
|
-
const props = defineProps({
|
|
27
|
-
/**
|
|
28
|
-
* Defines where the popover is placed relative to the trigger.
|
|
29
|
-
* Acceptable single values: 'top', 'bottom', 'left', 'right'
|
|
30
|
-
* Acceptable combos: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
|
|
31
|
-
* 'left-top', 'left-bottom', 'right-top', 'right-bottom'
|
|
32
|
-
* If you only provide 'top', 'bottom', 'left', or 'right',
|
|
33
|
-
* it aligns center by default.
|
|
34
|
-
*/
|
|
35
|
-
position: {
|
|
36
|
-
type: String,
|
|
37
|
-
default: 'bottom-left',
|
|
38
|
-
},
|
|
39
|
-
/**
|
|
40
|
-
* Distance (in px) between the popover and the trigger element.
|
|
41
|
-
*/
|
|
42
|
-
offset: {
|
|
43
|
-
type: Number,
|
|
44
|
-
default: 10,
|
|
45
|
-
},
|
|
46
|
-
disabled: {
|
|
47
|
-
type: Boolean,
|
|
48
|
-
default: false,
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const trigger = ref(null);
|
|
53
|
-
const popover = ref(null);
|
|
54
|
-
|
|
55
|
-
const { width: popoverWidth, height: popoverHeight } =
|
|
56
|
-
useElementBounding(popover);
|
|
57
|
-
|
|
58
|
-
const showPopover = ref(false);
|
|
59
|
-
const triggerRect = ref(null);
|
|
60
|
-
const popoverRect = ref(null);
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Calculates the inline style for the popover based on position & offset.
|
|
64
|
-
*/
|
|
65
|
-
const popoverStyle = computed(() => {
|
|
66
|
-
if (!showPopover.value || !triggerRect.value || !popoverRect.value) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const [main, sub = 'center'] = currentPosition.value.split('-');
|
|
71
|
-
const offset = props.offset;
|
|
72
|
-
|
|
73
|
-
const tRect = triggerRect.value;
|
|
74
|
-
const pRect = popoverRect.value;
|
|
75
|
-
|
|
76
|
-
let topValue = 0;
|
|
77
|
-
let leftValue = 0;
|
|
78
|
-
|
|
79
|
-
// Calculate vertical position (top)
|
|
80
|
-
if (main === 'top') {
|
|
81
|
-
topValue = tRect.top - offset - pRect.height;
|
|
82
|
-
} else if (main === 'bottom') {
|
|
83
|
-
topValue = tRect.bottom + offset;
|
|
84
|
-
} else {
|
|
85
|
-
// For 'left' or 'right' main, center vertically
|
|
86
|
-
topValue = tRect.top + tRect.height / 2 - pRect.height / 2;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Calculate horizontal position (left)
|
|
90
|
-
if (sub === 'left') {
|
|
91
|
-
leftValue = tRect.right - pRect.width;
|
|
92
|
-
} else if (sub === 'right') {
|
|
93
|
-
leftValue = tRect.left;
|
|
94
|
-
} else {
|
|
95
|
-
// 'center' is default horizontally
|
|
96
|
-
leftValue = tRect.left + tRect.width / 2 - pRect.width / 2;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// If the main position is 'left' or 'right', override horizontal positioning
|
|
100
|
-
if (main === 'left') {
|
|
101
|
-
leftValue = tRect.left - offset - pRect.width;
|
|
102
|
-
} else if (main === 'right') {
|
|
103
|
-
leftValue = tRect.right + offset;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
top: `${topValue}px`,
|
|
108
|
-
left: `${leftValue}px`,
|
|
109
|
-
};
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const currentPosition = ref(props.position);
|
|
113
|
-
|
|
114
|
-
const getFallbackPositions = (pos: string) => {
|
|
115
|
-
const [main, sub = 'center'] = pos.split('-');
|
|
116
|
-
|
|
117
|
-
const opposites: Record<string, string> = {
|
|
118
|
-
top: 'bottom',
|
|
119
|
-
bottom: 'top',
|
|
120
|
-
left: 'right',
|
|
121
|
-
right: 'left',
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const allPositions = [
|
|
125
|
-
`${main}-${sub}`,
|
|
126
|
-
`${opposites[main]}-${sub}`,
|
|
127
|
-
`${main}-center`,
|
|
128
|
-
`${opposites[main]}-center`,
|
|
129
|
-
`${sub}-${main}`, // e.g. left-top
|
|
130
|
-
`${sub}-${opposites[main]}`,
|
|
131
|
-
];
|
|
132
|
-
|
|
133
|
-
return [...new Set(allPositions)];
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
function checkIfFits(position: string, tRect: any, pRect: any, offset: number) {
|
|
137
|
-
const [main, sub = 'center'] = position.split('-');
|
|
138
|
-
|
|
139
|
-
const space = {
|
|
140
|
-
top: tRect.top,
|
|
141
|
-
bottom: window.innerHeight - tRect.bottom,
|
|
142
|
-
left: tRect.left,
|
|
143
|
-
right: window.innerWidth - tRect.right,
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
if (main === 'top' && space.top < pRect.height + offset) return false;
|
|
147
|
-
if (main === 'bottom' && space.bottom < pRect.height + offset) return false;
|
|
148
|
-
if (main === 'left' && space.left < pRect.width + offset) return false;
|
|
149
|
-
if (main === 'right' && space.right < pRect.width + offset) return false;
|
|
150
|
-
|
|
151
|
-
return true;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Calculates bounding client rects for trigger & popover,
|
|
156
|
-
* updating reactive refs.
|
|
157
|
-
*/
|
|
158
|
-
async function updateRects() {
|
|
159
|
-
await nextTick();
|
|
160
|
-
const triggerEl = trigger.value;
|
|
161
|
-
const popoverEl = popover.value?.firstElementChild || popover.value;
|
|
162
|
-
|
|
163
|
-
if (!triggerEl || !popoverEl) return;
|
|
164
|
-
|
|
165
|
-
const tRect = triggerEl.getBoundingClientRect();
|
|
166
|
-
triggerRect.value = tRect;
|
|
167
|
-
|
|
168
|
-
const fallbacks = getFallbackPositions(props.position);
|
|
169
|
-
|
|
170
|
-
for (const pos of fallbacks) {
|
|
171
|
-
// temporarily apply style to measure it
|
|
172
|
-
popoverEl.style.visibility = 'hidden';
|
|
173
|
-
popoverEl.style.display = 'block';
|
|
174
|
-
|
|
175
|
-
const pRect = popoverEl.getBoundingClientRect();
|
|
176
|
-
const fits = checkIfFits(pos, tRect, pRect, props.offset);
|
|
177
|
-
|
|
178
|
-
if (fits) {
|
|
179
|
-
popoverRect.value = pRect;
|
|
180
|
-
currentPosition.value = pos;
|
|
181
|
-
popoverEl.style.visibility = '';
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// fallback to original
|
|
187
|
-
popoverRect.value = popoverEl.getBoundingClientRect();
|
|
188
|
-
currentPosition.value = props.position;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Toggles popover visibility.
|
|
193
|
-
* @param {boolean|null} force - If `true`/`false`, force that state; if `null`, toggle.
|
|
194
|
-
*/
|
|
195
|
-
async function togglePopover(force: boolean | null = null) {
|
|
196
|
-
if (props.disabled) return;
|
|
197
|
-
showPopover.value = force !== null ? force : !showPopover.value;
|
|
198
|
-
if (!showPopover.value) return;
|
|
199
|
-
|
|
200
|
-
await nextTick();
|
|
201
|
-
updateRects();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Closes the popover if the click is outside trigger/popover.
|
|
206
|
-
*/
|
|
207
|
-
function handleDocumentClick(e: MouseEvent) {
|
|
208
|
-
if (!showPopover.value) return;
|
|
209
|
-
|
|
210
|
-
const clickedInsideTrigger =
|
|
211
|
-
trigger.value && trigger.value.contains(e.target as Node);
|
|
212
|
-
const clickedInsidePopover =
|
|
213
|
-
popover.value && popover.value.contains(e.target as Node);
|
|
214
|
-
|
|
215
|
-
if (!clickedInsideTrigger && !clickedInsidePopover) {
|
|
216
|
-
showPopover.value = false;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Updates position of popover on scroll/resize if popover is open.
|
|
222
|
-
*/
|
|
223
|
-
function redrawPopover() {
|
|
224
|
-
if (!showPopover.value) return;
|
|
225
|
-
updateRects();
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
watch([popoverWidth, popoverHeight], redrawPopover);
|
|
229
|
-
|
|
230
|
-
onMounted(() => {
|
|
231
|
-
document.addEventListener('click', handleDocumentClick);
|
|
232
|
-
window.addEventListener('scroll', redrawPopover, true);
|
|
233
|
-
window.addEventListener('resize', redrawPopover, true);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
onBeforeUnmount(() => {
|
|
237
|
-
document.removeEventListener('click', handleDocumentClick);
|
|
238
|
-
window.removeEventListener('scroll', redrawPopover, true);
|
|
239
|
-
window.removeEventListener('resize', redrawPopover, true);
|
|
240
|
-
});
|
|
241
|
-
</script>
|
|
242
|
-
<style scoped>
|
|
243
|
-
.popover {
|
|
244
|
-
border: 0;
|
|
245
|
-
background-color: transparent;
|
|
246
|
-
position: fixed;
|
|
247
|
-
z-index: 1;
|
|
248
|
-
}
|
|
249
|
-
</style>
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts" generic="T extends object">
|
|
2
|
-
import { computed, toRefs } from 'vue';
|
|
3
|
-
|
|
4
|
-
export type SelectableOption<T extends object = object> = string | T;
|
|
5
|
-
|
|
6
|
-
export interface SelectProps<T extends object = object> {
|
|
7
|
-
options: SelectableOption[];
|
|
8
|
-
multiple?: boolean;
|
|
9
|
-
field?: string;
|
|
10
|
-
optionName?: string;
|
|
11
|
-
placeholder?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const props = withDefaults(defineProps<SelectProps>(), {
|
|
15
|
-
placeholder: 'Select an option',
|
|
16
|
-
field: 'id',
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const { field, optionName, placeholder } = toRefs(props);
|
|
20
|
-
|
|
21
|
-
const modelValue = defineModel<SelectableOption | SelectableOption[] | null | undefined>({
|
|
22
|
-
required: true,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// Key to the object when option is not 'string'
|
|
26
|
-
const key = computed(() => field.value as Extract<keyof T, string>);
|
|
27
|
-
const label = computed(() => optionName.value as Extract<keyof T, string>);
|
|
28
|
-
|
|
29
|
-
const flatModalValue = computed(() => {
|
|
30
|
-
if (!modelValue.value) return null;
|
|
31
|
-
if (!props.multiple || !Array.isArray(modelValue.value))
|
|
32
|
-
return typeof modelValue.value === 'string'
|
|
33
|
-
? modelValue.value
|
|
34
|
-
: (modelValue.value as T)[key.value];
|
|
35
|
-
return modelValue.value.map((option) =>
|
|
36
|
-
typeof option === 'string' ? option : (option as T)[key.value]
|
|
37
|
-
);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
function toggleOption(option: SelectableOption, toggle: () => void) {
|
|
41
|
-
if (props.multiple) {
|
|
42
|
-
if (Array.isArray(modelValue.value)) {
|
|
43
|
-
const index = modelValue.value.findIndex((opt) =>
|
|
44
|
-
typeof option === 'string' ? option === opt : opt[key.value] === (option as T)[key.value]
|
|
45
|
-
);
|
|
46
|
-
if (index > -1) {
|
|
47
|
-
modelValue.value.splice(index, 1);
|
|
48
|
-
} else {
|
|
49
|
-
modelValue.value.push(option);
|
|
50
|
-
}
|
|
51
|
-
} else {
|
|
52
|
-
modelValue.value = [option];
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
modelValue.value = option;
|
|
56
|
-
// should later be turned off with some additional interaction modes
|
|
57
|
-
toggle();
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function isOptionSelected(option: SelectableOption): boolean {
|
|
62
|
-
if (typeof option === 'string') return modelValue.value === option;
|
|
63
|
-
return !!(
|
|
64
|
-
flatModalValue.value &&
|
|
65
|
-
(flatModalValue.value === (option as T)[key.value] ||
|
|
66
|
-
(flatModalValue.value as string[]).includes((option as T)[key.value] as string))
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function getOptionLabel(option: SelectableOption | undefined): string {
|
|
71
|
-
if (!option) return placeholder.value;
|
|
72
|
-
if (typeof option === 'string') return option;
|
|
73
|
-
if (optionName.value) return String((option as T)[label.value]);
|
|
74
|
-
return JSON.stringify(option);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function getOptionKey(option: SelectableOption): string | number {
|
|
78
|
-
if (typeof option === 'string') return option;
|
|
79
|
-
return String((option as T)[key.value]);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const selectorAttrs = computed(() => ({ getOptionKey, getOptionLabel }));
|
|
83
|
-
</script>
|
|
84
|
-
|
|
85
|
-
<template>
|
|
86
|
-
<orio-control-element>
|
|
87
|
-
<orio-popover position="bottom-right" :offset="5">
|
|
88
|
-
<template #default="{ toggle }">
|
|
89
|
-
<slot name="trigger" :toggle>
|
|
90
|
-
<div class="selector-trigger">
|
|
91
|
-
<slot name="trigger-content" :toggle v-bind="selectorAttrs" :attrs="$attrs">
|
|
92
|
-
<div class="trigger-content">
|
|
93
|
-
<slot name="trigger-label" :toggle v-bind="selectorAttrs" :attrs="$attrs">
|
|
94
|
-
<template v-if="!props.multiple">
|
|
95
|
-
{{ getOptionLabel(modelValue as T) }}
|
|
96
|
-
</template>
|
|
97
|
-
<template v-else-if="Array.isArray(modelValue)">
|
|
98
|
-
<span> {{ modelValue!.length }} selected </span>
|
|
99
|
-
</template>
|
|
100
|
-
</slot>
|
|
101
|
-
</div>
|
|
102
|
-
<orio-icon name="chevron-down" />
|
|
103
|
-
</slot>
|
|
104
|
-
</div>
|
|
105
|
-
</slot>
|
|
106
|
-
</template>
|
|
107
|
-
|
|
108
|
-
<template #content="{ toggle }">
|
|
109
|
-
<div class="selector-content">
|
|
110
|
-
<ul v-if="options.length">
|
|
111
|
-
<li
|
|
112
|
-
v-for="option in options"
|
|
113
|
-
:key="getOptionKey(option)"
|
|
114
|
-
:class="{ selected: isOptionSelected(option) }"
|
|
115
|
-
@click="toggleOption(option, toggle)"
|
|
116
|
-
>
|
|
117
|
-
<slot
|
|
118
|
-
name="option"
|
|
119
|
-
:option
|
|
120
|
-
:toggle
|
|
121
|
-
:selected="isOptionSelected(option)"
|
|
122
|
-
v-bind="selectorAttrs"
|
|
123
|
-
>
|
|
124
|
-
{{ getOptionLabel(option) }}
|
|
125
|
-
</slot>
|
|
126
|
-
</li>
|
|
127
|
-
</ul>
|
|
128
|
-
<slot v-else name="no-options">
|
|
129
|
-
<orio-empty-state title="No options found" size="small" />
|
|
130
|
-
</slot>
|
|
131
|
-
<slot name="options-addon" />
|
|
132
|
-
</div>
|
|
133
|
-
</template>
|
|
134
|
-
</orio-popover>
|
|
135
|
-
</orio-control-element>
|
|
136
|
-
</template>
|
|
137
|
-
|
|
138
|
-
<style scoped>
|
|
139
|
-
.selector-trigger {
|
|
140
|
-
z-index: 1;
|
|
141
|
-
min-height: 1.5rem;
|
|
142
|
-
user-select: none;
|
|
143
|
-
display: flex;
|
|
144
|
-
align-items: center;
|
|
145
|
-
justify-content: space-between;
|
|
146
|
-
cursor: pointer;
|
|
147
|
-
background: var(--color-bg);
|
|
148
|
-
border: 1px solid var(--color-border);
|
|
149
|
-
border-radius: 6px;
|
|
150
|
-
padding: 0.5rem 0.75rem;
|
|
151
|
-
font-size: 0.95rem;
|
|
152
|
-
color: var(--color-text);
|
|
153
|
-
transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
|
154
|
-
}
|
|
155
|
-
.selector-trigger:hover {
|
|
156
|
-
border-color: var(--color-accent);
|
|
157
|
-
background-color: var(--color-surface); /* subtle lift */
|
|
158
|
-
}
|
|
159
|
-
.selector-trigger:focus-within {
|
|
160
|
-
border-color: var(--color-accent);
|
|
161
|
-
box-shadow: 0 0 0 2px var(--color-surface);
|
|
162
|
-
}
|
|
163
|
-
.selector-trigger .icon {
|
|
164
|
-
color: var(--color-muted);
|
|
165
|
-
transition: color 0.2s ease;
|
|
166
|
-
}
|
|
167
|
-
.selector-trigger:hover .icon {
|
|
168
|
-
color: var(--color-accent);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.selector-content {
|
|
172
|
-
min-width: 15rem;
|
|
173
|
-
max-height: 20rem;
|
|
174
|
-
overflow: auto;
|
|
175
|
-
background: var(--color-bg);
|
|
176
|
-
border: 1px solid var(--color-border);
|
|
177
|
-
border-radius: 6px;
|
|
178
|
-
margin-top: 0.25rem;
|
|
179
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
180
|
-
}
|
|
181
|
-
.selector-content ul {
|
|
182
|
-
list-style: none;
|
|
183
|
-
padding: 0;
|
|
184
|
-
margin: 0;
|
|
185
|
-
}
|
|
186
|
-
.selector-content ul li {
|
|
187
|
-
padding: 0.5rem 0.75rem;
|
|
188
|
-
cursor: pointer;
|
|
189
|
-
transition: background-color 0.15s ease, color 0.15s ease;
|
|
190
|
-
color: var(--color-text);
|
|
191
|
-
}
|
|
192
|
-
.selector-content ul li:hover {
|
|
193
|
-
background-color: var(--color-surface); /* neutral lift */
|
|
194
|
-
}
|
|
195
|
-
.selector-content ul li.selected {
|
|
196
|
-
background-color: var(--color-accent);
|
|
197
|
-
color: var(--color-accent-soft);
|
|
198
|
-
font-weight: 500;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.trigger-content {
|
|
202
|
-
width: 100%;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
:deep(.popover) {
|
|
206
|
-
width: 100%;
|
|
207
|
-
}
|
|
208
|
-
</style>
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
export type TagStyle = 'neutral' | 'accent';
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
text: string;
|
|
6
|
-
variant?: TagStyle;
|
|
7
|
-
}
|
|
8
|
-
withDefaults(defineProps<Props>(), {
|
|
9
|
-
variant: 'neutral',
|
|
10
|
-
});
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
<template>
|
|
14
|
-
<span class="tag" :class="`tag--${variant}`">
|
|
15
|
-
{{ text }}
|
|
16
|
-
</span>
|
|
17
|
-
</template>
|
|
18
|
-
|
|
19
|
-
<style scoped>
|
|
20
|
-
.tag{border:1px solid transparent;border-radius:1rem;display:inline-block;font-size:.8rem;font-weight:500;line-height:1;max-height:1rem;padding:.25rem .6rem;-webkit-user-select:none;-moz-user-select:none;user-select:none}.tag--neutral{background-color:var(--color-surface);border-color:color-mix(in srgb,var(--color-border) 80%,var(--color-accent) 20%);color:var(--color-muted)}.tag--accent{background-color:var(--color-accent-soft);border-color:var(--color-accent-border);color:var(--color-accent)}
|
|
21
|
-
</style>
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { useAttrs } from 'vue';
|
|
3
|
-
|
|
4
|
-
const attrs = useAttrs();
|
|
5
|
-
defineEmits<{
|
|
6
|
-
(e: 'input', value: string): void;
|
|
7
|
-
}>();
|
|
8
|
-
|
|
9
|
-
const modelValue = defineModel<string>({ default: '' });
|
|
10
|
-
</script>
|
|
11
|
-
|
|
12
|
-
<template>
|
|
13
|
-
<orio-control-element v-bind="attrs">
|
|
14
|
-
<textarea
|
|
15
|
-
v-bind="attrs"
|
|
16
|
-
v-model="modelValue"
|
|
17
|
-
rows="4"
|
|
18
|
-
class="textarea"
|
|
19
|
-
/>
|
|
20
|
-
</orio-control-element>
|
|
21
|
-
</template>
|
|
22
|
-
|
|
23
|
-
<style scoped>
|
|
24
|
-
.textarea {
|
|
25
|
-
width: 100%;
|
|
26
|
-
padding: 0.5rem 0.75rem;
|
|
27
|
-
border: 1px solid var(--color-border);
|
|
28
|
-
border-radius: 4px;
|
|
29
|
-
font-size: 1rem;
|
|
30
|
-
line-height: 1.4;
|
|
31
|
-
color: var(--color-text);
|
|
32
|
-
background-color: var(--color-bg);
|
|
33
|
-
box-sizing: border-box;
|
|
34
|
-
resize: vertical; /* Let user resize vertically only */
|
|
35
|
-
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
36
|
-
}
|
|
37
|
-
.textarea::placeholder {
|
|
38
|
-
color: var(--color-muted);
|
|
39
|
-
}
|
|
40
|
-
.textarea:hover {
|
|
41
|
-
border-color: var(--color-accent);
|
|
42
|
-
}
|
|
43
|
-
.textarea:focus {
|
|
44
|
-
border-color: var(--color-accent);
|
|
45
|
-
box-shadow: 0 0 0 2px var(--color-accent-soft);
|
|
46
|
-
outline: none;
|
|
47
|
-
}
|
|
48
|
-
.textarea:disabled {
|
|
49
|
-
background-color: var(--color-surface);
|
|
50
|
-
color: var(--color-muted);
|
|
51
|
-
cursor: not-allowed;
|
|
52
|
-
}
|
|
53
|
-
</style>
|