pimelon-ui 0.0.104 → 0.1.0-alpha.4

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 (92) hide show
  1. package/package.json +47 -25
  2. package/src/components/.DS_Store +0 -0
  3. package/src/components/Alert.vue +2 -2
  4. package/src/components/Autocomplete.vue +83 -62
  5. package/src/components/Avatar.stories.ts +110 -0
  6. package/src/components/Avatar.vue +115 -50
  7. package/src/components/Badge.stories.js +149 -0
  8. package/src/components/Badge.vue +80 -36
  9. package/src/components/Button.stories.js +173 -0
  10. package/src/components/Button.vue +169 -131
  11. package/src/components/Checkbox.vue +74 -0
  12. package/src/components/Dialog.vue +127 -84
  13. package/src/components/Divider.stories.ts +110 -0
  14. package/src/components/Divider.vue +67 -0
  15. package/src/components/Dropdown.stories.ts +73 -0
  16. package/src/components/Dropdown.vue +19 -9
  17. package/src/components/FormControl.vue +96 -0
  18. package/src/components/Input.vue +1 -1
  19. package/src/components/LoadingText.vue +1 -1
  20. package/src/components/Popover.vue +1 -1
  21. package/src/components/Progress.stories.js +80 -0
  22. package/src/components/Progress.vue +91 -0
  23. package/src/components/Select.vue +135 -0
  24. package/src/components/Spinner.stories.ts +13 -0
  25. package/src/components/Spinner.vue +50 -0
  26. package/src/components/Switch.stories.js +52 -0
  27. package/src/components/Switch.vue +145 -0
  28. package/src/components/TextEditor/.DS_Store +0 -0
  29. package/src/components/TextEditor/InsertImage.vue +1 -1
  30. package/src/components/TextEditor/InsertLink.vue +2 -2
  31. package/src/components/TextEditor/InsertVideo.vue +1 -1
  32. package/src/components/TextEditor/TextEditor.vue +20 -9
  33. package/src/components/TextEditor/TextEditorBubbleMenu.vue +2 -1
  34. package/src/components/TextEditor/icons/.DS_Store +0 -0
  35. package/src/components/TextInput.stories.ts +143 -0
  36. package/src/components/TextInput.vue +147 -0
  37. package/src/components/Textarea.vue +86 -0
  38. package/src/components/Tooltip.vue +4 -2
  39. package/src/components/types/TextInput.ts +14 -0
  40. package/src/fonts/Inter/Inter-Black.woff2 +0 -0
  41. package/src/fonts/Inter/Inter-BlackItalic.woff2 +0 -0
  42. package/src/fonts/Inter/Inter-Bold.woff2 +0 -0
  43. package/src/fonts/Inter/Inter-BoldItalic.woff2 +0 -0
  44. package/src/fonts/Inter/Inter-Display.woff2 +0 -0
  45. package/src/fonts/Inter/Inter-DisplayBlack.woff2 +0 -0
  46. package/src/fonts/Inter/Inter-DisplayBlackItalic.woff2 +0 -0
  47. package/src/fonts/Inter/Inter-DisplayBold.woff2 +0 -0
  48. package/src/fonts/Inter/Inter-DisplayBoldItalic.woff2 +0 -0
  49. package/src/fonts/Inter/Inter-DisplayExtraBold.woff2 +0 -0
  50. package/src/fonts/Inter/Inter-DisplayExtraBoldItalic.woff2 +0 -0
  51. package/src/fonts/Inter/Inter-DisplayExtraLight.woff2 +0 -0
  52. package/src/fonts/Inter/Inter-DisplayExtraLightItalic.woff2 +0 -0
  53. package/src/fonts/Inter/Inter-DisplayItalic.woff2 +0 -0
  54. package/src/fonts/Inter/Inter-DisplayLight.woff2 +0 -0
  55. package/src/fonts/Inter/Inter-DisplayLightItalic.woff2 +0 -0
  56. package/src/fonts/Inter/Inter-DisplayMedium.woff2 +0 -0
  57. package/src/fonts/Inter/Inter-DisplayMediumItalic.woff2 +0 -0
  58. package/src/fonts/Inter/Inter-DisplaySemiBold.woff2 +0 -0
  59. package/src/fonts/Inter/Inter-DisplaySemiBoldItalic.woff2 +0 -0
  60. package/src/fonts/Inter/Inter-DisplayThin.woff2 +0 -0
  61. package/src/fonts/Inter/Inter-DisplayThinItalic.woff2 +0 -0
  62. package/src/fonts/Inter/Inter-ExtraBold.woff2 +0 -0
  63. package/src/fonts/Inter/Inter-ExtraBoldItalic.woff2 +0 -0
  64. package/src/fonts/Inter/Inter-ExtraLight.woff2 +0 -0
  65. package/src/fonts/Inter/Inter-ExtraLightItalic.woff2 +0 -0
  66. package/src/fonts/Inter/Inter-Italic.var.woff2 +0 -0
  67. package/src/fonts/Inter/Inter-Italic.woff2 +0 -0
  68. package/src/fonts/Inter/Inter-Light.woff2 +0 -0
  69. package/src/fonts/Inter/Inter-LightItalic.woff2 +0 -0
  70. package/src/fonts/Inter/Inter-Medium.woff2 +0 -0
  71. package/src/fonts/Inter/Inter-MediumItalic.woff2 +0 -0
  72. package/src/fonts/Inter/Inter-Regular.woff2 +0 -0
  73. package/src/fonts/Inter/Inter-SemiBold.woff2 +0 -0
  74. package/src/fonts/Inter/Inter-SemiBoldItalic.woff2 +0 -0
  75. package/src/fonts/Inter/Inter-Thin.woff2 +0 -0
  76. package/src/fonts/Inter/Inter-ThinItalic.woff2 +0 -0
  77. package/src/fonts/Inter/Inter.var.woff2 +0 -0
  78. package/src/fonts/Inter/inter.css +277 -0
  79. package/src/index.js +13 -1
  80. package/src/resources/documentResource.js +3 -2
  81. package/src/resources/index.js +6 -3
  82. package/src/resources/listResource.js +36 -17
  83. package/src/resources/plugin.js +1 -16
  84. package/src/resources/realtime.js +15 -0
  85. package/src/resources/resources.js +3 -4
  86. package/src/style.css +16 -1
  87. package/src/tokens/Color.vue +194 -0
  88. package/src/utils/{debounce.js → debounce.ts} +8 -4
  89. package/src/utils/tailwind.config.js +388 -73
  90. package/src/utils/tailwind.config.stories.js +8 -0
  91. package/src/utils/useId.ts +8 -0
  92. package/src/vite-env.d.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pimelon-ui",
