windly-ui 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -33
  3. package/package.json +49 -11
  4. package/ui-library/dist/module.d.mts +3 -0
  5. package/ui-library/dist/module.json +8 -0
  6. package/ui-library/dist/module.mjs +22 -0
  7. package/ui-library/dist/types.d.mts +7 -0
  8. package/app/app.vue +0 -10
  9. package/app/assets/css/tailwind.css +0 -3
  10. package/app/components/doc/component.vue +0 -34
  11. package/app/components/doc/megaSection.vue +0 -56
  12. package/app/components/doc/section.vue +0 -56
  13. package/app/pages/_docs.vue +0 -2420
  14. package/app/pages/index.vue +0 -137
  15. package/nuxt.config.ts +0 -19
  16. package/public/doc/avatar.avif +0 -0
  17. package/public/favicon.ico +0 -0
  18. package/public/robots.txt +0 -2
  19. package/tailwind.config.ts +0 -28
  20. package/tsconfig.json +0 -18
  21. package/ui-library/components/Accordion.vue +0 -118
  22. package/ui-library/components/Avatar.vue +0 -121
  23. package/ui-library/components/Badge.vue +0 -116
  24. package/ui-library/components/Breadcrumbs.vue +0 -138
  25. package/ui-library/components/Button.vue +0 -154
  26. package/ui-library/components/Card.vue +0 -84
  27. package/ui-library/components/Checkbox.vue +0 -148
  28. package/ui-library/components/CodeBlock.vue +0 -99
  29. package/ui-library/components/ColorPicker.vue +0 -302
  30. package/ui-library/components/Date.vue +0 -240
  31. package/ui-library/components/Dialog.vue +0 -187
  32. package/ui-library/components/Divider.vue +0 -78
  33. package/ui-library/components/Drawer.vue +0 -67
  34. package/ui-library/components/Dropdown.vue +0 -248
  35. package/ui-library/components/FileUploader.vue +0 -330
  36. package/ui-library/components/Icon.vue +0 -82
  37. package/ui-library/components/Image.vue +0 -78
  38. package/ui-library/components/Input.vue +0 -531
  39. package/ui-library/components/Radio.vue +0 -356
  40. package/ui-library/components/ScrollArea.vue +0 -43
  41. package/ui-library/components/Select.vue +0 -309
  42. package/ui-library/components/Stepper.vue +0 -361
  43. package/ui-library/components/TabPanel.vue +0 -25
  44. package/ui-library/components/Table.vue +0 -152
  45. package/ui-library/components/Tabs.vue +0 -71
  46. package/ui-library/components/Textarea.vue +0 -233
  47. package/ui-library/components/Toggle.vue +0 -319
  48. package/ui-library/components/Tooltip.vue +0 -200
  49. package/ui-library/components/plugin.ts +0 -8
  50. package/ui-library/components/uiProps.ts +0 -11
  51. package/ui-library/components/useUiClasses.ts +0 -159
  52. package/ui-library/module.ts +0 -20
