windly-ui 1.0.1 → 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 (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -33
  3. package/package.json +47 -15
  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/ui-library/components/Accordion.vue +0 -118
  9. package/ui-library/components/Avatar.vue +0 -121
  10. package/ui-library/components/Badge.vue +0 -116
  11. package/ui-library/components/Breadcrumbs.vue +0 -138
  12. package/ui-library/components/Button.vue +0 -154
  13. package/ui-library/components/Card.vue +0 -84
  14. package/ui-library/components/Checkbox.vue +0 -148
  15. package/ui-library/components/CodeBlock.vue +0 -99
  16. package/ui-library/components/ColorPicker.vue +0 -302
  17. package/ui-library/components/Date.vue +0 -240
  18. package/ui-library/components/Dialog.vue +0 -187
  19. package/ui-library/components/Divider.vue +0 -78
  20. package/ui-library/components/Drawer.vue +0 -67
  21. package/ui-library/components/Dropdown.vue +0 -248
  22. package/ui-library/components/FileUploader.vue +0 -330
  23. package/ui-library/components/Icon.vue +0 -82
  24. package/ui-library/components/Image.vue +0 -78
  25. package/ui-library/components/Input.vue +0 -531
  26. package/ui-library/components/Radio.vue +0 -356
  27. package/ui-library/components/ScrollArea.vue +0 -43
  28. package/ui-library/components/Select.vue +0 -309
  29. package/ui-library/components/Stepper.vue +0 -361
  30. package/ui-library/components/TabPanel.vue +0 -25
  31. package/ui-library/components/Table.vue +0 -152
  32. package/ui-library/components/Tabs.vue +0 -71
  33. package/ui-library/components/Textarea.vue +0 -233
  34. package/ui-library/components/Toggle.vue +0 -319
  35. package/ui-library/components/Tooltip.vue +0 -200
  36. package/ui-library/components/plugin.ts +0 -8
  37. package/ui-library/components/uiProps.ts +0 -11
  38. package/ui-library/components/useUiClasses.ts +0 -159
  39. package/ui-library/module.ts +0 -20
@@ -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>
@@ -1,154 +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
- // Props
11
- const props = defineProps({
12
- label: { type: String, default: null },
13
- to: { type: [String, Object], default: null },
14
- outline: { type: Boolean, default: false },
15
- flat: { type: Boolean, default: false },
16
- unelevated: { type: Boolean, default: false },
17
- dense: { type: Boolean, default: false },
18
- stacked: { type: Boolean, default: false },
19
- gradient: { type: Boolean, default: false },
20
- color2: { type: String, default: '' },
21
- icon: { type: String, default: null },
22
- iconColor: { type: String, default: null },
23
- iconPosition: { type: String, default: 'start' },
24
- ...uiProps
25
- })
26
-
27
- // Use composable
28
- const {
29
- // props
30
- contentClass,
31
- contentStyle,
32
- textColor,
33
- disable,
34
- loading,
35
- color,
36
- rounded,
37
- size,
38
- iconSize,
39
- // class maps
40
- roundedClass,
41
- sizeClass,
42
- iconSizeClasses,
43
- // computed
44
- colorClass,
45
- computedContentClass,
46
- isColorDark,
47
- isHex,
48
- isTailwindColor,
49
- shadeTailwind,
50
- shadeHex
51
- } = useUiClasses(props)
52
-
53
- const paddingClass = {
54
- default: {
55
- sm: "py-1 px-3 min-h-8 h-auto w-auto" + (props.label ? " min-w-16" : ""),
56
- md: "py-2 px-6 min-h-10 h-auto w-auto" + (props.label ? " min-w-28":""),
57
- lg: "py-3 px-8 min-h-12 h-auto w-auto" + (props.label ? " min-w-36":""),
58
- },
59
- dense: {
60
- sm: "py-0.5 px-2 min-h-6 h-auto w-auto" + (props.label ? " min-w-16":""),
61
- md: "py-1 px-4 min-h-8 h-auto w-auto" + (props.label ? " min-w-24":""),
62
- lg: "py-1.5 px-6 min-h-10 h-auto w-auto" + (props.label ? " min-w-32":""),
63
- },
64
- };
65
-
66
- const computedClass = computed(() => {
67
- let cls = `transition-all duration-300 text-nowrap ${roundedClass[(rounded.value as keyof typeof roundedClass)]} ${sizeClass[(size.value as keyof typeof sizeClass)]}`;
68
-
69
- if (props.outline)
70
- cls += ` border bg-white border-${colorClass.value} text-${colorClass.value} hover:bg-${colorClass.value} hover:text-white`;
71
-
72
- if (props.flat)
73
- cls += ` bg-transparent text-${colorClass.value} hover:bg-${colorClass.value} hover:bg-opacity-10 shadow-none`;
74
-
75
- cls += props.dense ? ` ${paddingClass.dense[size.value as keyof typeof paddingClass.dense]}` : ` ${paddingClass.default[size.value as keyof typeof paddingClass.default]}`;
76
- cls += props.unelevated ? " no-shadow" : " shadow-md";
77
- cls += props.stacked ? " flex flex-col items-center justify-center gap-2" : " flex items-center justify-center gap-2";
78
- cls += disable.value ? " opacity-50 cursor-not-allowed" : " cursor-pointer";
79
- cls += loading.value ? " opacity-75 cursor-progress" : "";
80
- cls += props.gradient
81
- ? ` bg-gradient-to-br from-${color.value} to-${computedColor2.value} hover:bg-gradient-to-tl text-${computedTextColor.value}`
82
- : !props.flat && !props.outline
83
- ? ` bg-${color.value} hover:bg-${computedColor2.value} text-${computedTextColor.value}`
84
- : "";
85
-
86
- return cls;
87
- });
88
-
89
- const computedIconClass = computed(() => {
90
- return `material-icons ${iconSizeClasses[props.iconPosition === 'start' ? 'lg' : 'md']} ${props.iconColor ? `text-${props.iconColor}` : ''}`
91
- })
92
-
93
- // Computed props for v-bind
94
- const computedProps = computed(() => ({
95
- class: computedClass.value,
96
- disabled: disable.value || loading.value
97
- }))
98
-
99
- const computedTextColor = computed(() => {
100
- if (textColor.value) {
101
- return textColor.value;
102
- }
103
-
104
- if (props.outline || props.flat) {
105
- return color.value;
106
- }
107
-
108
- const darkBg = isColorDark(color.value);
109
- return darkBg ? "white" : "gray-900"
110
- })
111
-
112
- const computedColor2 = computed(() => {
113
- if (props.color2) return props.color2
114
-
115
- if (isTailwindColor(color.value)) {
116
- return shadeTailwind(color.value, 100)
117
- }
118
-
119
- if (isHex(color.value)) {
120
- return shadeHex(color.value, -20)
121
- }
122
-
123
- return color.value
124
- })
125
-
126
- // Click handler
127
- function onClick() {
128
- if (!loading.value && !disable.value && !props.to) {
129
- emit('click')
130
- }
131
- }
132
-
133
- // Emit function for script setup
134
- const emit = defineEmits(['click'])
135
- </script>
136
-
137
- <template>
138
- <!-- Router Link if "to" prop is provided -->
139
- <router-link v-if="to" v-bind="computedProps" :to="to">
140
- <!-- Icon (start position) -->
141
- <i v-if="icon && iconPosition === 'start'" :class="computedIconClass">{{ icon }}</i>
142
- <!-- Label -->
143
- <span v-if="label" class="label">{{ label }}</span>
144
- <!-- Icon (end position) -->
145
- <i v-if="icon && iconPosition === 'end'" :class="computedIconClass">{{ icon }}</i>
146
- </router-link>
147
-
148
- <!-- Normal button if no "to" prop -->
149
- <button v-else v-bind="computedProps" @click="onClick">
150
- <i v-if="icon && iconPosition === 'start'" :class="computedIconClass">{{ icon }}</i>
151
- <span v-if="label" class="label">{{ label }}</span>
152
- <i v-if="icon && iconPosition === 'end'" :class="computedIconClass">{{ icon }}</i>
153
- </button>
154
- </template>
@@ -1,84 +0,0 @@
1
- <script setup lang="ts">
2
- defineOptions({
3
- inheritAttrs: false,
4
- })
5
-
6
- import { computed } from 'vue'
7
-
8
- const props = defineProps({
9
- flat: { type: Boolean, default: false },
10
- square: { type: Boolean, default: false },
11
- dense: { type: Boolean, default: false },
12
- bordered: { type: Boolean, default: true },
13
-
14
- color: { type: String, default: 'white' },
15
- borderColor: { type: String, default: 'gray-200' },
16
-
17
- width: { type: String, default: '100%' },
18
- })
19
-
20
- const isHex = (val: string) => val.startsWith('#')
21
-
22
- const cardStyle = computed(() => {
23
- const style: Record<string, string> = {
24
- width: props.width,
25
- maxWidth: '80vw',
26
- }
27
-
28
- if (isHex(props.color)) {
29
- style.backgroundColor = props.color
30
- }
31
-
32
- if (props.bordered && isHex(props.borderColor)) {
33
- style.borderColor = props.borderColor
34
- }
35
-
36
- return style
37
- })
38
-
39
- const cardClass = computed(() => [
40
- 'transition-shadow duration-300',
41
- props.flat ? 'shadow-none' : 'shadow-md',
42
- props.square ? 'rounded-none' : 'rounded-md',
43
- props.bordered ? 'border' : '',
44
- !isHex(props.borderColor) && props.bordered
45
- ? `border-${props.borderColor}`
46
- : '',
47
- !isHex(props.color) ? `bg-${props.color}` : '',
48
- ])
49
-
50
- const sectionPadding = computed(() =>
51
- props.dense ? 'p-0' : 'p-4'
52
- )
53
- </script>
54
-
55
- <template>
56
- <div
57
- :class="cardClass"
58
- :style="cardStyle"
59
- v-bind="$attrs"
60
- >
61
- <!-- Header -->
62
- <div
63
- v-if="$slots.header"
64
- :class="sectionPadding"
65
- class="border-b border-gray-200"
66
- >
67
- <slot name="header" />
68
- </div>
69
-
70
- <!-- Content -->
71
- <div :class="sectionPadding">
72
- <slot />
73
- </div>
74
-
75
- <!-- Footer -->
76
- <div
77
- v-if="$slots.footer"
78
- :class="sectionPadding"
79
- class="border-t border-gray-200"
80
- >
81
- <slot name="footer" />
82
- </div>
83
- </div>
84
- </template>
@@ -1,148 +0,0 @@
1
- <script setup lang="ts">
2
- defineOptions({ inheritAttrs: false })
3
-
4
- import { computed } from 'vue'
5
- import { useUiClasses } from './useUiClasses'
6
- import { uiProps } from './uiProps'
7
-
8
- const emit = defineEmits<{
9
- (e: 'update:modelValue', value: any): void
10
- }>()
11
-
12
- const props = defineProps({
13
- modelValue: { required: true },
14
-
15
- name: { type: String, default: null },
16
- label: { type: String, default: null },
17
-
18
- trueValue: { default: true },
19
- falseValue: { default: false },
20
- intermediateValue: { default: null },
21
-
22
- checkedIcon: { type: String, default: 'check' },
23
- uncheckedIcon: { type: String, default: null },
24
- intermediateIcon: { type: String, default: 'remove' },
25
-
26
- checkedColor: { type: String, default: '' },
27
- uncheckedColor: { type: String, default: 'gray-400' },
28
-
29
- dense: { type: Boolean, default: false },
30
-
31
- ...uiProps,
32
- })
33
-
34
- const {
35
- disable,
36
- color,
37
- size,
38
- rounded,
39
- roundedClass,
40
- } = useUiClasses(props)
41
-
42
- const isChecked = computed(
43
- () => props.modelValue === props.trueValue
44
- )
45
-
46
- const isIntermediate = computed(
47
- () => props.modelValue === props.intermediateValue
48
- )
49
-
50
- const boxSizeClass = computed(() => {
51
- const map = {
52
- sm: 'w-4 h-4',
53
- md: 'w-5 h-5',
54
- lg: 'w-6 h-6',
55
- } as const
56
-
57
- return map[size.value as keyof typeof map]
58
- })
59
-
60
- const labelSizeClass = computed(() => {
61
- const map = {
62
- sm: 'text-sm',
63
- md: 'text-base',
64
- lg: 'text-xl',
65
- } as const
66
-
67
- return map[size.value as keyof typeof map]
68
- })
69
-
70
- const boxColorClass = computed(() => {
71
- if (isChecked.value) {
72
- return `bg-${props.checkedColor || color.value} border-${props.checkedColor || color.value}`
73
- }
74
-
75
- if (isIntermediate.value) {
76
- return `bg-${color.value} border-${color.value}`
77
- }
78
-
79
- return `bg-transparent border-${props.uncheckedColor}`
80
- })
81
-
82
- const containerClass = computed(() =>
83
- `
84
- inline-flex items-center gap-2
85
- ${disable.value ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
86
- `
87
- )
88
-
89
- function toggle() {
90
- if (disable.value) return
91
-
92
- if (isChecked.value || isIntermediate.value) {
93
- emit('update:modelValue', props.falseValue)
94
- } else {
95
- emit('update:modelValue', props.trueValue)
96
- }
97
- }
98
- </script>
99
- <template>
100
- <label
101
- :class="containerClass"
102
- role="checkbox"
103
- :aria-checked="isIntermediate ? 'mixed' : isChecked"
104
- >
105
- <input
106
- type="checkbox"
107
- class="hidden"
108
- :name="name"
109
- :disabled="disable"
110
- @change="toggle"
111
- />
112
-
113
- <span
114
- :class="[
115
- 'flex items-center justify-center border transition-all duration-200 p-2',
116
- boxSizeClass,
117
- roundedClass[rounded as keyof typeof roundedClass],
118
- boxColorClass,
119
- ]"
120
- @click.prevent="toggle"
121
- >
122
- <i
123
- v-if="isChecked && checkedIcon"
124
- :class="labelSizeClass"
125
- class="material-icons text-white"
126
- >
127
- {{ checkedIcon }}
128
- </i>
129
- <i
130
- v-else-if="isIntermediate && intermediateIcon"
131
- :class="labelSizeClass"
132
- class="material-icons text-white"
133
- >
134
- {{ intermediateIcon }}
135
- </i>
136
- <i
137
- v-else-if="uncheckedIcon"
138
- :class="labelSizeClass"
139
- class="material-icons text-gray-400"
140
- >
141
- {{ uncheckedIcon }}
142
- </i>
143
- </span>
144
- <span v-if="label" class="select-none" :class="labelSizeClass">
145
- {{ label }}
146
- </span>
147
- </label>
148
- </template>
@@ -1,99 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, useSlots, computed, isVNode } from 'vue'
3
-
4
- defineProps<{
5
- title?: string
6
- }>()
7
-
8
- const activeTab = ref('example')
9
- const slots = useSlots()
10
-
11
- import type { VNode } from 'vue'
12
-
13
- function stringifyProps(props: unknown): string {
14
- if (!props || typeof props !== 'object') {
15
- return ''
16
- }
17
-
18
- return Object.entries(props as Record<string, any>)
19
- .filter(([key]) => !key.startsWith('on')) // 🔥 KEY FIX
20
- .map(([key, value]) => {
21
- if (value === true) return key
22
- if (value === false || value == null) return ''
23
- return `${key}="${String(value)}"`
24
- })
25
- .filter(Boolean)
26
- .join(' ')
27
- }
28
-
29
- function renderVNode(vnode: VNode): string {
30
- if (typeof vnode.children === 'string') {
31
- return vnode.children
32
- }
33
-
34
- if (vnode.type === Symbol.for('v-fgt')) {
35
- return (vnode.children as VNode[])
36
- .map(renderVNode)
37
- .join('')
38
- }
39
-
40
- if (typeof vnode.type === 'object') {
41
- const typeAny = vnode.type as any
42
- const name = typeAny.__name || typeAny.name || typeAny.displayName || 'Component'
43
- const props = stringifyProps(vnode.props as any)
44
- return `<${name}${props ? ' ' + props : ''} />`
45
- }
46
-
47
- if (typeof vnode.type === 'string') {
48
- const children = Array.isArray(vnode.children)
49
- ? vnode.children
50
- .map(child => (isVNode(child) ? renderVNode(child) : (typeof child === 'string' ? child : '')))
51
- .join('')
52
- : (typeof vnode.children === 'string' ? vnode.children : '')
53
- return `<${vnode.type}>${children}</${vnode.type}>`
54
- }
55
-
56
- return ''
57
- }
58
-
59
- const slotCode = computed(() => {
60
- const vnodes = slots.default?.() || []
61
- return vnodes.map(renderVNode).join('')
62
- })
63
-
64
- const buttonIcon = ref('content_copy')
65
-
66
- const copyCode = async () => {
67
- await navigator.clipboard.writeText(slotCode.value)
68
-
69
- buttonIcon.value = 'check'
70
- setTimeout(() => {
71
- buttonIcon.value = 'content_copy'
72
- }, 2000)
73
- }
74
- </script>
75
- <template>
76
- <div class="border rounded-lg bg-white overflow-hidden">
77
- <!-- Header -->
78
- <div class="flex items-center justify-between px-4 py-2 border-b bg-gray-50">
79
- <h3 v-if="title" class="font-medium text-gray-700">
80
- {{ title }}
81
- </h3>
82
-
83
- <UIButton flat :icon="buttonIcon" size="sm" class="text-gray-600" @click="copyCode" />
84
- </div>
85
-
86
- <!-- Tabs -->
87
- <UITabs v-model="activeTab">
88
- <UITabPanel name="example" label="Example">
89
- <div class="p-4">
90
- <slot />
91
- </div>
92
- </UITabPanel>
93
-
94
- <UITabPanel name="code" label="Code">
95
- <pre class="p-4 bg-gray-900 text-gray-100 text-sm overflow-x-auto"><code>{{ slotCode }}</code></pre>
96
- </UITabPanel>
97
- </UITabs>
98
- </div>
99
- </template>