orio-ui 1.3.0 → 1.4.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/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0 || ^4.0.0"
6
6
  },
7
- "version": "1.3.0",
7
+ "version": "1.4.1",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -1 +1 @@
1
- :root{--color-bg:#fff;--color-surface:#f7f8fa;--color-text:#0e1116;--color-muted:#626a78;--color-border:#bfbfc2;--color-accent:#1f3a8a;--color-accent-ink:#fff;--color-accent-soft:color-mix(in srgb,var(--color-accent) 10%,#e3e3e3);--color-accent-border:color-mix(in srgb,var(--color-accent) 40%,#c7d2fe);--color-accent-hover:color-mix(in srgb,var(--color-accent) 85%,#000 15%);--color-accent-active:color-mix(in srgb,var(--color-accent) 35%,#000 65%)}:root[data-mode=light]{--color-bg:#fff;--color-surface:#f7f8fa;--color-text:#0e1116;--color-muted:#626a78;--color-border:#bfbfc2}:root[data-mode=dark]{--color-bg:#0e1116;--color-surface:#1a1d23;--color-text:#fff;--color-muted:#a2a9b6;--color-border:#2e333d;--color-accent-ink:#fff;--color-accent-soft:color-mix(in srgb,var(--color-accent) 12%,#0e1116);--color-accent-border:color-mix(in srgb,var(--color-accent) 40%,#0e1116);--color-accent-hover:color-mix(in srgb,var(--color-accent) 30%,#fff 70%);--color-accent-active:color-mix(in srgb,var(--color-accent) 80%,#fff 25%)}:root[data-theme=normal]{--color-accent:#7a7a7a}:root[data-theme=navy]{--color-accent:#5c80c1}:root[data-theme=teal]{--color-accent:#27b9ab}:root[data-theme=forest]{--color-accent:#5db18b}:root[data-theme=wine]{--color-accent:#862737}:root[data-theme=royal]{--color-accent:#293da3}:root{--color-red:#dc2626;--color-red-ink:#fff;--color-red-soft:#fef2f2;--color-red-border:#fecaca;--color-red-hover:color-mix(in srgb,var(--color-red) 85%,#000 15%);--color-red-active:color-mix(in srgb,var(--color-red) 75%,#000 25%);--color-orange:#ea580c;--color-orange-ink:#fff;--color-orange-soft:#fff7ed;--color-orange-border:#fed7aa;--color-orange-hover:color-mix(in srgb,var(--color-orange) 85%,#000 15%);--color-orange-active:color-mix(in srgb,var(--color-orange) 75%,#000 25%);--color-yellow:#ca8a04;--color-yellow-ink:#fff;--color-yellow-soft:#fefce8;--color-yellow-border:#fef08a;--color-yellow-hover:color-mix(in srgb,var(--color-yellow) 85%,#000 15%);--color-yellow-active:color-mix(in srgb,var(--color-yellow) 75%,#000 25%);--color-green:#15803d;--color-green-ink:#fff;--color-green-soft:#f0fdf4;--color-green-border:#bbf7d0;--color-green-hover:color-mix(in srgb,var(--color-green) 85%,#000 15%);--color-green-active:color-mix(in srgb,var(--color-green) 75%,#000 25%);--color-teal:#0d9488;--color-teal-ink:#fff;--color-teal-soft:#f0fdfa;--color-teal-border:#99f6e4;--color-teal-hover:color-mix(in srgb,var(--color-teal) 85%,#000 15%);--color-teal-active:color-mix(in srgb,var(--color-teal) 75%,#000 25%);--color-blue:#2563eb;--color-blue-ink:#fff;--color-blue-soft:#eff6ff;--color-blue-border:#bfdbfe;--color-blue-hover:color-mix(in srgb,var(--color-blue) 85%,#000 15%);--color-blue-active:color-mix(in srgb,var(--color-blue) 75%,#000 25%);--color-purple:#7e22ce;--color-purple-ink:#fff;--color-purple-soft:#faf5ff;--color-purple-border:#e9d5ff;--color-purple-hover:color-mix(in srgb,var(--color-purple) 85%,#000 15%);--color-purple-active:color-mix(in srgb,var(--color-purple) 75%,#000 25%);--color-pink:#db2777;--color-pink-ink:#fff;--color-pink-soft:#fdf2f8;--color-pink-border:#fbcfe8;--color-pink-hover:color-mix(in srgb,var(--color-pink) 85%,#000 15%);--color-pink-active:color-mix(in srgb,var(--color-pink) 75%,#000 25%)}
1
+ :root{--color-bg:#fff;--color-surface:#f7f8fa;--color-text:#0e1116;--color-muted:#626a78;--color-border:#bfbfc2;--color-accent:#1f3a8a;--color-accent-ink:#fff;--color-accent-soft-base:color-mix(in srgb,var(--color-accent) 15%,#e3e3e3);--color-accent-soft:var(--color-accent-soft-base);--color-accent-soft-darker:color-mix(in srgb,var(--color-accent-soft),#000 70%);--color-accent-border:color-mix(in srgb,var(--color-accent) 40%,#c7d2fe);--color-accent-hover:color-mix(in srgb,var(--color-accent) 85%,#000 15%);--color-accent-active:color-mix(in srgb,var(--color-accent),#000 60%)}:root[data-mode=light]{--color-bg:#fff;--color-surface:#f7f8fa;--color-text:#0e1116;--color-muted:#626a78;--color-border:#bfbfc2}:root[data-mode=dark]{--color-bg:#0e1116;--color-surface:#1a1d23;--color-text:#fff;--color-muted:#a2a9b6;--color-border:#2e333d;--color-accent-ink:#fff;--color-accent-soft-base:color-mix(in srgb,var(--color-accent) 12%,#0e1116);--color-accent-soft:var(--color-accent-soft-base);--color-accent-border:color-mix(in srgb,var(--color-accent) 40%,#0e1116);--color-accent-hover:color-mix(in srgb,var(--color-accent) 30%,#fff 70%);--color-accent-active:color-mix(in srgb,var(--color-accent) 80%,#fff 25%)}:root[data-theme=normal]{--color-accent:#ced1d5;--color-accent-ink:#2a2a2b;--color-accent-hover:color-mix(in srgb,var(--color-accent-ink) 90%,#fff 10%)}:root[data-theme=navy]{--color-accent:#5c80c1;--color-accent-soft-darker:var(--color-accent-soft)}:root[data-theme=teal]{--color-accent:#27b9ab}:root[data-theme=forest]{--color-accent:#5db18b}:root[data-theme=wine]{--color-accent:#862737;--color-accent-soft:color-mix(in srgb,var(--color-accent) 5%,#fff);--color-accent-soft-darker:var(--color-accent-soft)}:root[data-theme=royal]{--color-accent:#293da3;--color-accent-soft:color-mix(in srgb,var(--color-accent) 5%,#fff);--color-accent-soft-darker:var(--color-accent-soft)}
@@ -1,3 +1,3 @@
1
- .gradient-hover{--gh-color:#fff;--gh-angle:150deg;--gh-peek-x:95%;--gh-mix-base:#000;background-image:linear-gradient(var(--gh-angle),color-mix(in srgb,var(--gh-color) 90%,var(--gh-mix-base)) 10%,var(--gh-color) 50%,color-mix(in srgb,var(--gh-color) 80%,var(--gh-mix-base)) 80%);background-position:0 0;background-repeat:no-repeat;background-size:0 0;transition:background-position .8s ease,border-color .2s ease;will-change:background-size,background-position}.gradient-hover:hover{background-position:var(--gh-peek-x) 0;background-size:200% 100%}:root[data-mode=dark] .gradient-hover{--gh-color:var(
1
+ .gradient-hover{--gh-color:#fff;--gh-angle:160deg;--gh-peek-x:95%;--gh-mix-base:#000;background-image:linear-gradient(var(--gh-angle),color-mix(in srgb,var(--gh-color) 90%,var(--gh-mix-base)) 10%,var(--gh-color) 50%,color-mix(in srgb,var(--gh-color) 80%,var(--gh-mix-base)) 80%);background-position:0 0;background-repeat:no-repeat;background-size:0 0;transition:background-position .4s ease,border-color .2s ease;will-change:background-size,background-position}.gradient-hover:hover{background-position:var(--gh-peek-x) 0;background-size:200% 250%}:root[data-mode=dark] .gradient-hover{--gh-color:var(
2
2
  --color-accent
3
3
  );--gh-mix-base:var(--color-bg)}
@@ -70,7 +70,7 @@ button.icon-only {
70
70
  border-radius: 50%;
71
71
  }
72
72
  button:disabled, button:disabled:hover {
73
- background-color: var(--color-accent-soft);
73
+ background-color: var(--color-accent-soft-base);
74
74
  color: var(--color-muted);
75
75
  border-color: var(--color-accent-border);
76
76
  cursor: not-allowed;
@@ -85,14 +85,14 @@ button.primary {
85
85
  button.secondary {
86
86
  background-color: transparent;
87
87
  border: 1px solid var(--color-accent);
88
- color: var(--color-accent);
88
+ color: var(--color-accent-active);
89
89
  }
90
90
  button.secondary:hover {
91
91
  color: var(--color-accent-hover);
92
92
  }
93
93
  button.subdued {
94
94
  background-color: transparent;
95
- color: var(--color-accent);
95
+ color: var(--color-accent-active);
96
96
  }
97
97
  button.subdued:hover {
98
98
  color: var(--color-accent-hover);
@@ -0,0 +1,89 @@
1
+ <script setup lang="ts">
2
+ import { computed, toRefs, useAttrs, useSlots } from "vue";
3
+
4
+ interface Props {
5
+ icon?: string;
6
+ disabled?: boolean;
7
+ active?: boolean;
8
+ }
9
+
10
+ const props = withDefaults(defineProps<Props>(), {
11
+ active: false,
12
+ });
13
+
14
+ const { disabled, active } = toRefs(props);
15
+
16
+ const attrs = useAttrs();
17
+ const slots = useSlots();
18
+
19
+ const isIconOnly = computed(() => {
20
+ const hasIcon = !!props.icon || !!slots.icon;
21
+ const hasDefault = !!slots.default;
22
+ return hasIcon && !hasDefault;
23
+ });
24
+
25
+ const emit = defineEmits<{
26
+ (e: "click", event: PointerEvent): void;
27
+ }>();
28
+
29
+ function click(event: PointerEvent) {
30
+ if (disabled.value) return;
31
+ emit("click", event);
32
+ }
33
+ </script>
34
+
35
+ <template>
36
+ <orio-control-element>
37
+ <button
38
+ v-bind="attrs"
39
+ :class="{ 'icon-only': isIconOnly, active }"
40
+ :disabled
41
+ :aria-current="active ? 'page' : undefined"
42
+ @click="click"
43
+ >
44
+ <slot name="icon">
45
+ <orio-icon v-if="icon" :name="icon" />
46
+ </slot>
47
+ <slot />
48
+ </button>
49
+ </orio-control-element>
50
+ </template>
51
+
52
+ <style scoped>
53
+ button {
54
+ background-color: transparent;
55
+ color: var(--color-text);
56
+ border: none;
57
+ border-radius: var(--border-radius-md);
58
+ padding: 8px 16px;
59
+ font-size: 1rem;
60
+ line-height: 1.5;
61
+ cursor: pointer;
62
+ display: inline-flex;
63
+ align-items: center;
64
+ gap: 0.5rem;
65
+ user-select: none;
66
+ transition: color 0.2s ease;
67
+ }
68
+ button.icon-only {
69
+ padding: 8px;
70
+ border-radius: 50%;
71
+ aspect-ratio: 1;
72
+ justify-content: center;
73
+ }
74
+ button:hover:not(:disabled) {
75
+ color: var(--color-accent);
76
+ }
77
+ button.active {
78
+ color: var(--color-accent);
79
+ font-weight: 600;
80
+ }
81
+ button:disabled {
82
+ opacity: 0.5;
83
+ cursor: not-allowed;
84
+ }
85
+ button:focus-visible {
86
+ outline: 2px solid var(--color-accent);
87
+ outline-offset: 2px;
88
+ }
89
+ </style>
@@ -27,8 +27,8 @@ import {
27
27
  onMounted,
28
28
  onBeforeUnmount,
29
29
  watch,
30
- } from 'vue';
31
- import { useElementBounding } from '@vueuse/core';
30
+ } from "vue";
31
+ import { useElementBounding } from "@vueuse/core";
32
32
 
33
33
  const props = defineProps({
34
34
  /**
@@ -41,7 +41,7 @@ const props = defineProps({
41
41
  */
42
42
  position: {
43
43
  type: String,
44
- default: 'bottom-left',
44
+ default: "bottom-left",
45
45
  },
46
46
  /**
47
47
  * Distance (in px) between the popover and the trigger element.
@@ -74,7 +74,7 @@ const popoverStyle = computed(() => {
74
74
  return;
75
75
  }
76
76
 
77
- const [main, sub = 'center'] = currentPosition.value.split('-');
77
+ const [main, sub = "center"] = currentPosition.value.split("-");
78
78
  const offset = props.offset;
79
79
 
80
80
  const tRect = triggerRect.value;
@@ -84,9 +84,9 @@ const popoverStyle = computed(() => {
84
84
  let leftValue = 0;
85
85
 
86
86
  // Calculate vertical position (top)
87
- if (main === 'top') {
87
+ if (main === "top") {
88
88
  topValue = tRect.top - offset - pRect.height;
89
- } else if (main === 'bottom') {
89
+ } else if (main === "bottom") {
90
90
  topValue = tRect.bottom + offset;
91
91
  } else {
92
92
  // For 'left' or 'right' main, center vertically
@@ -94,9 +94,9 @@ const popoverStyle = computed(() => {
94
94
  }
95
95
 
96
96
  // Calculate horizontal position (left)
97
- if (sub === 'left') {
97
+ if (sub === "left") {
98
98
  leftValue = tRect.right - pRect.width;
99
- } else if (sub === 'right') {
99
+ } else if (sub === "right") {
100
100
  leftValue = tRect.left;
101
101
  } else {
102
102
  // 'center' is default horizontally
@@ -104,9 +104,9 @@ const popoverStyle = computed(() => {
104
104
  }
105
105
 
106
106
  // If the main position is 'left' or 'right', override horizontal positioning
107
- if (main === 'left') {
107
+ if (main === "left") {
108
108
  leftValue = tRect.left - offset - pRect.width;
109
- } else if (main === 'right') {
109
+ } else if (main === "right") {
110
110
  leftValue = tRect.right + offset;
111
111
  }
112
112
 
@@ -119,13 +119,13 @@ const popoverStyle = computed(() => {
119
119
  const currentPosition = ref(props.position);
120
120
 
121
121
  const getFallbackPositions = (pos: string) => {
122
- const [main, sub = 'center'] = pos.split('-');
122
+ const [main, sub = "center"] = pos.split("-");
123
123
 
124
124
  const opposites: Record<string, string> = {
125
- top: 'bottom',
126
- bottom: 'top',
127
- left: 'right',
128
- right: 'left',
125
+ top: "bottom",
126
+ bottom: "top",
127
+ left: "right",
128
+ right: "left",
129
129
  };
130
130
 
131
131
  const allPositions = [
@@ -141,7 +141,7 @@ const getFallbackPositions = (pos: string) => {
141
141
  };
142
142
 
143
143
  function checkIfFits(position: string, tRect: any, pRect: any, offset: number) {
144
- const [main, sub = 'center'] = position.split('-');
144
+ const [main, sub = "center"] = position.split("-");
145
145
 
146
146
  const space = {
147
147
  top: tRect.top,
@@ -150,10 +150,10 @@ function checkIfFits(position: string, tRect: any, pRect: any, offset: number) {
150
150
  right: window.innerWidth - tRect.right,
151
151
  };
152
152
 
153
- if (main === 'top' && space.top < pRect.height + offset) return false;
154
- if (main === 'bottom' && space.bottom < pRect.height + offset) return false;
155
- if (main === 'left' && space.left < pRect.width + offset) return false;
156
- if (main === 'right' && space.right < pRect.width + offset) return false;
153
+ if (main === "top" && space.top < pRect.height + offset) return false;
154
+ if (main === "bottom" && space.bottom < pRect.height + offset) return false;
155
+ if (main === "left" && space.left < pRect.width + offset) return false;
156
+ if (main === "right" && space.right < pRect.width + offset) return false;
157
157
 
158
158
  return true;
159
159
  }
@@ -176,8 +176,8 @@ async function updateRects() {
176
176
 
177
177
  for (const pos of fallbacks) {
178
178
  // temporarily apply style to measure it
179
- popoverEl.style.visibility = 'hidden';
180
- popoverEl.style.display = 'block';
179
+ popoverEl.style.visibility = "hidden";
180
+ popoverEl.style.display = "block";
181
181
 
182
182
  const pRect = popoverEl.getBoundingClientRect();
183
183
  const fits = checkIfFits(pos, tRect, pRect, props.offset);
@@ -185,7 +185,7 @@ async function updateRects() {
185
185
  if (fits) {
186
186
  popoverRect.value = pRect;
187
187
  currentPosition.value = pos;
188
- popoverEl.style.visibility = '';
188
+ popoverEl.style.visibility = "";
189
189
  return;
190
190
  }
191
191
  }
@@ -235,15 +235,15 @@ function redrawPopover() {
235
235
  watch([popoverWidth, popoverHeight], redrawPopover);
236
236
 
237
237
  onMounted(() => {
238
- document.addEventListener('click', handleDocumentClick);
239
- window.addEventListener('scroll', redrawPopover, true);
240
- window.addEventListener('resize', redrawPopover, true);
238
+ document.addEventListener("click", handleDocumentClick);
239
+ window.addEventListener("scroll", redrawPopover, true);
240
+ window.addEventListener("resize", redrawPopover, true);
241
241
  });
242
242
 
243
243
  onBeforeUnmount(() => {
244
- document.removeEventListener('click', handleDocumentClick);
245
- window.removeEventListener('scroll', redrawPopover, true);
246
- window.removeEventListener('resize', redrawPopover, true);
244
+ document.removeEventListener("click", handleDocumentClick);
245
+ window.removeEventListener("scroll", redrawPopover, true);
246
+ window.removeEventListener("resize", redrawPopover, true);
247
247
  });
248
248
  </script>
249
249
  <style scoped>
@@ -251,6 +251,6 @@ onBeforeUnmount(() => {
251
251
  border: 0;
252
252
  background-color: transparent;
253
253
  position: fixed;
254
- z-index: 1;
254
+ z-index: 999999;
255
255
  }
256
256
  </style>
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts" generic="T extends object">
2
- import { computed, toRefs } from 'vue';
2
+ import { computed, toRefs } from "vue";
3
3
 
4
4
  export type SelectableOption<T extends object = object> = string | T;
5
5
 
@@ -12,8 +12,8 @@ export interface SelectProps<T extends object = object> {
12
12
  }
13
13
 
14
14
  const props = withDefaults(defineProps<SelectProps>(), {
15
- placeholder: 'Select an option',
16
- field: 'id',
15
+ placeholder: "Select an option",
16
+ field: "id",
17
17
  });
18
18
 
19
19
  const { field, optionName, placeholder } = toRefs(props);
@@ -31,11 +31,11 @@ const label = computed(() => optionName.value as Extract<keyof T, string>);
31
31
  const flatModalValue = computed(() => {
32
32
  if (!modelValue.value) return null;
33
33
  if (!props.multiple || !Array.isArray(modelValue.value))
34
- return typeof modelValue.value === 'string'
34
+ return typeof modelValue.value === "string"
35
35
  ? modelValue.value
36
36
  : (modelValue.value as T)[key.value];
37
37
  return modelValue.value.map((option) =>
38
- typeof option === 'string' ? option : (option as T)[key.value],
38
+ typeof option === "string" ? option : (option as T)[key.value],
39
39
  );
40
40
  });
41
41
 
@@ -43,7 +43,7 @@ function toggleOption(option: SelectableOption, toggle: () => void) {
43
43
  if (props.multiple) {
44
44
  if (Array.isArray(modelValue.value)) {
45
45
  const index = modelValue.value.findIndex((opt) =>
46
- typeof option === 'string'
46
+ typeof option === "string"
47
47
  ? option === opt
48
48
  : opt[key.value] === (option as T)[key.value],
49
49
  );
@@ -63,7 +63,7 @@ function toggleOption(option: SelectableOption, toggle: () => void) {
63
63
  }
64
64
 
65
65
  function isOptionSelected(option: SelectableOption): boolean {
66
- if (typeof option === 'string') return modelValue.value === option;
66
+ if (typeof option === "string") return modelValue.value === option;
67
67
  return !!(
68
68
  flatModalValue.value &&
69
69
  (flatModalValue.value === (option as T)[key.value] ||
@@ -75,13 +75,13 @@ function isOptionSelected(option: SelectableOption): boolean {
75
75
 
76
76
  function getOptionLabel(option: SelectableOption | undefined): string {
77
77
  if (!option) return placeholder.value;
78
- if (typeof option === 'string') return option;
78
+ if (typeof option === "string") return option;
79
79
  if (optionName.value) return String((option as T)[label.value]);
80
80
  return JSON.stringify(option);
81
81
  }
82
82
 
83
83
  function getOptionKey(option: SelectableOption): string | number {
84
- if (typeof option === 'string') return option;
84
+ if (typeof option === "string") return option;
85
85
  return String((option as T)[key.value]);
86
86
  }
87
87
 
@@ -211,8 +211,8 @@ const selectorAttrs = computed(() => ({ getOptionKey, getOptionLabel }));
211
211
  }
212
212
  .selector-content ul li.selected {
213
213
  background-color: var(--color-accent);
214
- color: var(--color-accent-soft);
215
- font-weight: 500;
214
+ color: var(--color-accent-soft-darker);
215
+ font-weight: 400;
216
216
  }
217
217
 
218
218
  .trigger-content {
@@ -1,4 +1,5 @@
1
1
  export { default as Button } from "./components/Button.vue.js";
2
+ export { default as NavButton } from "./components/NavButton.vue.js";
2
3
  export { default as Input } from "./components/Input.vue.js";
3
4
  export { default as Textarea } from "./components/Textarea.vue.js";
4
5
  export { default as CheckBox } from "./components/CheckBox.vue.js";
@@ -1,4 +1,5 @@
1
1
  export { default as Button } from "./components/Button.vue";
2
+ export { default as NavButton } from "./components/NavButton.vue";
2
3
  export { default as Input } from "./components/Input.vue";
3
4
  export { default as Textarea } from "./components/Textarea.vue";
4
5
  export { default as CheckBox } from "./components/CheckBox.vue";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orio-ui",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Modern Nuxt component library with theme support",
5
5
  "type": "module",
6
6
  "main": "./dist/module.mjs",