@indielayer/ui 1.0.0-alpha.0 → 1.0.0-alpha.5

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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -72
  3. package/lib/components/avatar/Avatar.vue.d.ts +2 -2
  4. package/lib/components/badge/Badge.vue.d.ts +2 -2
  5. package/lib/components/button/Button.vue.d.ts +2 -2
  6. package/lib/components/button/ButtonGroup.vue.d.ts +2 -2
  7. package/lib/components/checkbox/Checkbox.vue.d.ts +4 -3
  8. package/lib/components/drawer/Drawer.vue.d.ts +2 -2
  9. package/lib/components/icon/Icon.vue.d.ts +7 -3
  10. package/lib/components/index.d.ts +2 -2
  11. package/lib/components/input/Input.vue.d.ts +3 -2
  12. package/lib/components/menu/Menu.vue.d.ts +2 -2
  13. package/lib/components/menu/MenuItem.vue.d.ts +3 -3
  14. package/lib/components/notifications/Notifications.vue.d.ts +2 -2
  15. package/lib/components/pagination/Pagination.vue.d.ts +3 -2
  16. package/lib/components/pagination/PaginationItem.vue.d.ts +2 -2
  17. package/lib/components/radio/Radio.vue.d.ts +2 -2
  18. package/lib/components/select/Select.vue.d.ts +3 -2
  19. package/lib/components/slider/Slider.vue.d.ts +2 -2
  20. package/lib/components/spacer/Spacer.vue.d.ts +1 -1
  21. package/lib/components/spinner/Spinner.vue.d.ts +2 -2
  22. package/lib/components/{tabs → tab}/Tab.vue.d.ts +2 -2
  23. package/lib/components/{tabs/Tabs.vue.d.ts → tab/TabGroup.vue.d.ts} +0 -0
  24. package/lib/components/table/TableBody.vue.d.ts +1 -1
  25. package/lib/components/table/TableHead.vue.d.ts +1 -1
  26. package/lib/components/tag/Tag.vue.d.ts +2 -2
  27. package/lib/components/textarea/Textarea.vue.d.ts +3 -11
  28. package/lib/components/toggle/Toggle.vue.d.ts +2 -2
  29. package/lib/composables/keys.d.ts +1 -0
  30. package/lib/create.d.ts +12 -0
  31. package/lib/index.cjs.js +2 -2
  32. package/lib/index.d.ts +2 -0
  33. package/lib/index.es.js +271 -130
  34. package/lib/install.d.ts +4 -6
  35. package/lib/nuxt.js +15 -16
  36. package/lib/nuxt.plugin.js +8 -0
  37. package/lib/style.css +1 -1
  38. package/lib/version.d.ts +1 -1
  39. package/package.json +21 -15
  40. package/src/components/alert/Alert.vue +164 -0
  41. package/src/components/avatar/Avatar.vue +137 -0
  42. package/src/components/badge/Badge.vue +107 -0
  43. package/src/components/breadcrumbs/Breadcrumbs.vue +60 -0
  44. package/src/components/button/Button.vue +433 -0
  45. package/src/components/button/ButtonGroup.vue +73 -0
  46. package/src/components/card/Card.vue +25 -0
  47. package/src/components/checkbox/Checkbox.vue +205 -0
  48. package/src/components/collapse/Collapse.vue +181 -0
  49. package/src/components/container/Container.vue +23 -0
  50. package/src/components/divider/Divider.vue +52 -0
  51. package/src/components/drawer/Drawer.vue +244 -0
  52. package/src/components/form/Form.vue +111 -0
  53. package/src/components/icon/Icon.vue +123 -0
  54. package/src/components/image/Image.vue +36 -0
  55. package/src/components/index.ts +45 -0
  56. package/src/components/input/Input.vue +199 -0
  57. package/src/components/link/Link.vue +110 -0
  58. package/src/components/menu/Menu.vue +118 -0
  59. package/src/components/menu/MenuItem.vue +277 -0
  60. package/src/components/modal/Modal.vue +175 -0
  61. package/src/components/notifications/Notifications.vue +318 -0
  62. package/src/components/pagination/Pagination.vue +181 -0
  63. package/src/components/pagination/PaginationItem.vue +58 -0
  64. package/src/components/popover/Popover.vue +194 -0
  65. package/src/components/popover/PopoverContainer.vue +23 -0
  66. package/src/components/progress/Progress.vue +86 -0
  67. package/src/components/radio/Radio.vue +220 -0
  68. package/src/components/scroll/Scroll.vue +143 -0
  69. package/src/components/select/Select.vue +408 -0
  70. package/src/components/skeleton/Skeleton.vue +23 -0
  71. package/src/components/slider/Slider.vue +240 -0
  72. package/src/components/spacer/Spacer.vue +11 -0
  73. package/src/components/spinner/Spinner.vue +45 -0
  74. package/src/components/tab/Tab.vue +100 -0
  75. package/src/components/tab/TabGroup.vue +151 -0
  76. package/src/components/table/Table.vue +172 -0
  77. package/src/components/table/TableBody.vue +13 -0
  78. package/src/components/table/TableCell.vue +78 -0
  79. package/src/components/table/TableHead.vue +15 -0
  80. package/src/components/table/TableHeader.vue +94 -0
  81. package/src/components/table/TableRow.vue +43 -0
  82. package/src/components/tag/Tag.vue +98 -0
  83. package/src/components/textarea/Textarea.vue +156 -0
  84. package/src/components/toggle/Toggle.vue +144 -0
  85. package/src/components/tooltip/Tooltip.vue +26 -0
  86. package/src/composables/colors-utils.ts +378 -0
  87. package/src/composables/colors.ts +82 -0
  88. package/src/composables/common.ts +20 -0
  89. package/src/composables/css.ts +45 -0
  90. package/src/composables/index.ts +7 -0
  91. package/src/composables/inputtable.ts +128 -0
  92. package/src/composables/interactive.ts +16 -0
  93. package/src/composables/keys.ts +8 -0
  94. package/src/composables/notification.ts +10 -0
  95. package/src/create.ts +38 -0
  96. package/src/exports/nuxt.js +32 -0
  97. package/src/exports/nuxt.plugin.js +8 -0
  98. package/src/exports/tailwind.preset.js +55 -0
  99. package/src/index.ts +8 -0
  100. package/src/install.ts +8 -0
  101. package/src/shims-vue.d.ts +6 -0
  102. package/src/version.ts +1 -0
  103. package/volar.d.ts +1 -1
