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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +237 -0
  3. package/dist/module.cjs +5 -0
  4. package/dist/module.d.mts +3 -0
  5. package/dist/module.d.ts +3 -0
  6. package/dist/module.json +12 -0
  7. package/dist/module.mjs +16 -0
  8. package/dist/runtime/assets/css/animation.css +1 -0
  9. package/dist/runtime/assets/css/colors.css +1 -0
  10. package/dist/runtime/assets/css/cool-gradient-hover.css +23 -0
  11. package/dist/runtime/assets/css/main.css +1 -0
  12. package/dist/runtime/assets/css/scroll.css +1 -0
  13. package/dist/runtime/components/Button.vue +102 -0
  14. package/dist/runtime/components/CheckBox.vue +93 -0
  15. package/dist/runtime/components/ControlElement.vue +39 -0
  16. package/dist/runtime/components/DashedContainer.vue +59 -0
  17. package/dist/runtime/components/DatePicker.vue +30 -0
  18. package/dist/runtime/components/DateRangePicker.vue +73 -0
  19. package/dist/runtime/components/EmptyState.vue +81 -0
  20. package/dist/runtime/components/Icon.vue +40 -0
  21. package/dist/runtime/components/Input.vue +48 -0
  22. package/dist/runtime/components/LoadingSpinner.vue +6 -0
  23. package/dist/runtime/components/Modal.vue +69 -0
  24. package/dist/runtime/components/Popover.vue +249 -0
  25. package/dist/runtime/components/Selector.vue +208 -0
  26. package/dist/runtime/components/Tag.vue +21 -0
  27. package/dist/runtime/components/Textarea.vue +53 -0
  28. package/dist/runtime/components/view/Dates.vue +59 -0
  29. package/dist/runtime/components/view/Separator.vue +26 -0
  30. package/dist/runtime/components/view/Text.vue +79 -0
  31. package/dist/runtime/composables/index.d.ts +4 -0
  32. package/dist/runtime/composables/index.js +4 -0
  33. package/dist/runtime/composables/useApi.d.ts +10 -0
  34. package/dist/runtime/composables/useApi.js +9 -0
  35. package/dist/runtime/composables/useFuzzySearch.d.ts +10 -0
  36. package/dist/runtime/composables/useFuzzySearch.js +22 -0
  37. package/dist/runtime/composables/useModal.d.ts +15 -0
  38. package/dist/runtime/composables/useModal.js +28 -0
  39. package/dist/runtime/composables/useTheme.d.ts +6 -0
  40. package/dist/runtime/composables/useTheme.js +23 -0
  41. package/dist/runtime/index.d.ts +20 -0
  42. package/dist/runtime/index.js +20 -0
  43. package/dist/runtime/utils/icon-registry.d.ts +2 -0
  44. package/dist/runtime/utils/icon-registry.js +26 -0
  45. package/dist/types.d.mts +7 -0
  46. package/dist/types.d.ts +7 -0
  47. package/nuxt.config.ts +38 -0
  48. package/package.json +99 -0
  49. package/src/module.ts +16 -0
  50. package/src/runtime/assets/css/animation.css +88 -0
  51. package/src/runtime/assets/css/colors.css +142 -0
  52. package/src/runtime/assets/css/cool-gradient-hover.scss +33 -0
  53. package/src/runtime/assets/css/main.css +11 -0
  54. package/src/runtime/assets/css/scroll.css +46 -0
  55. package/src/runtime/components/Button.vue +110 -0
  56. package/src/runtime/components/CheckBox.vue +103 -0
  57. package/src/runtime/components/ControlElement.vue +42 -0
  58. package/src/runtime/components/DashedContainer.vue +60 -0
  59. package/src/runtime/components/DatePicker.vue +84 -0
  60. package/src/runtime/components/DateRangePicker.vue +74 -0
  61. package/src/runtime/components/EmptyState.vue +87 -0
  62. package/src/runtime/components/Icon.vue +51 -0
  63. package/src/runtime/components/Input.vue +54 -0
  64. package/src/runtime/components/LoadingSpinner.vue +6 -0
  65. package/src/runtime/components/Modal.vue +111 -0
  66. package/src/runtime/components/Popover.vue +249 -0
  67. package/src/runtime/components/Selector.vue +224 -0
  68. package/src/runtime/components/Tag.vue +45 -0
  69. package/src/runtime/components/Textarea.vue +59 -0
  70. package/src/runtime/components/view/Dates.vue +61 -0
  71. package/src/runtime/components/view/Separator.vue +30 -0
  72. package/src/runtime/components/view/Text.vue +83 -0
  73. package/src/runtime/composables/index.ts +4 -0
  74. package/src/runtime/composables/useApi.ts +26 -0
  75. package/src/runtime/composables/useFuzzySearch.ts +51 -0
  76. package/src/runtime/composables/useModal.ts +47 -0
  77. package/src/runtime/composables/useTheme.ts +31 -0
  78. package/src/runtime/index.ts +25 -0
  79. package/src/runtime/utils/icon-registry.ts +41 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 oriondor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,237 @@