3
- "version": "0.0.104",
3
+ "version": "0.1.0-alpha.4",
4
4
  "description": "A set of components and utilities for rapid UI development",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
@@ -10,7 +10,12 @@
10
10
  "bump-and-release": "git pull --rebase origin main && yarn version --patch && git push && git push --tags",
11
11
  "docs:dev": "vitepress dev docs",
12
12
  "docs:build": "vitepress build docs",
13
- "docs:serve": "vitepress serve docs"
13
+ "docs:serve": "vitepress serve docs",
14
+ "dev": "vite",
15
+ "build": "vite build",
16
+ "preview": "vite preview",
17
+ "storybook": "storybook dev -p 6006",
18
+ "build-storybook": "storybook build"
14
19
  },
15
20
  "files": [
16
21
  "src"
@@ -22,45 +27,62 @@
22
27
  "author": "Alphamonak Solutions",
23
28
  "license": "MIT",
24
29
  "dependencies": {
25
- "@headlessui/vue": "^1.5.0",
30
+ "@headlessui/vue": "^1.7.14",
26
31
  "@popperjs/core": "^2.11.2",
27
32
  "@tailwindcss/forms": "^0.5.3",
28
33
  "@tailwindcss/typography": "^0.5.0",
29
- "@tiptap/extension-color": "^2.0.0-beta.205",
30
- "@tiptap/extension-highlight": "^2.0.0-beta.205",
31
- "@tiptap/extension-image": "^2.0.0-beta.205",
32
- "@tiptap/extension-link": "^2.0.0-beta.205",
33
- "@tiptap/extension-mention": "^2.0.0-beta.205",
34
- "@tiptap/extension-placeholder": "^2.0.0-beta.205",
35
- "@tiptap/extension-table": "^2.0.0-beta.205",
36
- "@tiptap/extension-table-cell": "^2.0.0-beta.205",
37
- "@tiptap/extension-table-header": "^2.0.0-beta.205",
38
- "@tiptap/extension-table-row": "^2.0.0-beta.205",
39
- "@tiptap/extension-text-align": "^2.0.0-beta.205",
40
- "@tiptap/extension-text-style": "^2.0.0-beta.205",
41
- "@tiptap/extension-typography": "^2.0.0-beta.205",
42
- "@tiptap/pm": "^2.0.0-beta.220",
43
- "@tiptap/prosemirror-tables": "^1.1.3",
44
- "@tiptap/starter-kit": "^2.0.0-beta.205",
45
- "@tiptap/suggestion": "^2.0.0-beta.205",
46
- "@tiptap/vue-3": "^2.0.0-beta.205",
47
- "autoprefixer": "^10.4.2",
34
+ "@tiptap/extension-color": "^2.0.3",
35
+ "@tiptap/extension-highlight": "^2.0.3",
36
+ "@tiptap/extension-image": "^2.0.3",
37
+ "@tiptap/extension-link": "^2.0.3",
38
+ "@tiptap/extension-mention": "^2.0.3",
39
+ "@tiptap/extension-placeholder": "^2.0.3",
40
+ "@tiptap/extension-table": "^2.0.3",
41
+ "@tiptap/extension-table-cell": "^2.0.3",
42
+ "@tiptap/extension-table-header": "^2.0.3",
43
+ "@tiptap/extension-table-row": "^2.0.3",
44
+ "@tiptap/extension-text-align": "^2.0.3",
45
+ "@tiptap/extension-text-style": "^2.0.3",
46
+ "@tiptap/extension-typography": "^2.0.3",
47
+ "@tiptap/pm": "^2.0.3",
48
+ "@tiptap/starter-kit": "^2.0.3",
49
+ "@tiptap/suggestion": "^2.0.3",
50
+ "@tiptap/vue-3": "^2.0.3",
48
51
  "feather-icons": "^4.28.0",
49
52
  "idb-keyval": "^6.2.0",
50
- "postcss": "^8.4.5",
51
53
  "showdown": "^2.1.0",
52
54
  "socket.io-client": "^4.5.1",
53
- "tailwindcss": "^3.0.12",
54
55
  "tippy.js": "^6.3.7"
55
56
  },
57
+ "peerDependencies": {
58
+ "vue": "^3.2.45",
59
+ "vue-router": "^4.1.6"
60
+ },
56
61
  "devDependencies": {
62
+ "@storybook/addon-essentials": "^7.0.0-beta.61",
63
+ "@storybook/addon-interactions": "^7.0.0-beta.61",
64
+ "@storybook/addon-links": "^7.0.0-beta.61",
65
+ "@storybook/blocks": "^7.0.0-alpha.8",
66
+ "@storybook/testing-library": "^0.0.14-next.1",
67
+ "@storybook/vue3": "^7.0.0-beta.61",
68
+ "@storybook/vue3-vite": "^7.0.0-beta.61",
69
+ "@vitejs/plugin-vue": "^4.0.0",
70
+ "autoprefixer": "^10.4.13",
57
71
  "cross-fetch": "^3.1.5",
58
72
  "husky": "^8.0.3",
59
73
  "lint-staged": ">=10",
74
+ "postcss": "^8.4.21",
60
75
  "prettier": "2.7.1",
61
76
  "prettier-plugin-tailwindcss": "^0.1.13",
77
+ "react": "^18.2.0",
78
+ "react-dom": "^18.2.0",
79
+ "storybook": "^7.0.0-beta.61",
80
+ "tailwindcss": "^3.2.7",
81
+ "typescript": "^5.0.2",
82
+ "vite": "^4.1.0",
62
83
  "vitepress": "^1.0.0-alpha.29",
63
- "vue": "^3.2.45"
84
+ "vue": "^3.2.45",
85
+ "vue-router": "^4.1.6"
64
86
  },
65
87
  "lint-staged": {
66
88
  "*.{js,css,md,vue}": "prettier --write"
Binary file
@@ -24,10 +24,10 @@
24
24
  <h3 class="text-lg font-medium text-gray-900" v-if="title">
25
25
  {{ title }}
26
26
  </h3>
27
- <div class="mt-1 md:mt-0 md:ml-2">
27
+ <div class="mt-1 md:ml-2 md:mt-0">
28
28
  <slot></slot>
29
29
  </div>
30
- <div class="mt-3 md:mt-0 md:ml-auto">
30
+ <div class="mt-3 md:ml-auto md:mt-0">
31
31
  <slot name="actions"></slot>
32
32
  </div>
33
33
  </div>
@@ -1,15 +1,15 @@
1
1
  <template>
2
2
  <Combobox v-model="selectedValue" nullable v-slot="{ open: isComboboxOpen }">
3
- <Popover class="w-full">
4
- <template #target="{ open: openPopover }">
3
+ <Popover class="w-full" v-model:show="showOptions">
4
+ <template #target="{ open: openPopover, togglePopover }">
5
5
  <div class="w-full">
6
- <ComboboxButton
7
- class="flex w-full items-center justify-between rounded-md bg-gray-100 py-1 pl-3 pr-2"
8
- :class="{ 'rounded-b-none': isComboboxOpen }"
9
- @click="() => openPopover()"
6
+ <button
7
+ class="flex h-7 w-full items-center justify-between rounded bg-gray-100 py-1 pl-3 pr-2 transition-colors hover:bg-gray-200 focus:ring-2 focus:ring-gray-400"
8
+ :class="{ 'bg-gray-200': isComboboxOpen }"
9
+ @click="() => togglePopover()"
10
10
  >
11
11
  <span
12
- class="overflow-hidden text-ellipsis text-base leading-5"
12
+ class="overflow-hidden text-ellipsis whitespace-nowrap text-base leading-5"
13
13
  v-if="selectedValue"
14
14
  >
15
15
  {{ displayValue(selectedValue) }}
@@ -22,67 +22,77 @@
22
22
  class="h-4 w-4 text-gray-500"
23
23
  aria-hidden="true"
24
24
  />
25
- </ComboboxButton>
25
+ </button>
26
26
  </div>
27
27
  </template>
28
- <template #body>
29
- <ComboboxOptions
30
- class="max-h-[15rem] overflow-y-auto rounded-md rounded-t-none bg-white px-1.5 pb-1.5 shadow-md"
31
- static
32
- v-show="isComboboxOpen"
33
- >
34
- <div
35
- class="items-st sticky top-0 mb-1.5 flex items-stretch space-x-1.5 bg-white pt-1.5"
36
- >
37
- <ComboboxInput
38
- class="form-input w-full placeholder-gray-500"
39
- type="text"
40
- @change="
41
- (e) => {
42
- query = e.target.value
43
- }
44
- "
45
- :value="query"
46
- autocomplete="off"
47
- placeholder="Search by keyword"
48
- />
49
- <Button icon="x" @click="selectedValue = null" />
50
- </div>
51
- <div
52
- v-for="group in groups"
53
- :key="group.key"
54
- v-show="group.items.length > 0"
28
+ <template #body="{ isOpen }">
29
+ <div v-show="isOpen">
30
+ <ComboboxOptions
31
+ class="mt-1 max-h-[15rem] overflow-y-auto rounded-lg bg-white px-1.5 pb-1.5 shadow-2xl"
32
+ static
55
33
  >
56
34
  <div
57
- v-if="group.group && !group.hideLabel"
58
- class="px-2 py-1 text-xs font-semibold uppercase tracking-wider text-gray-500"
35
+ class="sticky top-0 z-10 flex items-stretch space-x-1.5 bg-white pt-1.5"
59
36
  >
60
- {{ group.group }}
37
+ <div class="relative w-full">
38
+ <ComboboxInput
39
+ class="form-input w-full"
40
+ type="text"
41
+ @change="
42
+ (e) => {
43
+ query = e.target.value
44
+ }
45
+ "
46
+ :value="query"
47
+ autocomplete="off"
48
+ placeholder="Search"
49
+ />
50
+ <button
51
+ class="absolute right-0 inline-flex h-7 w-7 items-center justify-center"
52
+ @click="selectedValue = null"
53
+ >
54
+ <FeatherIcon name="x" class="w-4" />
55
+ </button>
56
+ </div>
61
57
  </div>
62
- <ComboboxOption
63
- as="template"
64
- v-for="option in group.items"
65
- :key="option.value"
66
- :value="option"
67
- v-slot="{ active, selected }"
58
+ <div
59
+ class="mt-1.5"
60
+ v-for="group in groups"
61
+ :key="group.key"
62
+ v-show="group.items.length > 0"
68
63
  >
69
- <li
70
- :class="[
71
- 'rounded-md px-2.5 py-1.5 text-base',
72
- { 'bg-gray-100': active },
73
- ]"
64
+ <div
65
+ v-if="group.group && !group.hideLabel"
66
+ class="px-2.5 py-1.5 text-sm font-medium text-gray-500"
74
67
  >
75
- {{ option.label }}
76
- </li>
77
- </ComboboxOption>
78
- </div>
79
- <li
80
- v-if="groups.length == 0"
81
- class="rounded-md px-2.5 py-1.5 text-base text-gray-600"
82
- >
83
- No results found
84
- </li>
85
- </ComboboxOptions>
68
+ {{ group.group }}
69
+ </div>
70
+ <ComboboxOption
71
+ as="template"
72
+ v-for="option in group.items"
73
+ :key="option.value"
74
+ :value="option"
75
+ v-slot="{ active, selected }"
76
+ >
77
+ <li
78
+ :class="[
79
+ 'flex items-center rounded px-2.5 py-1.5 text-base',
80
+ { 'bg-gray-100': active },
81
+ ]"
82
+ >
83
+ <slot name="prefix" v-bind="{ active, selected, option }" />
84
+ {{ option.label }}
85
+ </li>
86
+ </ComboboxOption>
87
+ </div>
88
+ <li
89
+ v-if="groups.length == 0"
90
+ class="rounded-md px-2.5 py-1.5 text-base text-gray-600"
91
+ >
92
+ No results found
93
+ </li>
94
+ </ComboboxOptions>
95
+ </div>
86
96
  </template>