@@ -1,137 +0,0 @@
1
- <template>
2
- <DocMegaSection
3
- title="Mini WYSIWYG Editor"
4
- sub-title="Demonstrates advanced Vue reactivity, history tracking, and DOM syncing."
5
- class="mx-4"
6
- >
7
- <DocComponent
8
- title="Reactive Editor with Undo/Redo"
9
- caption="A minimal rich-text editor built using contenteditable and Vue's Composition API."
10
- >
11
- <div class="grid md:grid-cols-2 gap-6">
12
- <div class="space-y-3">
13
- <div class="flex gap-2">
14
- <UIButton label="Undo" @click="undo" :disable="historyIndex <= 0" />
15
- <UIButton label="Redo" @click="redo" :disable="historyIndex >= history.length - 1" />
16
- <UIButton label="Clear" @click="clearEditor" color="red-400" />
17
- <UIButton label="Copy HTML" @click="copyHTML" color="green-500" />
18
- </div>
19
-
20
- <div
21
- ref="editorRef"
22
- contenteditable
23
- class="border rounded-md p-4 min-h-[200px] bg-white focus:outline-none"
24
- @input="handleInput"
25
- ></div>
26
- </div>
27
-
28
- <div>
29
- <h4 class="font-semibold mb-2">Live HTML Output</h4>
30
- <pre class="bg-gray-900 text-green-400 p-4 rounded-md text-sm overflow-auto">
31
- {{ html }}
32
- </pre>
33
- </div>
34
- </div>
35
- </DocComponent>
36
- </DocMegaSection>
37
- </template>
38
- <script setup>
39
- import { ref, computed, onMounted, onUnmounted } from 'vue'
40
-
41
- const editorRef = ref(null)
42
- const html = ref("")
43
-
44
- const history = ref([""])
45
- const historyIndex = ref(0)
46
-
47
- const MAX_HISTORY = 30
48
-
49
- let isProgrammaticUpdate = false
50
-
51
- let debounceTimer = null
52
- const debounce = (fn, delay = 300) => {
53
- clearTimeout(debounceTimer)
54
- debounceTimer = setTimeout(fn, delay)
55
- }
56
-
57
- const handleInput = () => {
58
- if (isProgrammaticUpdate) return
59
-
60
- debounce(() => {
61
- const value = editorRef.value.innerHTML
62
-
63
- if (value === history.value[historyIndex.value]) return
64
-
65
- history.value = history.value.slice(0, historyIndex.value + 1)
66
-
67
- history.value.push(value)
68
-
69
- if (history.value.length > MAX_HISTORY) {
70
- history.value.shift()
71
- } else {
72
- historyIndex.value++
73
- }
74
-
75
- historyIndex.value = history.value.length - 1
76
-
77
- html.value = value
78
- })
79
- }
80
-
81
- const updateEditorFromHistory = () => {
82
- const value = history.value[historyIndex.value]
83
-
84
- isProgrammaticUpdate = true
85
-
86
- html.value = value
87
- editorRef.value.innerHTML = value
88
-
89
- setTimeout(() => {
90
- isProgrammaticUpdate = false
91
- }, 0)
92
- }
93
-
94
- const undo = () => {
95
- if (historyIndex.value > 0) {
96
- historyIndex.value--
97
- updateEditorFromHistory()
98
- }
99
- }
100
-
101
- const redo = () => {
102
- if (historyIndex.value < history.value.length - 1) {
103
- historyIndex.value++
104
- updateEditorFromHistory()
105
- }
106
- }
107
-
108
- const clearEditor = () => {
109
- history.value = [""]
110
- historyIndex.value = 0
111
- updateEditorFromHistory()
112
- }
113
-
114
- const copyHTML = async () => {
115
- await navigator.clipboard.writeText(html.value)
116
- }
117
-
118
- const handleKeydown = (e) => {
119
- if (e.ctrlKey && e.key.toLowerCase() === "z") {
120
- e.preventDefault()
121
- undo()
122
- }
123
-
124
- if (e.ctrlKey && e.key.toLowerCase() === "y") {
125
- e.preventDefault()
126
- redo()
127
- }
128
- }
129
-
130
- onMounted(() => {
131
- window.addEventListener("keydown", handleKeydown)
132
- })
133
-
134
- onUnmounted(() => {
135
- window.removeEventListener("keydown", handleKeydown)
136
- })
137
- </script>
package/nuxt.config.ts DELETED
@@ -1,19 +0,0 @@
1
- // https://nuxt.com/docs/api/configuration/nuxt-config
2
- import UiLibraryModule from './ui-library/module'
3
-
4
- export default defineNuxtConfig({
5
- modules: ['@nuxtjs/tailwindcss', UiLibraryModule],
6
- css: ['~/assets/css/tailwind.css'],
7
- compatibilityDate: '2025-07-15',
8
- devtools: { enabled: true },
9
- app: {
10
- head: {
11
- link: [
12
- {
13
- rel: 'stylesheet',
14
- href: 'https://fonts.googleapis.com/icon?family=Material+Icons'
15
- }
16
- ]
17
- }
18
- }
19
- })
Binary file
Binary file
package/public/robots.txt DELETED
@@ -1,2 +0,0 @@
1
- User-Agent: *
2
- Disallow:
@@ -1,28 +0,0 @@
1
- // tailwind.config.js
2
- export default {
3
- content: [
4
- './app/**/*.{vue,js,ts}',
5
- './ui-library/components/**/*.{vue,js,ts}'
6
- ],
7
- safelist: [
8
- 'bg-white',
9
- 'text-white',
10
- 'border-white',
11
- 'ring-white',
12
- 'hover:bg-white',
13
- 'hover:text-white',
14
- 'bg-black',
15
- 'text-black',
16
- 'border-black',
17
- 'ring-black',
18
- 'hover:bg-black',
19
- 'hover:text-black',
20
- {
21
- pattern:
22
- /(bg|text|border|ring|to|from)-(white|black|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(50|100|200|300|400|500|600|700|800|900|950)?/,
23
- variants: ['hover', 'focus', 'active']
24
- }
25
- ],
26
- theme: { extend: {} },
27
- plugins: []
28
- }
package/tsconfig.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- // https://nuxt.com/docs/guide/concepts/typescript
3
- "files": [],
4
- "references": [
5
- {
6
- "path": "./.nuxt/tsconfig.app.json"
7
- },
8
- {
9
- "path": "./.nuxt/tsconfig.server.json"
10
- },
11
- {
12
- "path": "./.nuxt/tsconfig.shared.json"
13
- },
14
- {
15
- "path": "./.nuxt/tsconfig.node.json"
16
- }
17
- ]
18
- }
@@ -1,118 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, computed, watch } from 'vue'
3
-
4
- type TriggerType = 'click' | 'hover'
5
- type ToggleSide = 'left' | 'right'
6
-
7
- const props = withDefaults(
8
- defineProps<{
9
- title?: string
10
- caption?: string
11
- modelValue?: boolean
12
- defaultOpen?: boolean
13
- trigger?: TriggerType
14
- disabled?: boolean
15
- dense?: boolean
16
- toggleSide?: ToggleSide
17
- margin?: boolean
18
- }>(),
19
- {
20
- defaultOpen: false,
21
- trigger: 'click',
22
- disabled: false,
23
- dense: false,
24
- toggleSide: 'right',
25
- margin: true
26
- }
27
- )
28
-
29
- const emit = defineEmits < {
30
- (e: 'update:modelValue', value: boolean): void
31
- }> ()
32
-
33
- const isControlled = computed(() => props.modelValue !== undefined)
34
- const isOpen = ref < boolean > (props.defaultOpen ?? false)
35
-
36
- watch(
37
- () => props.modelValue,
38
- val => {
39
- if (isControlled.value && typeof val === 'boolean') {
40
- isOpen.value = val
41
- }
42
- },
43
- { immediate: true }
44
- )
45
-
46
- const setOpen = (val: boolean) => {
47
- if (props.disabled) return
48
- isOpen.value = val
49
- emit('update:modelValue', val)
50
- }
51
-
52
- const toggle = () => setOpen(!isOpen.value)
53
-
54
- const headerEvents = computed<Record<string, (() => void) | undefined>>(() => {
55
- if (props.trigger === 'hover') {
56
- return {
57
- mouseenter: () => setOpen(true),
58
- mouseleave: () => setOpen(false),
59
- click: undefined
60
- }
61
- }
62
-
63
- return {
64
- mouseenter: undefined,
65
- mouseleave: undefined,
66
- click: toggle
67
- }
68
- })
69
-
70
- </script>
71
-
72
- <template>
73
- <div class="flex flex-col rounded-md border transition-opacity" :class="[
74
- dense ? 'text-sm' : '',
75
- disabled ? 'opacity-50 pointer-events-none' : ''
76
- ]">
77
-
78
- <!-- Header -->
79
- <div v-on="headerEvents" class="flex items-center cursor-pointer select-none gap-3" :class="[
80
- margin
81
- ? (dense ? 'p-2' : 'p-3')
82
- : (dense ? 'py-2' : 'py-3'),
83
- isOpen ? 'border-b' : ''
84
- ]">
85
-
86
- <!-- Toggle (left) -->
87
- <UIButton v-if="toggleSide === 'left'" icon="chevron_right" flat dense class="transition-transform"
88
- :class="isOpen ? 'rotate-90' : ''" />
89
-
90
- <!-- Header content -->
91
- <div class="flex-1">
92
- <slot name="header">
93
- <h3 class="font-semibold">
94
- {{ title }}
95
- </h3>
96
- <p v-if="caption" class="text-gray-500 text-xs">
97
- {{ caption }}
98
- </p>
99
- </slot>
100
- </div>
101
-
102
- <!-- Toggle (right) -->
103
- <UIButton v-if="toggleSide === 'right'" icon="chevron_right" flat dense class="transition-transform"
104
- :class="isOpen ? 'rotate-90' : ''" />
105
- </div>
106
-
107
- <!-- Content -->
108
- <transition enter-active-class="transition-all duration-300 ease-out"
109
- leave-active-class="transition-all duration-200 ease-in" enter-from-class="opacity-0 max-h-0"
110
- enter-to-class="opacity-100 max-h-[1000px]" leave-from-class="opacity-100 max-h-[1000px]"
111
- leave-to-class="opacity-0 max-h-0">
112
- <div v-show="isOpen" class="overflow-hidden pb-3" :class="margin ? 'px-3' : 'px-none'">
113
- <slot />
114
- </div>
115
- </transition>
116
-
117
- </div>
118
- </template>
@@ -1,121 +0,0 @@
1
- <script setup lang="ts">
2
- defineOptions({
3
- inheritAttrs: false,
4
- })
5
-
6
- import { computed } from 'vue'
7
- import { useUiClasses } from './useUiClasses'
8
- import { uiProps } from "./uiProps";
9
-
10
- const props = defineProps({
11
- image: { type: String, default: '' },
12
- altText: { type: String, default: 'Avatar' },
13
- initials: { type: String, default: '' },
14
- borderColor: { type: String, default: '#fff' },
15
- borderThickness: { type: Number, default: 2 },
16
- shadow: { type: Boolean, default: false },
17
- ...uiProps
18
- })
19
-
20
- // Use composable
21
- const {
22
- // props
23
- contentClass,
24
- contentStyle,
25
- textColor,
26
- color,
27
- size,
28
- // computed
29
- colorClass,
30
- computedContentClass,
31
- isColorDark,
32
- isHex,
33
- isTailwindColor
34
- } = useUiClasses(props)
35
-
36
- const sizeClass = computed(() => {
37
- const map = {
38
- sm: 'avatar-sm',
39
- md: 'avatar-md',
40
- lg: 'avatar-lg'
41
- } as const
42
-
43
- return map[size.value as keyof typeof map]
44
- })
45
-
46
- const shadowClass = computed(() =>
47
- props.shadow ? 'shadow-lg' : ''
48
- )
49
-
50
- const avatarClass = computed(() => {
51
- return [
52
- computedContentClass.value,
53
- sizeClass.value,
54
- shadowClass.value,
55
- isTailwindColor(textColor.value) ? `text-${textColor.value}` : `text-${isColorDark(color.value) ? 'white' : 'gray-700'}`,
56
- isTailwindColor(color.value) ? `bg-${color.value}` : ""
57
- ].filter((c) => !["", "!"].includes(c))
58
- })
59
-
60
- const avatarStyle = computed(() => ({
61
- backgroundColor: isHex(color.value) && color.value,
62
- color: isHex(textColor.value) && textColor.value,
63
- borderColor: props.borderColor,
64
- borderWidth: `${props.borderThickness}px`,
65
- borderStyle: 'solid',
66
- ...contentStyle.value
67
- }))
68
- </script>
69
-
70
- <template>
71
- <div class="avatar" :class="avatarClass" :style="avatarStyle">
72
- <img v-if="image" :src="image" :alt="altText" class="avatar-image" />
73
- <span v-else class="avatar-initials">
74
- {{ initials || '..' }}
75
- </span>
76
- </div>
77
- </template>
78
-
79
- <style scoped>
80
- .avatar {
81
- display: flex;
82
- justify-content: center;
83
- align-items: center;
84
- border-radius: 50%;
85
- overflow: hidden;
86
- position: relative;
87
- text-align: center;
88
- }
89
-
90
- .avatar-sm {
91
- width: 40px;
92
- height: 40px;
93
- font-size: 0.875rem;
94
- }
95
-
96
- .avatar-md {
97
- width: 60px;
98
- height: 60px;
99
- font-size: 1rem;
100
- }
101
-
102
- .avatar-lg {
103
- width: 80px;
104
- height: 80px;
105
- font-size: 1.25rem;
106
- }
107
-
108
- .avatar-image {
109
- width: 100%;
110
- height: 100%;
111
- object-fit: cover;
112
- }
113
-
114
- .avatar-initials {
115
- font-weight: bold;
116
- }
117
-
118
- .shadow-lg {
119
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
120
- }
121
- </style>
@@ -1,116 +0,0 @@
1
- <script setup lang="ts">
2
- defineOptions({
3
- inheritAttrs: false,
4
- })
5
-
6
- import { useSlots, computed } from 'vue'
7
- import { useUiClasses } from './useUiClasses'
8
- import { uiProps } from "./uiProps";
9
-
10
- const slots = useSlots()
11
-
12
- const props = defineProps({
13
- content: { type: String, default: '' },
14
- icon: { type: String, default: '' },
15
- position: { type: String, default: 'superscript' },
16
- show: { type: Boolean, default: true },
17
- ...uiProps
18
- })
19
-
20
- // Use composable
21
- const {
22
- // props
23
- contentClass,
24
- contentStyle,
25
- color,
26
- size,
27
- rounded,
28
- // computed
29
- colorClass,
30
- computedContentClass,
31
- isColorDark,
32
- isHex,
33
- isTailwindColor
34
- } = useUiClasses(props)
35
-
36
- const hasSlots = computed(() => {
37
- return !!slots.default?.()
38
- })
39
-
40
- const sizeClass = computed(() => {
41
- const map = {
42
- xs: 'w-auto min-w-2 h-2.5 text-[7px]',
43
- sm: 'w-auto min-w-3 h-3 text-[10px]',
44
- md: 'w-auto min-w-4 h-4 text-xs',
45
- lg: 'w-auto min-w-6 h-6 text-sm'
46
- } as const
47
-
48
- return map[size.value as keyof typeof map]
49
- })
50
-
51
- const roundedClass = computed(() => {
52
- const map = {
53
- xs: 'rounded-xs',
54
- sm: 'rounded-sm',
55
- md: 'rounded',
56
- lg: 'rounded-lg',
57
- full: 'rounded-full'
58
- } as const
59
-
60
- return map[rounded.value as keyof typeof map]
61
- })
62
-
63
- const positionClasses = computed(() =>
64
- props.position === 'superscript' && hasSlots.value
65
- ? 'absolute top-0 right-0 transform translate-x-1/2 -translate-y-1/2'
66
- : ''
67
- )
68
-
69
- const badgeClass = computed(() => {
70
- return [
71
- 'inline-flex items-center justify-center font-bold',
72
- computedContentClass.value,
73
- sizeClass.value,
74
- roundedClass.value,
75
- positionClasses.value,
76
- isColorDark(color.value) ? 'text-white' : 'text-gray-700',
77
- isTailwindColor(color.value) ? `bg-${color.value}` : ""
78
- ].filter((c) => !["", "!"].includes(c))
79
- })
80
-
81
- const badgeStyle = computed(() => {
82
- const style = contentStyle.value
83
- return {
84
- backgroundColor: isHex(color.value) ? color.value : undefined,
85
- ...(typeof style === 'object' && style ? style : {})
86
- }
87
- })
88
- </script>
89
-
90
- <template>
91
- <div class="flex">
92
- <div v-if="hasSlots" class="relative">
93
- <slot />
94
- <span v-if="show" :class="[
95
- badgeClass
96
- ]" :style="[badgeStyle]">
97
- <span v-if="icon" class="p-1 material-icons">{{ icon }}</span>
98
- <span v-if="content" class="p-1">{{ content }}</span>
99
- </span>
100
- </div>
101
- <div v-else>
102
- <span v-if="show" :class="[
103
- badgeClass
104
- ]" :style="[badgeStyle]">
105
- <span v-if="icon" class="p-1 material-icons">{{ icon }}</span>
106
- <span v-if="content" class="p-1">{{ content }}</span>
107
- </span>
108
- </div>
109
- </div>
110
- </template>
111
-
112
- <style scoped>
113
- .material-icons {
114
- font-size: 1em;
115
- }
116
- </style>
@@ -1,138 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import { RouterLink, useRoute } from 'vue-router'
4
-
5
- type BreadcrumbItem = {
6
- label: string
7
- icon?: string
8
- caption?: string
9
- to?: string | object
10
- href?: string
11
- target?: string
12
- tag?: string
13
- exact?: boolean
14
- replace?: boolean
15
- activeClass?: string
16
- exactActiveClass?: string
17
- disable?: boolean
18
- }
19
-
20
- const props = defineProps({
21
- items: {
22
- type: Array as () => BreadcrumbItem[],
23
- required: true,
24
- },
25
- separator: {
26
- type: String,
27
- default: '/',
28
- },
29
- gutter: {
30
- type: String,
31
- default: '0.5rem',
32
- },
33
- align: {
34
- type: String,
35
- default: 'start', // start | center | end
36
- },
37
- activeColor: {
38
- type: String,
39
- default: 'primary',
40
- },
41
- separatorColor: {
42
- type: String,
43
- default: 'grey-6',
44
- },
45
- })
46
-
47
- const route = useRoute()
48
-
49
- const alignmentClass = computed(() => {
50
- switch (props.align) {
51
- case 'center':
52
- return 'justify-center'
53
- case 'end':
54
- return 'justify-end'
55
- default:
56
- return 'justify-start'
57
- }
58
- })
59
-
60
- function resolveTag(item: BreadcrumbItem) {
61
- if (item.disable) return item.tag || 'span'
62
- if (item.href) return 'a'
63
- if (item.to) return RouterLink
64
- return item.tag || 'span'
65
- }
66
-
67
- function resolveProps(item: BreadcrumbItem) {
68
- if (item.href) {
69
- return {
70
- href: item.href,
71
- target: item.target,
72
- }
73
- }
74
-
75
- if (item.to) {
76
- return {
77
- to: item.to,
78
- exact: item.exact,
79
- replace: item.replace,
80
- activeClass: item.activeClass,
81
- exactActiveClass: item.exactActiveClass,
82
- }
83
- }
84
-
85
- return {}
86
- }
87
-
88
- function isActive(item: BreadcrumbItem) {
89
- if (!item.to) return false
90
- return route.path === item.to
91
- }
92
- </script>
93
-
94
- <template>
95
- <nav
96
- class="flex items-center"
97
- :class="alignmentClass"
98
- aria-label="Breadcrumbs"
99
- >
100
- <template v-for="(item, index) in items" :key="index">
101
- <!-- Item -->
102
- <component
103
- :is="resolveTag(item)"
104
- v-bind="resolveProps(item)"
105
- class="flex items-center gap-1"
106
- :class="[
107
- item.disable && 'opacity-50 pointer-events-none',
108
- isActive(item) && `text-${activeColor}`,
109
- ]"
110
- >
111
- <span v-if="item.icon && item.icon !== 'none'" class="material-icons">
112
- {{ item.icon }}
113
- </span>
114
-
115
- <span>{{ item.label }}</span>
116
-
117
- <small
118
- v-if="item.caption"
119
- class="block text-xs opacity-70"
120
- >
121
- {{ item.caption }}
122
- </small>
123
- </component>
124
-
125
- <!-- Separator -->
126
- <span
127
- v-if="index < items.length - 1"
128
- class="select-none"
129
- :style="{ marginInline: gutter }"
130
- :class="`text-${separatorColor}`"
131
- >
132
- <slot name="separator">
133
- {{ separator }}
134
- </slot>
135
- </span>
136
- </template>
137
- </nav>
138
- </template>