@@ -0,0 +1,111 @@
1
+ <script lang="ts">
2
+ import { defineComponent, provide, onMounted, watch, nextTick } from 'vue'
3
+ import { injectFormKey } from '../../composables/keys'
4
+
5
+ export type Form = {
6
+ name: string,
7
+ focus: ()=> void,
8
+ validate: ()=> boolean,
9
+ setError: (val: string)=> void,
10
+ }
11
+
12
+ export default defineComponent({
13
+ name: 'XForm',
14
+
15
+ inheritAttrs: false,
16
+
17
+ props: {
18
+ autoValidate: {
19
+ type: Boolean,
20
+ default: true,
21
+ },
22
+ autoFocus: {
23
+ type: Boolean,
24
+ default: true,
25
+ },
26
+ disabled: Boolean,
27
+ errors: {
28
+ type: Array,
29
+ default: () => [],
30
+ },
31
+ },
32
+
33
+ emits: ['submit'],
34
+
35
+ setup(props, { emit }) {
36
+ const inputs: Form[] = []
37
+
38
+ provide(injectFormKey, {
39
+ registerInput: (name: string, focus: ()=> void, validate: ()=> boolean, setError: (val: string)=> void) => {
40
+ const exists = inputs.find((i) => i.name === name)
41
+
42
+ if (exists) {
43
+ exists.focus = focus
44
+ exists.validate = validate
45
+ exists.setError = setError
46
+ }
47
+ else inputs.push({ name, focus, validate, setError })
48
+ },
49
+ unregisterInput: (name: string) => {
50
+ const index = inputs.findIndex((i) => i.name === name)
51
+
52
+ inputs.splice(index, 1)
53
+ },
54
+ isInsideForm: true,
55
+ })
56
+
57
+ onMounted(() => {
58
+ if (props.autoFocus && inputs && inputs.length > 0) inputs[0].focus()
59
+ })
60
+
61
+ watch(() => props.errors, (errors) => {
62
+ nextTick(() => {
63
+ errors.forEach((error: any) => {
64
+ const input = inputs.find((i) => i.name === error.field)
65
+
66
+ if (input) input.setError(error.msg)
67
+ })
68
+ })
69
+ })
70
+
71
+ const validate = () => {
72
+ let isFormValid = true
73
+
74
+ inputs.forEach((input) => {
75
+ const isInputValid = input.validate()
76
+
77
+ if (!isInputValid && isFormValid) {
78
+ isFormValid = false
79
+
80
+ // focus on input error
81
+ if (input.focus) input.focus()
82
+ }
83
+ })
84
+
85
+ return isFormValid
86
+ }
87
+
88
+ const submit = (e: Event) => {
89
+ e.preventDefault()
90
+ e.stopPropagation()
91
+
92
+ const isFormValid = props.autoValidate ? validate() : true
93
+
94
+ emit('submit', isFormValid)
95
+ }
96
+
97
+ return {
98
+ validate,
99
+ submit,
100
+ }
101
+ },
102
+ })
103
+ </script>
104
+
105
+ <template>
106
+ <form @submit="submit">
107
+ <fieldset :disabled="disabled">
108
+ <slot></slot>
109
+ </fieldset>
110
+ </form>
111
+ </template>
@@ -0,0 +1,123 @@
1
+ <script lang="ts">
2
+ import { computed, defineComponent, inject, ref, watchEffect } from 'vue'
3
+ import { useCommon } from '../../composables/common'
4
+ import { injectIconsKey } from '../../composables/keys'
5
+
6
+ export default defineComponent({
7
+ name: 'XIcon',
8
+
9
+ validators: {
10
+ ...useCommon.validators(),
11
+ },
12
+
13
+ props: {
14
+ ...useCommon.props(),
15
+ icon: {
16
+ type: String,
17
+ required: true,
18
+ },
19
+ filled: Boolean,
20
+ viewBox: {
21
+ type: String,
22
+ default: '0 0 24 24',
23
+ },
24
+ },
25
+
26
+ setup(props) {
27
+ const icons: any = inject(injectIconsKey, {})
28
+ const sizeClasses = computed(() => {
29
+ if (props.size === 'xs') return 'h-3 w-3'
30
+ else if (props.size === 'sm') return 'h-4 w-4'
31
+ else if (props.size === 'lg') return 'h-6 w-6'
32
+ else if (props.size === 'xl') return 'h-9 w-9'
33
+
34
+ return 'h-5 w-5'
35
+ })
36
+
37
+ const isWrapSVG = ref(false)
38
+ const computedIcon = ref('')
39
+ const computedFilled = ref(props.filled)
40
+ const computedViewBox = ref(props.viewBox)
41
+ const attrs = ref({})
42
+
43
+ watchEffect(() => {
44
+ const injectedIcon = icons[props.icon]
45
+
46
+ isWrapSVG.value = false
47
+ computedIcon.value = injectedIcon
48
+
49
+ if (injectedIcon) {
50
+ if (typeof injectedIcon === 'string') {
51
+ if (injectedIcon.startsWith('<svg')) {
52
+ isWrapSVG.value = true
53
+
54
+ const { content, attributes } = getSVG(injectedIcon)
55
+
56
+ attrs.value = attributes
57
+ computedIcon.value = content
58
+ } else {
59
+ isWrapSVG.value = false
60
+ computedIcon.value = injectedIcon
61
+ }
62
+ } else if (typeof injectedIcon === 'object') {
63
+ computedIcon.value = injectedIcon.icon
64
+ computedFilled.value = injectedIcon.filled || props.filled
65
+ computedViewBox.value = injectedIcon.viewBox || injectedIcon.viewbox || props.viewBox
66
+ }
67
+ } else {
68
+ computedIcon.value = props.icon
69
+ }
70
+ })
71
+
72
+ function getSVG(svgString: string) {
73
+ const temp = document.createElement('template')
74
+
75
+ temp.innerHTML = svgString.trim()
76
+
77
+ const [svg] = temp.content.children
78
+ const names = svg.getAttributeNames()
79
+ const attributes: Record<string, string | null> = {}
80
+
81
+ names.forEach((n) => {
82
+ if (!['height', 'width', 'class'].includes(n)) attributes[n] = svg.getAttribute(n)
83
+ })
84
+
85
+ return {
86
+ attributes,
87
+ content: svg.innerHTML,
88
+ }
89
+ }
90
+
91
+ return {
92
+ attrs,
93
+ isWrapSVG,
94
+ computedIcon,
95
+ computedFilled,
96
+ computedViewBox,
97
+ sizeClasses,
98
+ }
99
+ },
100
+ })
101
+ </script>
102
+
103
+ <template>
104
+ <svg
105
+ v-if="isWrapSVG"
106
+ class="inline"
107
+ :class="sizeClasses"
108
+ v-bind="attrs"
109
+ v-html="computedIcon"
110
+ />
111
+ <svg
112
+ v-else
113
+ xmlns="http://www.w3.org/2000/svg"
114
+ class="inline"
115
+ :class="[sizeClasses, { 'stroke-2': !computedFilled}]"
116
+ :stroke-linejoin="computedFilled ? undefined : 'round'"
117
+ :stroke-linecap="computedFilled ? undefined : 'round'"
118
+ :stroke="computedFilled ? undefined : 'currentColor'"
119
+ :fill="computedFilled ? 'currentColor' : 'none'"
120
+ :viewBox="computedViewBox"
121
+ v-html="computedIcon"
122
+ />
123
+ </template>
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import { defineComponent, watch, ref } from 'vue'
3
+
4
+ const fallback = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
5
+
6
+ export default defineComponent({
7
+ name: 'XImage',
8
+
9
+ props: {
10
+ src: String,
11
+ },
12
+
13
+ setup(props) {
14
+ const source = ref<string | undefined>(fallback)
15
+
16
+ watch(() => props.src, (src) => {
17
+ if (!src) return
18
+ const img = new Image()
19
+
20
+ img.onload = () => { source.value = props.src }
21
+ img.onerror = () => { }
22
+ img.src = src
23
+ }, {
24
+ immediate: true,
25
+ })
26
+
27
+ return {
28
+ source,
29
+ }
30
+ },
31
+ })
32
+ </script>
33
+
34
+ <template>
35
+ <img :src="source" />
36
+ </template>
@@ -0,0 +1,45 @@
1
+ export { default as XAlert } from './alert/Alert.vue'
2
+ export { default as XAvatar } from './avatar/Avatar.vue'
3
+ export { default as XBadge } from './badge/Badge.vue'
4
+ export { default as XBreadcrumbs } from './breadcrumbs/Breadcrumbs.vue'
5
+ export { default as XButton } from './button/Button.vue'
6
+ export { default as XButtonGroup } from './button/ButtonGroup.vue'
7
+ export { default as XCard } from './card/Card.vue'
8
+ export { default as XCheckbox } from './checkbox/Checkbox.vue'
9
+ export { default as XCollapse } from './collapse/Collapse.vue'
10
+ export { default as XContainer } from './container/Container.vue'
11
+ export { default as XDivider } from './divider/Divider.vue'
12
+ export { default as XDrawer } from './drawer/Drawer.vue'
13
+ export { default as XForm } from './form/Form.vue'
14
+ export { default as XIcon } from './icon/Icon.vue'
15
+ export { default as XImage } from './image/Image.vue'
16
+ export { default as XInput } from './input/Input.vue'
17
+ export { default as XLink } from './link/Link.vue'
18
+ export { default as XMenu } from './menu/Menu.vue'
19
+ export { default as XMenuItem } from './menu/MenuItem.vue'
20
+ export { default as XModal } from './modal/Modal.vue'
21
+ export { default as XNotifications } from './notifications/Notifications.vue'
22
+ export { default as XPagination } from './pagination/Pagination.vue'
23
+ export { default as XPaginationItem } from './pagination/PaginationItem.vue'
24
+ export { default as XPopover } from './popover/Popover.vue'
25
+ export { default as XPopoverContainer } from './popover/PopoverContainer.vue'
26
+ export { default as XProgress } from './progress/Progress.vue'
27
+ export { default as XRadio } from './radio/Radio.vue'
28
+ export { default as XScroll } from './scroll/Scroll.vue'
29
+ export { default as XSelect } from './select/Select.vue'
30
+ export { default as XSkeleton } from './skeleton/Skeleton.vue'
31
+ export { default as XSlider } from './slider/Slider.vue'
32
+ export { default as XSpacer } from './spacer/Spacer.vue'
33
+ export { default as XSpinner } from './spinner/Spinner.vue'
34
+ export { default as XTable } from './table/Table.vue'
35
+ export { default as XTableBody } from './table/TableBody.vue'
36
+ export { default as XTableCell } from './table/TableCell.vue'
37
+ export { default as XTableHead } from './table/TableHead.vue'
38
+ export { default as XTableHeader } from './table/TableHeader.vue'
39
+ export { default as XTableRow } from './table/TableRow.vue'
40
+ export { default as XTab } from './tab/Tab.vue'
41
+ export { default as XTabGroup } from './tab/TabGroup.vue'
42
+ export { default as XTag } from './tag/Tag.vue'
43
+ export { default as XTextarea } from './textarea/Textarea.vue'
44
+ export { default as XToggle } from './toggle/Toggle.vue'
45
+ export { default as XTooltip } from './tooltip/Tooltip.vue'
@@ -0,0 +1,199 @@
1
+ <script lang="ts">
2
+ import { defineComponent, ref, computed } from 'vue'
3
+ import { useCSS } from '../../composables/css'
4
+ import { useColors } from '../../composables/colors'
5
+ import { useCommon } from '../../composables/common'
6
+ import { useInputtable } from '../../composables/inputtable'
7
+ import { useInteractive } from '../../composables/interactive'
8
+
9
+ export default defineComponent({
10
+ name: 'XInput',
11
+
12
+ validators: {
13
+ ...useCommon.validators(),
14
+ },
15
+
16
+ props: {
17
+ ...useCommon.props(),
18
+ ...useInteractive.props(),
19
+ ...useInputtable.props(),
20
+ showPasswordToggle: {
21
+ type: Boolean,
22
+ default: true,
23
+ },
24
+ helper: String,
25
+ label: String,
26
+ dir: {
27
+ type: String,
28
+ default: 'ltr',
29
+ },
30
+ icon: String,
31
+ iconRight: String,
32
+ max: Number,
33
+ maxlength: Number,
34
+ min: Number,
35
+ minlength: Number,
36
+ placeholder: String,
37
+ type: {
38
+ type: String,
39
+ default: 'text',
40
+ },
41
+ inputClass: String,
42
+ },
43
+
44
+ emits: useInputtable.emits(),
45
+
46
+ setup(props, { emit }) {
47
+ const elRef = ref<HTMLInputElement>()
48
+ const currentType = ref(props.type)
49
+
50
+ const labelClasses = computed(() => {
51
+ if (props.size === 'xs') return 'text-xs'
52
+ else if (props.size === 'sm') return 'text-sm'
53
+ else if (props.size === 'lg') return 'text-lg'
54
+ else if (props.size === 'xl') return 'text-xl'
55
+
56
+ return ''
57
+ })
58
+
59
+ const sizeClasses = computed(() => {
60
+ if (props.size === 'xs') return 'px-2 py-1 text-xs'
61
+ else if (props.size === 'sm') return 'px-2 py-2 text-sm'
62
+ else if (props.size === 'lg') return 'px-4 py-3 text-lg'
63
+ else if (props.size === 'xl') return 'px-5 py-4 text-xl'
64
+
65
+ return 'px-3 py-2'
66
+ })
67
+
68
+ const css = useCSS('input')
69
+ const colors = useColors()
70
+ const color = colors.getPalette('primary')
71
+ const style = css.get('border', color[500])
72
+
73
+ function onChange(e: Event) {
74
+ if (!e.target) return
75
+
76
+ const target = (e.target as HTMLInputElement)
77
+
78
+ if (props.type === 'number') {
79
+ const value = Number(target.value)
80
+
81
+ if (typeof props.min !== 'undefined') {
82
+ if (value < props.min) target.value = props.min.toString()
83
+ }
84
+
85
+ if (typeof props.max !== 'undefined') {
86
+ if (value > props.max) target.value = props.max.toString()
87
+ }
88
+ }
89
+ }
90
+
91
+ function togglePasswordVisibility() {
92
+ currentType.value = currentType.value === 'password' ? 'text' : 'password'
93
+ }
94
+
95
+ const interactive = useInteractive(elRef)
96
+
97
+ return {
98
+ ...interactive,
99
+ ...useInputtable(props, { focus: interactive.focus, emit }),
100
+ currentType,
101
+ labelClasses,
102
+ sizeClasses,
103
+ style,
104
+ elRef,
105
+ onChange,
106
+ togglePasswordVisibility,
107
+ }
108
+ },
109
+ })
110
+ </script>
111
+
112
+ <template>
113
+ <label
114
+ class="inline-block relative align-bottom text-left"
115
+ :class="{ 'mb-3': isInsideForm }"
116
+ >
117
+ <p
118
+ v-if="label"
119
+ class="font-medium text-gray-800 dark:text-gray-200 mb-1"
120
+ :class="labelClasses"
121
+ v-text="label"
122
+ ></p>
123
+
124
+ <div class="relative">
125
+ <x-icon
126
+ v-if="icon"
127
+ :size="size"
128
+ :icon="icon"
129
+ class="text-gray-600 dark:text-gray-300 absolute ml-2 left-0 my-auto inset-y-0"
130
+ />
131
+
132
+ <input
133
+ ref="elRef"
134
+ class="appearance-none block w-full placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none transition-colors duration-150 ease-in-out border-gray-300 hover:border-gray-400 dark:border-gray-700 border shadow-sm rounded-md
135
+ focus:border-[color:var(--x-input-border)]
136
+ "
137
+ :style="style"
138
+ :class="[
139
+ sizeClasses,
140
+ disabled ? 'bg-gray-100 dark:bg-gray-700 text-gray-500 cursor-not-allowed' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200',
141
+ type === 'password' ? 'pr-9' : '',
142
+ {
143
+ '!pl-9': icon,
144
+ '!pr-9': iconRight,
145
+ // error
146
+ 'border-red-500 focus:border-red-500 dark:focus:border-red-500': errorInternal,
147
+ },
148
+ inputClass,
149
+ ]"
150
+ :disabled="disabled"
151
+ :min="min"
152
+ :max="max"
153
+ :minlength="minlength"
154
+ :maxlength="maxlength"
155
+ :dir="dir"
156
+ :name="name"
157
+ :placeholder="placeholder"
158
+ :readonly="readonly"
159
+ :type="currentType"
160
+ :value="modelValue"
161
+ v-bind="$attrs"
162
+ v-on="inputListeners"
163
+ @change="onChange"
164
+ />
165
+
166
+ <x-icon
167
+ v-if="iconRight"
168
+ :size="size"
169
+ :icon="iconRight"
170
+ class="text-gray-600 dark:text-gray-300 absolute mr-2 right-0 my-auto inset-y-0"
171
+ />
172
+
173
+ <svg
174
+ v-else-if="type === 'password' && showPasswordToggle"
175
+ width="24"
176
+ height="24"
177
+ viewBox="0 0 24 24"
178
+ stroke="currentColor"
179
+ stroke-linejoin="round"
180
+ stroke-linecap="round"
181
+ fill="none"
182
+ class="text-gray-600 dark:text-gray-300 stroke-2 w-5 h-5 absolute my-auto mr-2 inset-y-0 right-1 cursor-pointer"
183
+ @click="togglePasswordVisibility()"
184
+ >
185
+ <template v-if="currentType === 'password'">
186
+ <path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
187
+ <path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
188
+ </template>
189
+
190
+ <template v-else>
191
+ <path d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
192
+ </template>
193
+ </svg>
194
+ </div>
195
+
196
+ <p v-if="errorInternal" class="text-sm text-red-500 mt-1" v-text="errorInternal"></p>
197
+ <p v-else-if="helper" class="text-sm text-gray-500 mt-1" v-text="helper"></p>
198
+ </label>
199
+ </template>
@@ -0,0 +1,110 @@
1
+ <script lang="ts">
2
+ import { defineComponent, computed } from 'vue'
3
+ import { useColors } from '../../composables/colors'
4
+ import { useCSS } from '../../composables/css'
5
+
6
+ export default defineComponent({
7
+ name: 'XLink',
8
+
9
+ props: {
10
+ ...useColors.props(),
11
+ tag: {
12
+ type: String,
13
+ default: 'a',
14
+ },
15
+ to: [String, Object],
16
+ shadow: Boolean,
17
+ external: Boolean,
18
+ underline: Boolean,
19
+ },
20
+
21
+ setup(props) {
22
+ const css = useCSS()
23
+ const colors = useColors()
24
+ const styles = computed(() => {
25
+ const color = colors.getPalette(props.color || 'gray')
26
+
27
+ return css.variables({
28
+ text: props.color ? color[500] : '',
29
+ hover: {
30
+ text: !props.shadow ? color[600] : '',
31
+ },
32
+ shadow: color[100],
33
+ dark: {
34
+ text: props.color ? color[400] : '',
35
+ hover: {
36
+ text: color[300],
37
+ },
38
+ shadow: color[900],
39
+ },
40
+ })
41
+ })
42
+
43
+ return {
44
+ styles,
45
+ }
46
+ },
47
+ })
48
+ </script>
49
+
50
+ <template>
51
+ <component
52
+ :is="to ? 'router-link' : tag"
53
+ :to="to"
54
+ class="transition duration-300 ease-in-out cursor-pointer
55
+ text-[color:var(--x-text)]
56
+ dark:text-[color:var(--x-dark-text)]
57
+ "
58
+ :style="styles"
59
+ :class="[
60
+ [shadow ? $style['link--shadow'] : ''],
61
+ {
62
+ 'underline': underline
63
+ },
64
+ ]"
65
+ >
66
+ <span
67
+ v-if="external"
68
+ class="inline-flex items-center"
69
+ >
70
+ <slot></slot>
71
+ <svg
72
+ width="24"
73
+ height="24"
74
+ viewBox="0 0 24 24"
75
+ stroke="currentColor"
76
+ stroke-linejoin="round"
77
+ stroke-linecap="round"
78
+ fill="none"
79
+ role="presentation"
80
+ class="stroke-2 w-4 h-4 ml"
81
+ >
82
+ <line x1="7" y1="17" x2="17" y2="7" />
83
+ <polyline points="7 7 17 7 17 17" />
84
+ </svg>
85
+ </span>
86
+ <template v-else>
87
+ <slot></slot>
88
+ </template>
89
+ </component>
90
+ </template>
91
+
92
+ <style lang="postcss" module scoped>
93
+ .link {
94
+ &--shadow {
95
+ box-shadow: inset 0 -0.315em 0 0 var(--x-shadow);
96
+ &:hover {
97
+ box-shadow: inset 0 -1.125em 0 0 var(--x-shadow);
98
+ }
99
+ }
100
+
101
+ :global(.dark) & {
102
+ &--shadow {
103
+ box-shadow: inset 0 -0.315em 0 0 var(--x-dark-shadow);
104
+ &:hover {
105
+ box-shadow: inset 0 -1.125em 0 0 var(--x-dark-shadow);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ </style>