87
97
  </Popover>
88
98
  </Combobox>
@@ -102,7 +112,7 @@ import FeatherIcon from './FeatherIcon.vue'
102
112
  export default {
103
113
  name: 'Autocomplete',
104
114
  props: ['modelValue', 'options', 'placeholder'],
105
- emits: ['update:modelValue', 'change'],
115
+ emits: ['update:modelValue', 'update:query', 'change'],
106
116
  components: {
107
117
  Popover,
108
118
  Button,
@@ -116,6 +126,7 @@ export default {
116
126
  data() {
117
127
  return {
118
128
  query: '',
129
+ showOptions: false,
119
130
  }
120
131
  },
121
132
  computed: {
@@ -128,6 +139,9 @@ export default {
128
139
  },
129
140
  set(val) {
130
141
  this.query = ''
142
+ if (val) {
143
+ this.showOptions = false
144
+ }
131
145
  this.$emit(this.valuePropPassed ? 'change' : 'update:modelValue', val)
132
146
  },
133
147
  },
@@ -150,6 +164,11 @@ export default {
150
164
  .filter((group) => group.items.length > 0)
151
165
  },
152
166
  },
167
+ watch: {
168
+ query(q) {
169
+ this.$emit('update:query', q)
170
+ },
171
+ },
153
172
  methods: {
154
173
  filterOptions(options) {
155
174
  if (!this.query) {
@@ -167,7 +186,9 @@ export default {
167
186
  },
168
187
  displayValue(option) {
169
188
  if (typeof option === 'string') {
170
- return option
189
+ let allOptions = this.groups.flatMap((group) => group.items)
190
+ let selectedOption = allOptions.find((o) => o.value === option)
191
+ return selectedOption?.label || option
171
192
  }
172
193
  return option?.label
173
194
  },
@@ -0,0 +1,110 @@
1
+ import { Avatar } from '../index'
2
+
3
+ const IconOnline = {
4
+ template: `<svg xmlns="http://www.w3.org/2000/svg" class="h-full w-full text-green-500" viewBox="0 0 20 20" fill="currentColor"><circle cx="10" cy="10" r="10"/></svg>`,
5
+ }
6
+
7
+ const IconOffline = {
8
+ template: `<svg xmlns="http://www.w3.org/2000/svg" class="h-full w-full text-gray-500" viewBox="0 0 20 20" fill="currentColor"><circle cx="10" cy="10" r="10"/></svg>`,
9
+ }
10
+
11
+ const IconSleep = {
12
+ template: `<svg class="h-full w-full" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
13
+ <rect width="18" height="18" rx="9" fill="white"/>
14
+ <path d="M16.9819 9.71993C16.9899 9.63114 16.8851 9.57883 16.8161 9.63536C15.7802 10.4848 14.4551 10.9946 13.0109 10.9946C9.69415 10.9946 7.00543 8.30585 7.00543 4.98914C7.00543 3.54495 7.51521 2.21982 8.36464 1.18388C8.42117 1.11495 8.36886 1.0101 8.28008 1.01809C4.19856 1.3855 1 4.8156 1 8.99276C1 13.415 4.58496 17 9.00724 17C13.1844 17 16.6145 13.8014 16.9819 9.71993Z" fill="#7C7C7C"/>
15
+ </svg>
16
+ `,
17
+ }
18
+
19
+ const IconUser = {
20
+ template: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
21
+ <path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM3.751 20.105a8.25 8.25 0 0116.498 0 .75.75 0 01-.437.695A18.683 18.683 0 0112 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 01-.437-.695z" clip-rule="evenodd" />
22
+ </svg>`,
23
+ }
24
+
25
+ export default {
26
+ component: Avatar,
27
+ tags: ['autodocs'],
28
+ render: function (args, { argTypes }) {
29
+ return {
30
+ props: Object.keys(argTypes),
31
+ components: { Avatar },
32
+ template: `<Avatar v-bind="$props">
33
+ <template v-if="$props.default" #default>
34
+ <component :is="$props.default" />
35
+ </template>
36
+ <template v-if="$props.indicator" #indicator>
37
+ <component :is="$props.indicator" />
38
+ </template>
39
+ </Avatar>`,
40
+ }
41
+ },
42
+ argTypes: {
43
+ size: {
44
+ options: ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'],
45
+ control: { type: 'inline-radio' },
46
+ },
47
+ shape: {
48
+ options: ['circle', 'square'],
49
+ control: { type: 'inline-radio' },
50
+ },
51
+ indicator: {
52
+ options: ['online', 'offline', 'sleep'],
53
+ mapping: {
54
+ online: IconOnline,
55
+ offline: IconOffline,
56
+ sleep: IconSleep,
57
+ },
58
+ },
59
+ default: {
60
+ options: ['user', 'none'],
61
+ mapping: {
62
+ none: null,
63
+ user: IconUser,
64
+ },
65
+ },
66
+ },
67
+ args: {
68
+ size: '3xl',
69
+ shape: 'circle',
70
+ },
71
+ }
72
+
73
+ export const Normal = {
74
+ args: {
75
+ image: 'https://randomuser.me/api/portraits/women/1.jpg',
76
+ },
77
+ }
78
+
79
+ export const SizeSmall = {
80
+ args: {
81
+ size: 'sm',
82
+ image: 'https://randomuser.me/api/portraits/women/2.jpg',
83
+ },
84
+ }
85
+
86
+ export const Label = {
87
+ args: {
88
+ label: 'A',
89
+ },
90
+ }
91
+
92
+ export const RoundedSquare = {
93
+ args: {
94
+ shape: 'square',
95
+ image: 'https://randomuser.me/api/portraits/women/3.jpg',
96
+ },
97
+ }
98
+
99
+ export const WithIndicator = {
100
+ args: {
101
+ indicator: 'online',
102
+ image: 'https://randomuser.me/api/portraits/women/4.jpg',
103
+ },
104
+ }
105
+
106
+ export const IconViaDefaultSlot = {
107
+ args: {
108
+ default: 'user',
109
+ },
110
+ }
@@ -1,62 +1,127 @@
1
1
  <template>
2
- <div class="shrink-0 overflow-hidden" :class="styleClasses">
2
+ <div
3
+ class="relative inline-block shrink-0"
4
+ :class="[sizeClasses, shapeClasses]"
5
+ >
3
6
  <img
4
- v-if="imageURL"
5
- :src="imageURL"
6
- class="object-cover"
7
- :class="styleClasses"
8
- loading="lazy"
7
+ v-if="image"
8
+ :src="image"
9
+ :class="[shapeClasses, 'h-full w-full object-cover']"
9
10
  />
10
11
  <div
11
12
  v-else
12
- class="flex h-full w-full items-center justify-center bg-gray-200 uppercase text-gray-600"
13
- :class="{ sm: 'text-xs', md: 'text-base', lg: 'text-lg' }[size]"
13
+ class="flex h-full w-full items-center justify-center bg-gray-100 uppercase text-gray-600"
14
+ :class="[labelClasses, shapeClasses]"
14
15
  >
15
- {{ label && label[0] }}
16
+ <div :class="iconClasses" v-if="$slots.default">
17
+ <slot></slot>
18
+ </div>
19
+ <template v-else>
20
+ {{ label && label[0] }}
21
+ </template>
22
+ </div>
23
+ <div
24
+ v-if="$slots.indicator"
25
+ :class="[
26
+ 'absolute bottom-0 right-0 grid place-items-center rounded-full bg-white',
27
+ indicatorContainerClasses,
28
+ ]"
29
+ >
30
+ <div :class="indicatorClasses">
31
+ <slot name="indicator"></slot>
32
+ </div>
16
33
  </div>
17
34
  </div>
18
35
  </template>
19
36
 
20
- <script>
21
- const validShapes = ['square', 'circle']
22
-
23
- export default {
24
- name: 'Avatar',
25
- props: {
26
- imageURL: String,
27
- label: String,
28
- size: {
29
- default: 'md',
30
- },
31
- shape: {
32
- default: 'circle',
33
- validator(value) {
34
- const valid = validShapes.includes(value)
35
- if (!valid) {
36
- console.warn(
37
- `shape property for <Avatar /> must be one of `,
38
- validShapes
39
- )
40
- }
41
- return valid
42
- },
43
- },
44
- },
45
- computed: {
46
- styleClasses() {
47
- const sizeClasses = {
48
- sm: 'w-5 h-5',
49
- md: 'w-8 h-8',
50
- lg: 'w-12 h-12',
51
- }[this.size]
52
-
53
- const shapeClass = {
54
- circle: 'rounded-full',
55
- square: 'rounded-lg',
56
- }[this.shape]
57
-
58
- return `${shapeClass} ${sizeClasses}`
59
- },
60
- },
37
+ <script setup lang="ts">
38
+ import { computed } from 'vue'
39
+
40
+ interface AvatarProps {
41
+ image?: string
42
+ label?: string
43
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'
44
+ shape?: 'circle' | 'square'
61
45
  }
46
+
47
+ const props = withDefaults(defineProps<AvatarProps>(), {
48
+ size: 'md',
49
+ shape: 'circle',
50
+ })
51
+
52
+ const shapeClasses = computed(() => {
53
+ return {
54
+ circle: 'rounded-full',
55
+ square: {
56
+ xs: 'rounded-[4px]',
57
+ sm: 'rounded-[5px]',
58
+ md: 'rounded-[5px]',
59
+ lg: 'rounded-[6px]',
60
+ xl: 'rounded-[6px]',
61
+ '2xl': 'rounded-[8px]',
62
+ '3xl': 'rounded-[10px]',
63
+ }[props.size],
64
+ }[props.shape]
65
+ })
66
+
67
+ const sizeClasses = computed(() => {
68
+ return {
69
+ xs: 'w-4 h-4',
70
+ sm: 'w-5 h-5',
71
+ md: 'w-6 h-6',
72
+ lg: 'w-7 h-7',
73
+ xl: 'w-8 h-8',
74
+ '2xl': 'w-10 h-10',
75
+ '3xl': 'w-11.5 h-11.5',
76
+ }[props.size]
77
+ })
78
+
79
+ const labelClasses = computed(() => {
80
+ let sizeClass = {
81
+ xs: 'text-2xs',
82
+ sm: 'text-sm',
83
+ md: 'text-base',
84
+ lg: 'text-base',
85
+ xl: 'text-lg',
86
+ '2xl': 'text-xl',
87
+ '3xl': 'text-2xl',
88
+ }[props.size]
89
+ return ['font-medium', sizeClass]
90
+ })
91
+
92
+ const indicatorContainerClasses = computed(() => {
93
+ return {
94
+ xs: '-mr-[.1rem] -mb-[.1rem] h-2 w-2',
95
+ sm: '-mr-[.1rem] -mb-[.1rem] h-[9px] w-[9px]',
96
+ md: '-mr-[.1rem] -mb-[.1rem] h-2.5 w-2.5',
97
+ lg: '-mr-[.1rem] -mb-[.1rem] h-3 w-3',
98
+ xl: '-mr-[.1rem] -mb-[.1rem] h-3 w-3',
99
+ '2xl': '-mr-[.1rem] -mb-[.1rem] h-3.5 w-3.5',
100
+ '3xl': '-mr-[.2rem] -mb-[.2rem] h-4 w-4',
101
+ }[props.size]
102
+ })
103
+
104
+ const indicatorClasses = computed(() => {
105
+ return {
106
+ xs: 'h-1 w-1',
107
+ sm: 'h-[5px] w-[5px]',
108
+ md: 'h-1.5 w-1.5',
109
+ lg: 'h-2 w-2',
110
+ xl: 'h-2 w-2',
111
+ '2xl': 'h-2.5 w-2.5',
112
+ '3xl': 'h-3 w-3',
113
+ }[props.size]
114
+ })
115
+
116
+ const iconClasses = computed(() => {
117
+ return {
118
+ xs: 'h-2.5 w-2.5',
119
+ sm: 'h-3 w-3',
120
+ md: 'h-4 w-4',
121
+ lg: 'h-4 w-4',
122
+ xl: 'h-4 w-4',
123
+ '2xl': 'h-5 w-5',
124
+ '3xl': 'h-5 w-5',
125
+ }[props.size]
126
+ })
62
127
  </script>