1
+ # Orio UI
2
+
3
+ A delightful, lightweight component library for Nuxt 3 applications. Built with TypeScript, fully tested, and designed for modern web development.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/orio-ui.svg)](https://www.npmjs.com/package/orio-ui)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ ✨ **18 Components** - Beautiful, accessible components ready to use
11
+ 🎨 **Themeable** - 5 built-in accent themes with light/dark mode support
12
+ 🚀 **Auto-imported** - Works seamlessly with Nuxt's auto-import system
13
+ 📦 **Tree-shakeable** - Only bundle what you use
14
+ 🎯 **TypeScript** - Fully typed for great developer experience
15
+ 🧪 **Tested** - 71+ unit tests for reliability
16
+ 📱 **Responsive** - Mobile-first design approach
17
+ ♿ **Accessible** - ARIA-compliant components
18
+
19
+ ## Quick Start
20
+
21
+ ### Installation
22
+
23
+ ```bash
24
+ npm install orio-ui
25
+ ```
26
+
27
+ ### Setup
28
+
29
+ Add Orio UI to your `nuxt.config.ts`:
30
+
31
+ ```typescript
32
+ export default defineNuxtConfig({
33
+ extends: ["orio-ui"],
34
+ });
35
+ ```
36
+
37
+ That's it! All components and composables are now auto-imported.
38
+
39
+ ### Usage
40
+
41
+ ```vue
42
+ <template>
43
+ <div>
44
+ <orio-button type="primary" @click="handleClick"> Click Me </orio-button>
45
+
46
+ <orio-input v-model="email" label="Email" placeholder="you@example.com" />
47
+
48
+ <orio-view-text type="title" size="large">
49
+ Welcome to Orio UI
50
+ </orio-view-text>
51
+ </div>
52
+ </template>
53
+
54
+ <script setup>
55
+ const email = ref("");
56
+
57
+ // Composables are auto-imported too!
58
+ const { theme, setTheme } = useTheme();
59
+
60
+ function handleClick() {
61
+ setTheme("ocean");
62
+ }
63
+ </script>
64
+ ```
65
+
66
+ ## What's Included
67
+
68
+ ### Components (18)
69
+
70
+ #### Form Controls
71
+
72
+ - **Input** - Text input with label support
73
+ - **Textarea** - Multi-line text input
74
+ - **CheckBox** - Custom checkbox with icon states
75
+ - **DatePicker** - Date selection with month/year options
76
+ - **DateRangePicker** - Start and end date selection
77
+ - **Selector** - Generic dropdown selector (single/multi-select)
78
+ - **Tag** - Styled tag/badge component
79
+
80
+ #### Interactive
81
+
82
+ - **Button** - Primary, secondary, subdued variants with loading/icon support
83
+ - **Modal** - Animated modal with origin morphing
84
+ - **Popover** - Positioned popover with smart placement
85
+
86
+ #### Display
87
+
88
+ - **Icon** - SVG icon system with 12 bundled icons
89
+ - **LoadingSpinner** - Animated loading indicator
90
+ - **EmptyState** - Empty state placeholder
91
+ - **DashedContainer** - Dashed border container with icon
92
+ - **ControlElement** - Form control wrapper
93
+
94
+ #### View
95
+
96
+ - **Text** - Typography component with variants
97
+ - **Dates** - Date range display formatter
98
+ - **Separator** - Visual divider
99
+
100
+ ### Composables (4)
101
+
102
+ - **useTheme** - Theme and color mode management
103
+ - **useModal** - Modal state with animation origin tracking
104
+ - **useFuzzySearch** - Fuzzy search powered by Fuse.js
105
+ - **useApi** - Type-safe API request wrapper
106
+
107
+ ### Theming
108
+
109
+ Built-in themes:
110
+
111
+ - **Navy** (default) - Professional blue
112
+ - **Ocean** - Fresh cyan
113
+ - **Sunset** - Warm orange
114
+ - **Forest** - Natural green
115
+ - **Purple** - Creative purple
116
+
117
+ All themes support light and dark modes. Fully customizable via CSS variables.
118
+
119
+ ```vue
120
+ <script setup>
121
+ const { setTheme, setMode } = useTheme();
122
+
123
+ setTheme("ocean");
124
+ setMode("dark");
125
+ </script>
126
+ ```
127
+
128
+ ## Documentation
129
+
130
+ Full documentation:
131
+
132
+ - [Getting Started Guide](./docs/getting-started.md)
133
+ - [Theming Guide](./docs/theming.md)
134
+ - [Component Documentation](./docs/components/)
135
+ - [Composable Documentation](./docs/composables/)
136
+ - [Utils Documentation](./docs/utils/)
137
+
138
+ ## Development
139
+
140
+ ### Setup Development Environment
141
+
142
+ ```bash
143
+ # Clone the repository
144
+ git clone https://github.com/oriondor/orio-ui.git
145
+ cd orio-ui
146
+
147
+ # Install dependencies
148
+ npm install
149
+
150
+ # Run documentation site
151
+ npm run dev
152
+
153
+ # Run tests
154
+ npm test
155
+
156
+ # Build library
157
+ npm run build
158
+
159
+ # Run documentation
160
+ npm run docs:dev
161
+ ```
162
+
163
+ ### Project Structure
164
+
165
+ ```
166
+ orio-ui/
167
+ ├── src/runtime/
168
+ │ ├── components/ # 18 Vue components
169
+ │ ├── composables/ # 4 composables
170
+ │ ├── assets/css/ # Theme CSS files
171
+ │ └── utils/ # Icon registry
172
+ ├── tests/ # Vitest unit tests
173
+ ├── docs/ # VitePress documentation
174
+ └── nuxt.config.ts # Nuxt Layer configuration
175
+ ```
176
+
177
+ ### Running Tests
178
+
179
+ ```bash
180
+ # Run all tests
181
+ npm test
182
+
183
+ # Run tests once
184
+ npm run test:unit
185
+
186
+ # Watch mode
187
+ npm run test:watch
188
+ ```
189
+
190
+ ## TypeScript Support
191
+
192
+ Orio UI is written in TypeScript and provides full type definitions:
193
+
194
+ ```typescript
195
+ import type { TextTypes, TagStyle } from "orio-ui/composables";
196
+ import type { OriginRect, ModalProps } from "orio-ui/composables";
197
+ import type { IconName } from "orio-ui/composables";
198
+ ```
199
+
200
+ ## Browser Support
201
+
202
+ - Chrome (latest)
203
+ - Firefox (latest)
204
+ - Safari (latest)
205
+ - Edge (latest)
206
+
207
+ ## Contributing
208
+
209
+ Contributions are welcome! Please read our contributing guidelines before submitting PRs.
210
+
211
+ 1. Fork the repository
212
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
213
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
214
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
215
+ 5. Open a Pull Request
216
+
217
+ ## License
218
+
219
+ MIT © [oriondor](https://github.com/oriondor)
220
+
221
+ ## Changelog
222
+
223
+ See [CHANGELOG.md](./CHANGELOG.md) for version history.
224
+
225
+ ## Credits
226
+
227
+ Built with:
228
+
229
+ - [Nuxt 3](https://nuxt.com/) - Vue framework
230
+ - [VueUse](https://vueuse.org/) - Vue composables collection
231
+ - [Fuse.js](https://fusejs.io/) - Fuzzy search library
232
+ - [VitePress](https://vitepress.dev/) - Documentation
233
+ - [Vitest](https://vitest.dev/) - Testing framework
234
+
235
+ ---
236
+
237
+ **Made with ❤️ for the Nuxt community**
@@ -0,0 +1,5 @@
1
+ module.exports = function(...args) {
2
+ return import('./module.mjs').then(m => m.default.call(this, ...args))
3
+ }
4
+ const _meta = module.exports.meta = require('./module.json')
5
+ module.exports.getMeta = () => Promise.resolve(_meta)
@@ -0,0 +1,3 @@
1
+ declare const _default: any;
2
+
3
+ export { _default as default };
@@ -0,0 +1,3 @@
1
+ declare const _default: any;
2
+
3
+ export { _default as default };
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "orio-ui",
3
+ "configKey": "orioUi",
4
+ "compatibility": {
5
+ "nuxt": "^3.0.0 || ^4.0.0"
6
+ },
7
+ "version": "0.1.0",
8
+ "builder": {
9
+ "@nuxt/module-builder": "0.8.4",
10
+ "unbuild": "2.0.0"
11
+ }
12
+ }
@@ -0,0 +1,16 @@
1
+ import { defineNuxtModule } from '@nuxt/kit';
2
+
3
+ const module = defineNuxtModule({
4
+ meta: {
5
+ name: "orio-ui",
6
+ configKey: "orioUi",
7
+ compatibility: {
8
+ nuxt: "^3.0.0 || ^4.0.0"
9
+ }
10
+ },
11
+ defaults: {},
12
+ setup() {
13
+ }
14
+ });
15
+
16
+ export { module as default };
@@ -0,0 +1 @@
1
+ :root{--motion-duration-short:0.05s;--motion-duration-medium:0.15s;--motion-distance-small:0.15rem;--motion-distance-medium:2rem;--motion-distance-large:1rem;--motion-ease-default:ease-in;--motion-ease-smooth:cubic-bezier(0.22,1,0.36,1)}.animate-fade-enter-active,.animate-fade-leave-active{transition:opacity var(--motion-duration-short) var(--motion-ease-default),transform var(--motion-duration-short) var(--motion-ease-default)}.animate-fade-enter-from,.animate-fade-leave-to{opacity:0;transform:translateY(calc(var(--motion-distance-small)*-1))}.animate-fade-slide-appear-active,.animate-fade-slide-enter-active,.animate-fade-slide-leave-active{transition:opacity var(--motion-duration-medium) var(--motion-ease-smooth),transform var(--motion-duration-medium) var(--motion-ease-smooth);transition-delay:calc(var(--index, 0)*.08s)}.animate-fade-slide-appear-from,.animate-fade-slide-enter-from{opacity:0;transform:translateY(var(--motion-distance-medium))}.animate-fade-slide-leave-to{opacity:0;transform:translateY(calc(var(--motion-distance-large)*-1))}.animate-fade-slide-move{transition:transform var(--motion-duration-medium) ease}.animate-fade-reverse-enter-active,.animate-fade-reverse-leave-active{transition:opacity var(--motion-duration-short) var(--motion-ease-default),transform var(--motion-duration-short) var(--motion-ease-default)}.animate-fade-reverse-enter-from,.animate-fade-reverse-leave-to{opacity:0;transform:translateY(var(--motion-distance-small))}.animate-fade-slide-reverse-appear-active,.animate-fade-slide-reverse-enter-active,.animate-fade-slide-reverse-leave-active{transition:opacity var(--motion-duration-medium) var(--motion-ease-smooth),transform var(--motion-duration-medium) var(--motion-ease-smooth);transition-delay:calc(var(--index, 0)*.08s)}.animate-fade-slide-reverse-appear-from,.animate-fade-slide-reverse-enter-from{opacity:0;transform:translateY(calc(var(--motion-distance-medium)*-1))}.animate-fade-slide-reverse-leave-to{opacity:0;transform:translateY(var(--motion-distance-large))}.animate-fade-slide-reverse-move{transition:transform var(--motion-duration-medium) ease}
@@ -0,0 +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:#eef2ff;--color-accent-border:#c7d2fe;--color-accent-hover:color-mix(in srgb,var(--color-accent) 85%,#000 15%);--color-accent-active:color-mix(in srgb,var(--color-accent) 75%,#000 25%)}: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:#a0a7b5;--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=navy]{--color-accent:#333c4d;--color-accent-soft:#f0f5ff;--color-accent-border:#bdcdef}:root[data-theme=teal]{--color-accent:#147b71;--color-accent-soft:#e7f8f6;--color-accent-border:#a6d9d4}:root[data-theme=forest]{--color-accent:#235c42;--color-accent-soft:#e9f7f0;--color-accent-border:#a2cdb9}:root[data-theme=wine]{--color-accent:#862737;--color-accent-soft:#fbeff1;--color-accent-border:#dbb3ba}:root[data-theme=royal]{--color-accent:#293da3;--color-accent-soft:#eef0fb;--color-accent-border:#b3badb}: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%)}
@@ -0,0 +1,23 @@
1
+ .gradient-hover {
2
+ --gh-color: #fff;
3
+ --gh-angle: 120deg;
4
+ --gh-peek-x: 95%;
5
+ --gh-mix-base: black;
6
+ background-image: linear-gradient(var(--gh-angle), color-mix(in srgb, var(--gh-color) 90%, var(--gh-mix-base)) 0%, var(--gh-color) 40%, color-mix(in srgb, var(--gh-color) 90%, var(--gh-mix-base)) 80%);
7
+ background-repeat: no-repeat;
8
+ background-size: 0% 0%;
9
+ background-position: 0% 0%;
10
+ transition: background-position 0.8s ease, border-color 0.2s ease;
11
+ will-change: background-size, background-position;
12
+ }
13
+
14
+ .gradient-hover:hover {
15
+ background-size: 200% 100%;
16
+ background-position: var(--gh-peek-x) 0;
17
+ }
18
+
19
+ /* Dark mode: now theme-aware */
20
+ :root[data-mode=dark] .gradient-hover {
21
+ --gh-color: var(--color-accent); /* switch from static white to theme accent */
22
+ --gh-mix-base: var(--color-bg); /* blend into dark background */
23
+ }
@@ -0,0 +1 @@
1
+ @import "./colors.css";@import "./animation.css";@import "./scroll.css";*,:after,:before{box-sizing:border-box}
@@ -0,0 +1 @@
1
+ ::-webkit-scrollbar{height:12px;width:12px}::-webkit-scrollbar-track{background:var(--color-surface);border-left:1px solid var(--color-border)}::-webkit-scrollbar-thumb{background-color:var(--color-accent-border);border:3px solid var(--color-surface);border-radius:8px;-webkit-transition:background-color .2s ease;transition:background-color .2s ease}::-webkit-scrollbar-thumb:hover{background-color:var(--color-accent)}::-webkit-scrollbar-corner{background:var(--color-surface)}*{scrollbar-color:var(--color-accent-border) var(--color-surface);scrollbar-width:thin}:root[data-mode=dark] ::-webkit-scrollbar-track{background:var(--color-bg);border-left:1px solid var(--color-border)}:root[data-mode=dark] ::-webkit-scrollbar-thumb{background-color:var(--color-accent-border);border:3px solid var(--color-bg)}:root[data-mode=dark] *{scrollbar-color:var(--color-accent-border) var(--color-bg)}
@@ -0,0 +1,102 @@
1
+ <script setup lang="ts">
2
+ import { computed, toRefs, useAttrs, useSlots } from 'vue';
3
+
4
+ interface Props {
5
+ type?: 'primary' | 'secondary' | 'subdued';
6
+ icon?: string;
7
+ loading?: boolean;
8
+ disabled?: boolean;
9
+ }
10
+
11
+ const props = withDefaults(defineProps<Props>(), {
12
+ type: 'primary',
13
+ });
14
+
15
+ const { loading, disabled } = toRefs(props);
16
+
17
+ const attrs = useAttrs();
18
+ const slots = useSlots();
19
+
20
+ const isIconOnly = computed(() => {
21
+ const hasIcon = !!props.icon || !!slots.icon;
22
+ const hasDefault = !!slots.default;
23
+ return hasIcon && !hasDefault;
24
+ });
25
+
26
+ const emit = defineEmits<{
27
+ (e: 'click', event: PointerEvent): void;
28
+ }>();
29
+
30
+ function click(event: PointerEvent) {
31
+ if (loading.value || disabled.value) return;
32
+ emit('click', event);
33
+ }
34
+ </script>
35
+
36
+ <template>
37
+ <orio-control-element>
38
+ <button
39
+ v-bind="attrs"
40
+ :class="[type, 'gradient-hover', { 'icon-only': isIconOnly }]"
41
+ @click="click"
42
+ >
43
+ <orio-loading-spinner v-if="loading" />
44
+ <slot v-else 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: var(--color-accent);
55
+ color: var(--color-accent-ink);
56
+ border: 1px solid transparent;
57
+ border-radius: 4px;
58
+ padding: 8px 16px;
59
+ cursor: pointer;
60
+ display: inline-flex;
61
+ align-items: center;
62
+ gap: 0.5rem;
63
+ user-select: none;
64
+ }
65
+ button.icon-only {
66
+ padding: 0;
67
+ border-radius: 50%;
68
+ }
69
+ button:disabled {
70
+ background-color: var(--color-accent-soft);
71
+ color: var(--color-muted);
72
+ border-color: var(--color-accent-border);
73
+ cursor: not-allowed;
74
+ }
75
+ button:active {
76
+ border: 1px solid var(--color-accent-border);
77
+ }
78
+ button.primary {
79
+ --gh-color: var(--color-accent);
80
+ }
81
+ button.secondary {
82
+ background-color: transparent;
83
+ border: 1px solid var(--color-accent);
84
+ color: var(--color-accent);
85
+ }
86
+ button.secondary:hover {
87
+ color: var(--color-accent-hover);
88
+ }
89
+ button.subdued {
90
+ background-color: transparent;
91
+ color: var(--color-accent);
92
+ }
93
+ button.subdued:hover {
94
+ color: var(--color-accent-hover);
95
+ }
96
+ button:active {
97
+ border-color: var(--color-accent-border) !important;
98
+ }
99
+ button:hover {
100
+ color: var(--color-accent-ink);
101
+ }
102
+ </style>
@@ -0,0 +1,93 @@
1
+ <script setup lang="ts">
2
+ const modelValue = defineModel<boolean>({ required: false });
3
+
4
+ defineProps<{
5
+ checkedIcon?: string; // optional: pass icon name for checked state
6
+ uncheckedIcon?: string; // optional: pass icon name for unchecked state
7
+ }>();
8
+ </script>
9
+
10
+ <template>
11
+ <orio-control-element class="checkbox">
12
+ <label class="checkbox-label">
13
+ <input
14
+ v-model="modelValue"
15
+ type="checkbox"
16
+ class="checkbox-input"
17
+ tabindex="-1"
18
+ />
19
+ <span
20
+ class="checkbox-box"
21
+ :class="{
22
+ defaultChecked: !checkedIcon,
23
+ defaultUnchecked: !uncheckedIcon,
24
+ }"
25
+ >
26
+ <slot name="icon" :checked="modelValue">
27
+ <orio-icon v-if="modelValue && checkedIcon" :name="checkedIcon" />
28
+ <orio-icon
29
+ v-else-if="!modelValue && uncheckedIcon"
30
+ :name="uncheckedIcon"
31
+ />
32
+ </slot>
33
+ </span>
34
+ <slot />
35
+ </label>
36
+ </orio-control-element>
37
+ </template>
38
+
39
+ <style scoped>
40
+ .checkbox {
41
+ --box-size: 1rem;
42
+ }
43
+ .checkbox-label {
44
+ position: relative;
45
+ user-select: none;
46
+ display: inline-flex;
47
+ align-items: center;
48
+ gap: 0.4rem;
49
+ cursor: pointer;
50
+ font-size: 0.9rem;
51
+ color: var(--color-text);
52
+ }
53
+ .checkbox-input {
54
+ position: absolute;
55
+ inset: 0;
56
+ width: var(--box-size);
57
+ height: 1rem;
58
+ margin: 0;
59
+ opacity: 0;
60
+ }
61
+ .checkbox-box {
62
+ width: var(--box-size);
63
+ height: var(--box-size);
64
+ border: 2px solid var(--color-border);
65
+ border-radius: 4px;
66
+ background-color: var(--color-bg);
67
+ display: inline-flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ transition: background-color 0.2s ease, border-color 0.2s ease;
71
+ }
72
+ .checkbox .checkbox-input:checked + .checkbox-box {
73
+ background-color: var(--color-accent);
74
+ border-color: var(--color-accent);
75
+ }
76
+ .checkbox .checkbox-input:checked + .checkbox-box.defaultChecked::after {
77
+ content: "";
78
+ width: 0.3rem;
79
+ height: 0.6rem;
80
+ position: relative;
81
+ bottom: 0.1rem;
82
+ border: solid var(--color-accent-ink);
83
+ border-width: 0 2px 2px 0;
84
+ transform: rotate(45deg);
85
+ }
86
+ .checkbox-label:hover .checkbox-box {
87
+ border-color: var(--color-accent);
88
+ }
89
+ .checkbox .checkbox-input:focus-visible + .checkbox-box {
90
+ outline: 2px solid var(--color-accent);
91
+ outline-offset: 2px;
92
+ }
93
+ </style>
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ export interface ControlProps {
3
+ /**
4
+ * Minimal will reset margin and remove border and box shadow from every element inside the slot
5
+ */
6
+ appearance?: "normal" | "minimal";
7
+ }
8
+
9
+ withDefaults(defineProps<ControlProps>(), {
10
+ appearance: "normal",
11
+ });
12
+ </script>
13
+
14
+ <template>
15
+ <div class="control" :class="[appearance]">
16
+ <label v-if="$attrs.label" class="control-label">{{ $attrs.label }}</label>
17
+ <div class="slot-wrapper" v-bind="$attrs">
18
+ <slot />
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <style scoped>
24
+ .control {
25
+ margin: 0.5rem;
26
+ }
27
+ .control .control-label {
28
+ user-select: none;
29
+ }
30
+ .control.minimal {
31
+ margin: 0;
32
+ }
33
+ .control.minimal .slot-wrapper :first-child {
34
+ border: 0;
35
+ }
36
+ .control.minimal .slot-wrapper :first-child:focus {
37
+ box-shadow: none;
38
+ }
39
+ </style>
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+
4
+ interface Props {
5
+ icon?: string;
6
+ text?: string;
7
+ size?: 'small' | 'medium' | 'large';
8
+ }
9
+ const props = withDefaults(defineProps<Props>(), {
10
+ size: 'medium',
11
+ });
12
+ defineEmits(['click']);
13
+
14
+ const iconSize = computed(() => {
15
+ switch (props.size) {
16
+ case 'small':
17
+ return '2rem';
18
+ case 'large':
19
+ return '5rem';
20
+ default:
21
+ return '3rem';
22
+ }
23
+ });
24
+ </script>
25
+ <template>
26
+ <div class="dashed-container gradient-hover" @click="$emit('click')">
27
+ <orio-icon
28
+ v-if="icon"
29
+ :name="icon"
30
+ class="icon-class"
31
+ :size="iconSize"
32
+ />
33
+ <span v-if="text" class="text-class" :size>{{ text }}</span>
34
+ </div>
35
+ </template>
36
+
37
+ <style scoped>
38
+ .dashed-container {
39
+ cursor: pointer;
40
+ border: 3px dashed var(--color-border);
41
+ border-radius: 4px;
42
+ padding: 0.5rem;
43
+ display: flex;
44
+ flex-direction: column;
45
+ justify-content: center;
46
+ align-items: center;
47
+ }
48
+ .dashed-container:hover .text-class {
49
+ color: var(--color-accent-hover);
50
+ }
51
+
52
+ .icon-class {
53
+ color: var(--color-muted);
54
+ }
55
+
56
+ .text-class {
57
+ color: var(--color-accent);
58
+ }
59
+ </style>
@@ -0,0 +1,30 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import { nanoid } from 'nanoid';
4
+
5
+ interface Props {
6
+ month?: boolean;
7
+ }
8
+ defineProps<Props>();
9
+
10
+ const date = defineModel<string | null | undefined>('date', {
11
+ required: true,
12
+ });
13
+
14
+ const randomName = computed(() => `date-${nanoid(8)}`);
15
+ </script>
16
+
17
+ <template>
18
+ <orio-control-element class="date-picker" v-bind="$attrs">
19
+ <input
20
+ v-model="date"
21
+ :type="month ? 'month' : 'date'"
22
+ class="date-input"
23
+ :name="randomName"
24
+ />
25
+ </orio-control-element>
26
+ </template>
27
+
28
+ <style scoped>
29
+ .date-picker *{cursor:pointer;width:100%}.date-picker-label{color:var(--color-text);display:inline-flex;flex-direction:column;font-size:.9rem;gap:.25rem}.date-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--color-bg);border:1px solid var(--color-border);border-radius:4px;box-sizing:border-box;color:var(--color-text);font-size:.95rem;padding:.4rem .6rem;transition:border-color .2s ease,box-shadow .2s ease}.date-input:focus,.date-input:hover{border-color:var(--color-accent)}.date-input:focus{box-shadow:0 0 0 2px var(--color-accent-soft);outline:none}.date-input:disabled{background-color:var(--color-surface);color:var(--color-muted);cursor:not-allowed}.date-input::-webkit-calendar-picker-indicator{cursor:pointer;filter:invert(36%) sepia(65%) saturate(325%) hue-rotate(180deg);opacity:.7;-webkit-transition:opacity .2s ease;transition:opacity .2s ease}.date-input::-webkit-calendar-picker-indicator:hover{opacity:1}
30
+ </style>