orio-ui 0.1.0
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/LICENSE +21 -0
- package/README.md +237 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +3 -0
- package/dist/module.d.ts +3 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +16 -0
- package/dist/runtime/assets/css/animation.css +1 -0
- package/dist/runtime/assets/css/colors.css +1 -0
- package/dist/runtime/assets/css/cool-gradient-hover.css +23 -0
- package/dist/runtime/assets/css/main.css +1 -0
- package/dist/runtime/assets/css/scroll.css +1 -0
- package/dist/runtime/components/Button.vue +102 -0
- package/dist/runtime/components/CheckBox.vue +93 -0
- package/dist/runtime/components/ControlElement.vue +39 -0
- package/dist/runtime/components/DashedContainer.vue +59 -0
- package/dist/runtime/components/DatePicker.vue +30 -0
- package/dist/runtime/components/DateRangePicker.vue +73 -0
- package/dist/runtime/components/EmptyState.vue +81 -0
- package/dist/runtime/components/Icon.vue +40 -0
- package/dist/runtime/components/Input.vue +48 -0
- package/dist/runtime/components/LoadingSpinner.vue +6 -0
- package/dist/runtime/components/Modal.vue +69 -0
- package/dist/runtime/components/Popover.vue +249 -0
- package/dist/runtime/components/Selector.vue +208 -0
- package/dist/runtime/components/Tag.vue +21 -0
- package/dist/runtime/components/Textarea.vue +53 -0
- package/dist/runtime/components/view/Dates.vue +59 -0
- package/dist/runtime/components/view/Separator.vue +26 -0
- package/dist/runtime/components/view/Text.vue +79 -0
- package/dist/runtime/composables/index.d.ts +4 -0
- package/dist/runtime/composables/index.js +4 -0
- package/dist/runtime/composables/useApi.d.ts +10 -0
- package/dist/runtime/composables/useApi.js +9 -0
- package/dist/runtime/composables/useFuzzySearch.d.ts +10 -0
- package/dist/runtime/composables/useFuzzySearch.js +22 -0
- package/dist/runtime/composables/useModal.d.ts +15 -0
- package/dist/runtime/composables/useModal.js +28 -0
- package/dist/runtime/composables/useTheme.d.ts +6 -0
- package/dist/runtime/composables/useTheme.js +23 -0
- package/dist/runtime/index.d.ts +20 -0
- package/dist/runtime/index.js +20 -0
- package/dist/runtime/utils/icon-registry.d.ts +2 -0
- package/dist/runtime/utils/icon-registry.js +26 -0
- package/dist/types.d.mts +7 -0
- package/dist/types.d.ts +7 -0
- package/nuxt.config.ts +38 -0
- package/package.json +99 -0
- package/src/module.ts +16 -0
- package/src/runtime/assets/css/animation.css +88 -0
- package/src/runtime/assets/css/colors.css +142 -0
- package/src/runtime/assets/css/cool-gradient-hover.scss +33 -0
- package/src/runtime/assets/css/main.css +11 -0
- package/src/runtime/assets/css/scroll.css +46 -0
- package/src/runtime/components/Button.vue +110 -0
- package/src/runtime/components/CheckBox.vue +103 -0
- package/src/runtime/components/ControlElement.vue +42 -0
- package/src/runtime/components/DashedContainer.vue +60 -0
- package/src/runtime/components/DatePicker.vue +84 -0
- package/src/runtime/components/DateRangePicker.vue +74 -0
- package/src/runtime/components/EmptyState.vue +87 -0
- package/src/runtime/components/Icon.vue +51 -0
- package/src/runtime/components/Input.vue +54 -0
- package/src/runtime/components/LoadingSpinner.vue +6 -0
- package/src/runtime/components/Modal.vue +111 -0
- package/src/runtime/components/Popover.vue +249 -0
- package/src/runtime/components/Selector.vue +224 -0
- package/src/runtime/components/Tag.vue +45 -0
- package/src/runtime/components/Textarea.vue +59 -0
- package/src/runtime/components/view/Dates.vue +61 -0
- package/src/runtime/components/view/Separator.vue +30 -0
- package/src/runtime/components/view/Text.vue +83 -0
- package/src/runtime/composables/index.ts +4 -0
- package/src/runtime/composables/useApi.ts +26 -0
- package/src/runtime/composables/useFuzzySearch.ts +51 -0
- package/src/runtime/composables/useModal.ts +47 -0
- package/src/runtime/composables/useTheme.ts +31 -0
- package/src/runtime/index.ts +25 -0
- package/src/runtime/utils/icon-registry.ts +41 -0
|
@@ -0,0 +1,208 @@
|
|
|
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>
|
|
@@ -0,0 +1,21 @@
|
|
|
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>
|
|
@@ -0,0 +1,53 @@
|
|
|
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>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, toRefs } from 'vue';
|
|
3
|
+
|
|
4
|
+
export interface ResumeDate {
|
|
5
|
+
startDate: string;
|
|
6
|
+
endDate?: string | null; // undefined - mean single date, null - means "Present"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
dates: ResumeDate;
|
|
11
|
+
month?: boolean; // Optional prop to indicate if the date should be displayed as month/year
|
|
12
|
+
size?: 'small' | 'medium' | 'large';
|
|
13
|
+
type?: 'text' | 'title' | 'subtitle' | 'italics';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
17
|
+
size: 'small',
|
|
18
|
+
type: 'italics',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const { dates } = toRefs(props);
|
|
22
|
+
|
|
23
|
+
function formatMonthYear(value: string) {
|
|
24
|
+
if (!value) return '';
|
|
25
|
+
if (!props.month)
|
|
26
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
27
|
+
day: 'numeric',
|
|
28
|
+
month: 'short',
|
|
29
|
+
year: 'numeric',
|
|
30
|
+
}).format(new Date(value));
|
|
31
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
32
|
+
month: 'short',
|
|
33
|
+
year: 'numeric',
|
|
34
|
+
}).format(new Date(value));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const startDate = computed(() => formatMonthYear(dates.value.startDate));
|
|
38
|
+
|
|
39
|
+
const endDate = computed(() => {
|
|
40
|
+
if (dates.value.endDate === undefined) return null;
|
|
41
|
+
return dates.value.endDate !== null
|
|
42
|
+
? formatMonthYear(dates.value.endDate)
|
|
43
|
+
: 'Present';
|
|
44
|
+
});
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<div class="view-date">
|
|
49
|
+
<orio-view-text :model-value="startDate" :type :size />
|
|
50
|
+
<template v-if="endDate">
|
|
51
|
+
<span v-if="startDate"> - </span>
|
|
52
|
+
<orio-view-text :model-value="endDate" :type :size />
|
|
53
|
+
</template>
|
|
54
|
+
</div>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<style scoped>
|
|
58
|
+
.view-date *{display:inline}
|
|
59
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
style?: 'dotted' | 'dashed' | 'solid' | 'double' | 'groove' | 'ridge';
|
|
6
|
+
size?: number | string;
|
|
7
|
+
margin?: number | string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
11
|
+
style: 'solid',
|
|
12
|
+
size: 1,
|
|
13
|
+
margin: 1,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const sizePx = computed(() => `${props.size}px`);
|
|
17
|
+
const margin = computed(() => `${props.margin}rem`);
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<div />
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<style scoped>
|
|
25
|
+
div{border-block-end:v-bind(sizePx) v-bind(style) var(--color-border);margin-block:v-bind(margin);width:100%}
|
|
26
|
+
</style>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, useAttrs } from 'vue';
|
|
3
|
+
|
|
4
|
+
export type TextTypes = 'text' | 'title' | 'subtitle' | 'italics';
|
|
5
|
+
|
|
6
|
+
export interface TextProps {
|
|
7
|
+
type?: TextTypes;
|
|
8
|
+
size?: 'small' | 'medium' | 'large' | 'extra-large';
|
|
9
|
+
uppercase?: boolean;
|
|
10
|
+
icon?: string | null;
|
|
11
|
+
lineClamp?: number | string;
|
|
12
|
+
}
|
|
13
|
+
const props = withDefaults(defineProps<TextProps>(), {
|
|
14
|
+
type: 'text',
|
|
15
|
+
size: 'medium',
|
|
16
|
+
uppercase: false,
|
|
17
|
+
icon: null,
|
|
18
|
+
lineClamp: undefined,
|
|
19
|
+
});
|
|
20
|
+
const attrs = useAttrs();
|
|
21
|
+
const modelValue = defineModel<string>();
|
|
22
|
+
|
|
23
|
+
const clampLines = computed(() => Number(props.lineClamp ?? 1));
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<div :class="[type, size, { uppercase, clamp: !!lineClamp }]" v-bind="attrs">
|
|
28
|
+
<orio-icon v-if="icon" :name="icon" />
|
|
29
|
+
<slot>
|
|
30
|
+
{{ modelValue }}
|
|
31
|
+
</slot>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<style scoped>
|
|
36
|
+
div {
|
|
37
|
+
white-space: pre-wrap;
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
gap: 0.25rem;
|
|
41
|
+
}
|
|
42
|
+
div.clamp {
|
|
43
|
+
display: -webkit-box;
|
|
44
|
+
overflow: hidden;
|
|
45
|
+
line-clamp: v-bind(clampLines);
|
|
46
|
+
-webkit-line-clamp: v-bind(clampLines);
|
|
47
|
+
-webkit-box-orient: vertical;
|
|
48
|
+
}
|
|
49
|
+
div.uppercase {
|
|
50
|
+
text-transform: uppercase;
|
|
51
|
+
}
|
|
52
|
+
div.text {
|
|
53
|
+
color: var(--color-text);
|
|
54
|
+
}
|
|
55
|
+
div.title {
|
|
56
|
+
font-weight: bold;
|
|
57
|
+
color: var(--color-text);
|
|
58
|
+
}
|
|
59
|
+
div.subtitle {
|
|
60
|
+
font-weight: semi-bold;
|
|
61
|
+
color: var(--color-muted);
|
|
62
|
+
}
|
|
63
|
+
div.italics {
|
|
64
|
+
font-style: italic;
|
|
65
|
+
color: var(--color-muted);
|
|
66
|
+
}
|
|
67
|
+
div.small {
|
|
68
|
+
font-size: 0.75rem;
|
|
69
|
+
}
|
|
70
|
+
div.medium {
|
|
71
|
+
font-size: 0.875rem;
|
|
72
|
+
}
|
|
73
|
+
div.large {
|
|
74
|
+
font-size: 1.25rem;
|
|
75
|
+
}
|
|
76
|
+
div.extra-large {
|
|
77
|
+
font-size: 1.75rem;
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type RequestBody = Record<string, unknown>;
|
|
2
|
+
export type RequestMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
3
|
+
export interface ApiOptions {
|
|
4
|
+
method?: RequestMethod;
|
|
5
|
+
body?: RequestBody;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
query?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export declare function useApi<T = unknown>(url: string): Promise<T>;
|
|
10
|
+
export declare function useApi<T = unknown>(url: string, options: ApiOptions): Promise<T>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type MaybeRef } from 'vue';
|
|
2
|
+
import { useFuse, type FuseOptions } from '@vueuse/integrations/useFuse';
|
|
3
|
+
/**
|
|
4
|
+
* Search using Fuse.js fuzzy search
|
|
5
|
+
* @param dataSource - Array of strings or objects to search
|
|
6
|
+
* @param search - Search query string
|
|
7
|
+
* @param options - Fuse.js options (e.g., { keys: ['name'] })
|
|
8
|
+
*/
|
|
9
|
+
export declare function useFuzzySearch(dataSource: MaybeRef<string[]>, search: MaybeRef<string>): ReturnType<typeof useFuse>;
|
|
10
|
+
export declare function useFuzzySearch<T extends object>(dataSource: MaybeRef<T[]>, search: MaybeRef<string>, options: FuseOptions<T>): ReturnType<typeof useFuse>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { computed, unref } from "vue";
|
|
2
|
+
import { useFuse } from "@vueuse/integrations/useFuse";
|
|
3
|
+
export function useFuzzySearch(dataSource, search, options) {
|
|
4
|
+
const isObjectArray = !!options;
|
|
5
|
+
if (!isObjectArray) {
|
|
6
|
+
const wrappedData = computed(() => {
|
|
7
|
+
const data = unref(dataSource);
|
|
8
|
+
return data.map((str) => ({ value: str }));
|
|
9
|
+
});
|
|
10
|
+
const { results } = useFuse(search, wrappedData, {
|
|
11
|
+
fuseOptions: { keys: ["value"] },
|
|
12
|
+
matchAllWhenSearchEmpty: true
|
|
13
|
+
});
|
|
14
|
+
return computed(() => results.value.map(({ item }) => item.value));
|
|
15
|
+
} else {
|
|
16
|
+
const { results } = useFuse(search, dataSource, {
|
|
17
|
+
fuseOptions: options,
|
|
18
|
+
matchAllWhenSearchEmpty: true
|
|
19
|
+
});
|
|
20
|
+
return computed(() => results.value.map(({ item }) => item));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface OriginRect {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ModalProps {
|
|
8
|
+
show: Boolean;
|
|
9
|
+
origin: OriginRect | null;
|
|
10
|
+
'onUpdate:show': (state: boolean) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function useModal(): {
|
|
13
|
+
modalProps: import("vue").Ref<any, any>;
|
|
14
|
+
openModal: (event?: MouseEvent) => void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
export function useModal() {
|
|
3
|
+
const modalProps = ref({
|
|
4
|
+
show: false,
|
|
5
|
+
origin: null,
|
|
6
|
+
"onUpdate:show": (state) => updateShow(state)
|
|
7
|
+
});
|
|
8
|
+
function updateShow(state) {
|
|
9
|
+
modalProps.value.show = state;
|
|
10
|
+
}
|
|
11
|
+
function openModal(event) {
|
|
12
|
+
modalProps.value.origin = null;
|
|
13
|
+
if (!event) {
|
|
14
|
+
modalProps.value.show = true;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const target = event.target;
|
|
18
|
+
const rect = target.getBoundingClientRect();
|
|
19
|
+
modalProps.value.origin = {
|
|
20
|
+
x: rect.left,
|
|
21
|
+
y: rect.top,
|
|
22
|
+
width: rect.width,
|
|
23
|
+
height: rect.height
|
|
24
|
+
};
|
|
25
|
+
modalProps.value.show = true;
|
|
26
|
+
}
|
|
27
|
+
return { modalProps, openModal };
|
|
28
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { onMounted } from "vue";
|
|
2
|
+
import { useLocalStorage } from "@vueuse/core";
|
|
3
|
+
export function useTheme() {
|
|
4
|
+
const theme = useLocalStorage("orio-theme", "navy");
|
|
5
|
+
const mode = useLocalStorage("orio-mode", "dark");
|
|
6
|
+
function setTheme(name) {
|
|
7
|
+
theme.value = name;
|
|
8
|
+
if (typeof document !== "undefined") {
|
|
9
|
+
document.documentElement.setAttribute("data-theme", name);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function setMode(name) {
|
|
13
|
+
mode.value = name;
|
|
14
|
+
if (typeof document !== "undefined") {
|
|
15
|
+
document.documentElement.setAttribute("data-mode", name);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
onMounted(() => {
|
|
19
|
+
setTheme(theme.value);
|
|
20
|
+
setMode(mode.value);
|
|
21
|
+
});
|
|
22
|
+
return { theme, setTheme, mode, setMode };
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { default as Button } from './components/Button.vue.js';
|
|
2
|
+
export { default as Input } from './components/Input.vue.js';
|
|
3
|
+
export { default as Textarea } from './components/Textarea.vue.js';
|
|
4
|
+
export { default as CheckBox } from './components/CheckBox.vue.js';
|
|
5
|
+
export { default as DatePicker } from './components/DatePicker.vue.js';
|
|
6
|
+
export { default as DateRangePicker } from './components/DateRangePicker.vue.js';
|
|
7
|
+
export { default as Selector } from './components/Selector.vue.js';
|
|
8
|
+
export { default as Tag } from './components/Tag.vue.js';
|
|
9
|
+
export { default as Icon } from './components/Icon.vue.js';
|
|
10
|
+
export { default as LoadingSpinner } from './components/LoadingSpinner.vue.js';
|
|
11
|
+
export { default as Modal } from './components/Modal.vue.js';
|
|
12
|
+
export { default as Popover } from './components/Popover.vue.js';
|
|
13
|
+
export { default as EmptyState } from './components/EmptyState.vue.js';
|
|
14
|
+
export { default as DashedContainer } from './components/DashedContainer.vue.js';
|
|
15
|
+
export { default as ControlElement } from './components/ControlElement.vue.js';
|
|
16
|
+
export { default as ViewText } from './components/view/Text.vue.js';
|
|
17
|
+
export { default as ViewDates } from './components/view/Dates.vue.js';
|
|
18
|
+
export { default as ViewSeparator } from './components/view/Separator.vue.js';
|
|
19
|
+
export * from './composables/index.js';
|
|
20
|
+
export { iconRegistry, type IconName } from './utils/icon-registry.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { default as Button } from "./components/Button.vue";
|
|
2
|
+
export { default as Input } from "./components/Input.vue";
|
|
3
|
+
export { default as Textarea } from "./components/Textarea.vue";
|
|
4
|
+
export { default as CheckBox } from "./components/CheckBox.vue";
|
|
5
|
+
export { default as DatePicker } from "./components/DatePicker.vue";
|
|
6
|
+
export { default as DateRangePicker } from "./components/DateRangePicker.vue";
|
|
7
|
+
export { default as Selector } from "./components/Selector.vue";
|
|
8
|
+
export { default as Tag } from "./components/Tag.vue";
|
|
9
|
+
export { default as Icon } from "./components/Icon.vue";
|
|
10
|
+
export { default as LoadingSpinner } from "./components/LoadingSpinner.vue";
|
|
11
|
+
export { default as Modal } from "./components/Modal.vue";
|
|
12
|
+
export { default as Popover } from "./components/Popover.vue";
|
|
13
|
+
export { default as EmptyState } from "./components/EmptyState.vue";
|
|
14
|
+
export { default as DashedContainer } from "./components/DashedContainer.vue";
|
|
15
|
+
export { default as ControlElement } from "./components/ControlElement.vue";
|
|
16
|
+
export { default as ViewText } from "./components/view/Text.vue";
|
|
17
|
+
export { default as ViewDates } from "./components/view/Dates.vue";
|
|
18
|
+
export { default as ViewSeparator } from "./components/view/Separator.vue";
|
|
19
|
+
export * from "./composables/index.js";
|
|
20
|
+
export { iconRegistry } from "./utils/icon-registry.js";
|