pimelon-ui 0.1.70 → 0.1.72

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.
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "pimelon-ui",
3
- "version": "0.1.70",
3
+ "version": "0.1.72",
4
4
  "description": "A set of components and utilities for rapid UI development",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
7
7
  "test": "npx prettier --check ./src",
8
8
  "prettier": "npx prettier -w ./src",
9
- "prepare": "husky install",
10
9
  "bump-and-release": "git pull --rebase origin main && yarn version --patch && git push && git push --tags",
11
10
  "dev": "vite",
12
11
  "build": "vite build",
@@ -49,6 +48,7 @@
49
48
  "@tiptap/vue-3": "^2.0.3",
50
49
  "@vueuse/core": "^10.4.1",
51
50
  "feather-icons": "^4.28.0",
51
+ "husky": "^9.1.6",
52
52
  "idb-keyval": "^6.2.0",
53
53
  "prettier": "^3.3.2",
54
54
  "radix-vue": "^1.5.3",
@@ -66,10 +66,8 @@
66
66
  "autoprefixer": "^10.4.13",
67
67
  "cross-fetch": "^3.1.5",
68
68
  "histoire": "^0.17.14",
69
- "husky": "^9.1.6",
70
69
  "lint-staged": ">=10",
71
70
  "postcss": "^8.4.21",
72
- "prettier": "3.3.2",
73
71
  "prettier-plugin-tailwindcss": "^0.1.13",
74
72
  "tailwindcss": "^3.2.7",
75
73
  "typescript": "^5.0.2",
@@ -5,7 +5,7 @@
5
5
  nullable
6
6
  v-slot="{ open: isComboboxOpen }"
7
7
  >
8
- <Popover class="w-full" v-model:show="showOptions">
8
+ <Popover class="w-full" v-model:show="showOptions" ref="rootRef">
9
9
  <template #target="{ open: openPopover, togglePopover }">
10
10
  <slot name="target" v-bind="{ open: openPopover, togglePopover }">
11
11
  <div class="w-full">
@@ -179,14 +179,29 @@ import FeatherIcon from './FeatherIcon.vue'
179
179
 
180
180
  export default {
181
181
  name: 'Autocomplete',
182
- props: [
183
- 'modelValue',
184
- 'options',
185
- 'placeholder',
186
- 'bodyClasses',
187
- 'multiple',
188
- 'hideSearch',
189
- ],
182
+ props: {
183
+ options: {
184
+ type: Array,
185
+ default: () => [],
186
+ },
187
+ modelValue: {
188
+ type: [String, Object, Array],
189
+ },
190
+ placeholder: {
191
+ type: String,
192
+ },
193
+ bodyClasses: {
194
+ type: [String, Array, Object],
195
+ },
196
+ multiple: {
197
+ type: Boolean,
198
+ default: false,
199
+ },
200
+ hideSearch: {
201
+ type: Boolean,
202
+ default: false,
203
+ },
204
+ },
190
205
  emits: ['update:modelValue', 'update:query', 'change'],
191
206
  components: {
192
207
  Popover,
@@ -198,7 +213,7 @@ export default {
198
213
  ComboboxOption,
199
214
  ComboboxButton,
200
215
  },
201
- expose: ['togglePopover'],
216
+ expose: ['togglePopover', 'rootRef'],
202
217
  data() {
203
218
  return {
204
219
  query: '',
@@ -262,6 +277,9 @@ export default {
262
277
  },
263
278
  },
264
279
  methods: {
280
+ rootRef() {
281
+ return this.$refs['rootRef']
282
+ },
265
283
  togglePopover(val) {
266
284
  this.showOptions = val ?? !this.showOptions
267
285
  },
@@ -41,35 +41,37 @@ const props = withDefaults(defineProps<BadgeProps>(), {
41
41
 
42
42
  const classes = computed(() => {
43
43
  let solidClasses = {
44
- gray: 'text-white bg-gray-900',
45
- blue: 'text-white bg-blue-500',
46
- green: 'text-white bg-green-600',
47
- orange: 'text-white bg-amber-600',
48
- red: 'text-white bg-red-600',
44
+ gray: 'text-text-icons-white bg-surface-gray-7',
45
+ blue: 'text-text-icons-blue-1 bg-surface-blue-2',
46
+ green: 'text-text-icons-green-1 bg-surface-green-3',
47
+ orange: 'text-text-icons-amber-1 bg-surface-amber-2',
48
+ red: 'text-text-icons-red-1 bg-surface-red-4',
49
49
  }[props.theme]
50
50
 
51
51
  let subtleClasses = {
52
- gray: 'text-gray-700 bg-gray-100',
53
- blue: 'text-blue-600 bg-blue-100',
54
- green: 'text-green-800 bg-green-200',
55
- orange: 'text-amber-700 bg-amber-100',
56
- red: 'text-red-600 bg-red-100',
52
+ gray: 'text-text-icons-gray-6 bg-surface-gray-2',
53
+ blue: 'text-text-icons-blue-2 bg-surface-blue-1',
54
+ green: 'text-text-icons-green-3 bg-surface-green-2',
55
+ orange: 'text-text-icons-amber-3 bg-surface-amber-1',
56
+ red: 'text-text-icons-red-4 bg-surface-red-1',
57
57
  }[props.theme]
58
58
 
59
59
  let outlineClasses = {
60
- gray: 'text-gray-700 bg-white border border-gray-300',
61
- blue: 'text-blue-600 bg-white border border-blue-300',
62
- green: 'text-green-800 bg-white border border-green-300',
63
- orange: 'text-amber-700 bg-white border border-amber-300',
64
- red: 'text-red-600 bg-white border border-red-300',
60
+ gray: 'text-text-icons-gray-6 bg-transparent border border-outline-gray-1',
61
+ blue: 'text-text-icons-blue-2 bg-transparent border border-outline-blue-1',
62
+ green:
63
+ 'text-text-icons-green-3 bg-transparent border border-outline-green-2',
64
+ orange:
65
+ 'text-text-icons-amber-3 bg-transparent border border-outline-amber-2',
66
+ red: 'text-text-icons-red-4 bg-transparent border border-outline-red-2',
65
67
  }[props.theme]
66
68
 
67
69
  let ghostClasses = {
68
- gray: 'text-gray-700 bg-transparent',
69
- blue: 'text-blue-600 bg-transparent',
70
- green: 'text-green-800 bg-transparent',
71
- orange: 'text-amber-700 bg-transparent',
72
- red: 'text-red-600 bg-transparent',
70
+ gray: 'text-text-icons-gray-6 bg-transparent',
71
+ blue: 'text-text-icons-blue-2 bg-transparent',
72
+ green: 'text-text-icons-green-3 bg-transparent',
73
+ orange: 'text-text-icons-amber-3 bg-transparent',
74
+ red: 'text-text-icons-red-4 bg-transparent',
73
75
  }[props.theme]
74
76
 
75
77
  let variantClasses = {
@@ -133,6 +133,7 @@ const defaultConfig = {
133
133
  redundantCellHeight: 50,
134
134
  hourHeight: 50,
135
135
  enableShortcuts: true,
136
+ showIcon: true,
136
137
  }
137
138
 
138
139
  const overrideConfig = { ...defaultConfig, ...props.config }
@@ -26,12 +26,14 @@
26
26
  ]
27
27
  "
28
28
  >
29
- <component
30
- v-if="eventIcons[props.event.type]"
31
- :is="eventIcons[props.event.type]"
32
- class="h-4 w-4 text-black"
33
- />
34
- <FeatherIcon v-else name="circle" class="h-4 text-black" />
29
+ <div v-if="config.showIcon">
30
+ <component
31
+ v-if="eventIcons[props.event.type]"
32
+ :is="eventIcons[props.event.type]"
33
+ class="h-4 w-4 text-black"
34
+ />
35
+ <FeatherIcon v-else name="circle" class="h-4 text-black" />
36
+ </div>
35
37
 
36
38
  <div class="flex w-fit flex-col overflow-hidden whitespace-nowrap">
37
39
  <p class="text-ellipsis text-sm font-medium text-gray-800">
@@ -72,12 +74,14 @@
72
74
  ]
73
75
  "
74
76
  >
75
- <component
76
- v-if="eventIcons[props.event.type]"
77
- :is="eventIcons[props.event.type]"
78
- class="h-4 w-4 text-black"
79
- />
80
- <FeatherIcon v-else name="circle" class="h-4 text-black" />
77
+ <div v-if="config.showIcon">
78
+ <component
79
+ v-if="eventIcons[props.event.type]"
80
+ :is="eventIcons[props.event.type]"
81
+ class="h-4 w-4 text-black"
82
+ />
83
+ <FeatherIcon v-else name="circle" class="h-4 text-black" />
84
+ </div>
81
85
 
82
86
  <div class="flex w-fit flex-col overflow-hidden whitespace-nowrap">
83
87
  <p class="text-ellipsis text-sm font-medium text-gray-800">
@@ -65,6 +65,7 @@ The object for this kind of event looks like:
65
65
  redundantCellHeight: 50,
66
66
  hourHeight: 50,
67
67
  enableShortcuts: true,
68
+ showIcon: true,
68
69
 
69
70
  }
70
71
 
@@ -122,6 +123,10 @@ e.g.
122
123
  - Delete: When an event is focused you can press the delete button to delete
123
124
  the event.
124
125
 
126
+ - `showIcon`: Boolean value which determines whether the icon will be displayed
127
+ or not in the Event. By default the value is true i.e. icon will be displayed,
128
+ can be disabled by setting it to false.
129
+
125
130
  - Many functional props are also there which will be discussed in the below
126
131
  sections.
127
132
 
@@ -73,7 +73,7 @@ const config = {
73
73
  eventIcons: {},
74
74
  allowCustomClickEvents: true,
75
75
  redundantCellHeight: 100,
76
- enableShortcuts: true,
76
+ enableShortcuts: false,
77
77
  }
78
78
 
79
79
  const events = ref([
@@ -82,17 +82,17 @@ const events = ref([
82
82
  participant: 'Ryan Mathew',
83
83
  id: 'EDU-CSH-2024-00091',
84
84
  venue: 'CNF-ROOM-2024-00001',
85
- fromDate: '2024-07-08 16:30:00', //can be a date object
86
- toDate: '2024-07-08 17:30:00',
87
- color: 'green',
85
+ fromDate: '2024-10-08 16:30:00', //can be a date object
86
+ toDate: '2024-10-08 17:30:00',
87
+ color: 'violet',
88
88
  },
89
89
  {
90
90
  title: 'English by Ryan Mathew',
91
91
  participant: 'Ryan Mathew',
92
92
  id: 'EDU-CSH-2024-00092',
93
93
  venue: 'CNF-ROOM-2024-00002',
94
- fromDate: '2024-07-08 13:30:00',
95
- toDate: '2024-07-08 17:30:00',
94
+ fromDate: '2024-10-08 13:30:00',
95
+ toDate: '2024-10-08 17:30:00',
96
96
  color: 'green',
97
97
  },
98
98
  {
@@ -100,26 +100,26 @@ const events = ref([
100
100
  participant: 'Sheldon',
101
101
  id: 'EDU-CSH-2024-00093',
102
102
  venue: 'CNF-ROOM-2024-00001',
103
- fromDate: '2024-07-09 10:30:00',
104
- toDate: '2024-07-09 11:30:00',
105
- color: 'green',
103
+ fromDate: '2024-10-09 10:30:00',
104
+ toDate: '2024-10-09 11:30:00',
105
+ color: 'blue',
106
106
  },
107
107
  {
108
108
  title: 'English by Ryan Mathew',
109
109
  participant: 'Ryan Mathew',
110
110
  id: 'EDU-CSH-2024-00094',
111
111
  venue: 'CNF-ROOM-2024-00001',
112
- fromDate: '2024-07-17 16:30:00',
113
- toDate: '2024-07-17 17:30:00',
114
- color: 'green',
112
+ fromDate: '2024-10-17 16:30:00',
113
+ toDate: '2024-10-17 17:30:00',
114
+ color: 'red',
115
115
  },
116
116
  {
117
117
  title: 'Google Meet with John ',
118
118
  participant: 'John',
119
119
  id: '#htrht41',
120
120
  venue: 'Google Meet',
121
- fromDate: '2024-07-21 00:00:00',
122
- toDate: '2024-07-21 23:59:59',
121
+ fromDate: '2024-10-21 00:00:00',
122
+ toDate: '2024-10-21 23:59:59',
123
123
  color: 'amber',
124
124
  isFullDay: true,
125
125
  },
@@ -128,8 +128,8 @@ const events = ref([
128
128
  participant: 'Sheldon',
129
129
  id: '#htrht42',
130
130
  venue: 'Google Meet',
131
- fromDate: '2024-07-21 00:00:00',
132
- toDate: '2024-07-21 23:59:59',
131
+ fromDate: '2024-10-21 00:00:00',
132
+ toDate: '2024-10-21 23:59:59',
133
133
  color: 'amber',
134
134
  isFullDay: true,
135
135
  },
@@ -29,7 +29,17 @@
29
29
  import LoadingText from './LoadingText.vue'
30
30
  export default {
31
31
  name: 'Card',
32
- props: ['title', 'subtitle', 'loading'],
32
+ props: {
33
+ title: {
34
+ type: String,
35
+ },
36
+ subtitle: {
37
+ type: String,
38
+ },
39
+ loading: {
40
+ type: Boolean,
41
+ },
42
+ },
33
43
  components: {
34
44
  LoadingText,
35
45
  },
@@ -11,12 +11,13 @@
11
11
  <p v-if="!showPercentage">{{ step }}</p>
12
12
  <p v-else>{{ progress.toFixed(0) }}%</p>
13
13
  </div>
14
- <div v-else class="check-icon" />
14
+ <FeatherIcon v-else name="check" class="check-icon" />
15
15
  </div>
16
16
  </template>
17
17
 
18
18
  <script setup lang="ts">
19
19
  import { computed } from 'vue'
20
+ import FeatherIcon from './FeatherIcon.vue'
20
21
 
21
22
  interface Props {
22
23
  step: number
@@ -45,6 +46,7 @@ interface SizeProps {
45
46
  ringSize: string
46
47
  ringBarWidth: string
47
48
  innerTextFontSize: string
49
+ checkIconSize: string
48
50
  }
49
51
 
50
52
  // predefined sizes for the circular progress bar
@@ -53,26 +55,31 @@ const sizeMap: Record<Size, SizeProps> = {
53
55
  ringSize: '30px',
54
56
  ringBarWidth: '6px',
55
57
  innerTextFontSize: props.showPercentage ? '8px' : '12px',
58
+ checkIconSize: '16px',
56
59
  },
57
60
  sm: {
58
61
  ringSize: '42px',
59
62
  ringBarWidth: '10px',
60
63
  innerTextFontSize: props.showPercentage ? '12px' : '16px',
64
+ checkIconSize: '20px',
61
65
  },
62
66
  md: {
63
67
  ringSize: '60px',
64
68
  ringBarWidth: '14px',
65
69
  innerTextFontSize: props.showPercentage ? '16px' : '20px',
70
+ checkIconSize: '24px',
66
71
  },
67
72
  lg: {
68
73
  ringSize: '84px',
69
74
  ringBarWidth: '18px',
70
75
  innerTextFontSize: props.showPercentage ? '20px' : '24px',
76
+ checkIconSize: '40px',
71
77
  },
72
78
  xl: {
73
79
  ringSize: '108px',
74
80
  ringBarWidth: '22px',
75
81
  innerTextFontSize: props.showPercentage ? '24px' : '28px',
82
+ checkIconSize: '48px',
76
83
  },
77
84
  }
78
85
 
@@ -123,6 +130,7 @@ const isCompleted = computed(() => props.step === props.totalSteps)
123
130
  --size: v-bind(size.ringSize);
124
131
  --bar-width: v-bind(size.ringBarWidth);
125
132
  --font-size: v-bind(size.innerTextFontSize);
133
+ --check-icon-size: v-bind(size.checkIconSize);
126
134
  --color-progress: v-bind(theme.primary);
127
135
  --color-remaining-circle: v-bind(theme.secondary);
128
136
  --color-complete: v-bind($props.themeComplete);
@@ -180,11 +188,8 @@ const isCompleted = computed(() => props.step === props.totalSteps)
180
188
  }
181
189
 
182
190
  .check-icon {
183
- width: 15px;
184
- height: 15px;
185
- background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODUiIGhlaWdodD0iODUiIHZpZXdCb3g9IjUgMzAgNzUgMTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0zNS40MjM3IDUzLjczMjdMNjcuOTc4NyAyMS4xNzc3TDcyLjk4OTUgMjYuMTg0MkwzNS40MTk1IDYzLjc1TDEyLjg4NiA0MS4yMTIyTDE3Ljg5MjUgMzYuMjAxNUwzNS40MjM3IDUzLjczMjdaIiBmaWxsPSIjMWYxYTM4Ii8+Cjwvc3ZnPgo=');
186
- background-size: contain;
187
- background-repeat: no-repeat;
188
- background-position: center;
191
+ z-index: 3;
192
+ width: var(--check-icon-size);
193
+ height: var(--check-icon-size);
189
194
  }
190
195
  </style>
@@ -12,12 +12,26 @@
12
12
  </template>
13
13
  <script>
14
14
  import Dialog from './Dialog.vue'
15
+ import Button from './Button.vue'
16
+
15
17
  export default {
16
18
  name: 'ConfirmDialog',
17
- props: ['title', 'message', 'fields', 'onConfirm'],
19
+ props: {
20
+ title: {
21
+ type: String,
22
+ },
23
+ message: {
24
+ type: String,
25
+ },
26
+ onConfirm: {
27
+ type: Function,
28
+ default: null,
29
+ },
30
+ },
18
31
  expose: ['show', 'hide'],
19
32
  components: {
20
33
  Dialog,
34
+ Button,
21
35
  },
22
36
  data() {
23
37
  return {
@@ -118,14 +118,27 @@ import FeatherIcon from './FeatherIcon.vue'
118
118
  import TextInput from './TextInput.vue'
119
119
  export default {
120
120
  name: 'DatePicker',
121
- props: [
122
- 'value',
123
- 'modelValue',
124
- 'placeholder',
125
- 'formatter',
126
- 'readonly',
127
- 'inputClass',
128
- ],
121
+ props: {
122
+ value: {
123
+ type: String,
124
+ },
125
+ modelValue: {
126
+ type: String,
127
+ },
128
+ placeholder: {
129
+ type: String,
130
+ },
131
+ formatter: {
132
+ type: Function,
133
+ default: null,
134
+ },
135
+ readonly: {
136
+ type: Boolean,
137
+ },
138
+ inputClass: {
139
+ type: [String, Array, Object],
140
+ },
141
+ },
129
142
  emits: ['update:modelValue', 'change'],
130
143
  components: {
131
144
  Popover,
@@ -107,14 +107,27 @@ import FeatherIcon from './FeatherIcon.vue'
107
107
  import TextInput from './TextInput.vue'
108
108
  export default {
109
109
  name: 'DateRangePicker',
110
- props: [
111
- 'value',
112
- 'modelValue',
113
- 'placeholder',
114
- 'formatter',
115
- 'readonly',
116
- 'inputClass',
117
- ],
110
+ props: {
111
+ value: {
112
+ type: String,
113
+ },
114
+ modelValue: {
115
+ type: String,
116
+ },
117
+ placeholder: {
118
+ type: String,
119
+ },
120
+ formatter: {
121
+ type: Function,
122
+ default: null,
123
+ },
124
+ readonly: {
125
+ type: Boolean,
126
+ },
127
+ inputClass: {
128
+ type: [String, Array, Object],
129
+ },
130
+ },
118
131
  emits: ['update:modelValue', 'change'],
119
132
  components: {
120
133
  Popover,
@@ -157,14 +157,27 @@ import FeatherIcon from './FeatherIcon.vue'
157
157
  import TextInput from './TextInput.vue'
158
158
  export default {
159
159
  name: 'DateTimePicker',
160
- props: [
161
- 'value',
162
- 'modelValue',
163
- 'placeholder',
164
- 'formatter',
165
- 'readonly',
166
- 'inputClass',
167
- ],
160
+ props: {
161
+ value: {
162
+ type: String,
163
+ },
164
+ modelValue: {
165
+ type: String,
166
+ },
167
+ placeholder: {
168
+ type: String,
169
+ },
170
+ formatter: {
171
+ type: Function,
172
+ default: null,
173
+ },
174
+ readonly: {
175
+ type: Boolean,
176
+ },
177
+ inputClass: {
178
+ type: [String, Array, Object],
179
+ },
180
+ },
168
181
  emits: ['update:modelValue', 'change'],
169
182
  components: {
170
183
  Popover,
@@ -26,7 +26,7 @@
26
26
  <div v-for="group in groups" :key="group.key" class="p-1.5">
27
27
  <div
28
28
  v-if="group.group && !group.hideLabel"
29
- class="flex h-7 items-center px-2 text-sm font-medium text-gray-500"
29
+ class="flex h-7 items-center px-2 text-sm font-medium text-text-icons-gray-6"
30
30
  >
31
31
  {{ group.group }}
32
32
  </div>
@@ -43,7 +43,7 @@
43
43
  <button
44
44
  v-else
45
45
  :class="[
46
- active ? 'bg-gray-100' : 'text-gray-800',
46
+ active ? 'bg-gray-100' : 'text-text-icons-gray-6',
47
47
  'group flex h-7 w-full items-center rounded px-2 text-base',
48
48
  ]"
49
49
  @click="item.onClick"
@@ -51,11 +51,11 @@
51
51
  <FeatherIcon
52
52
  v-if="item.icon && typeof item.icon === 'string'"
53
53
  :name="item.icon"
54
- class="mr-2 h-4 w-4 flex-shrink-0 text-gray-700"
54
+ class="mr-2 h-4 w-4 flex-shrink-0 text-text-icons-gray-6"
55
55
  aria-hidden="true"
56
56
  />
57
57
  <component
58
- class="mr-2 h-4 w-4 flex-shrink-0 text-gray-700"
58
+ class="mr-2 h-4 w-4 flex-shrink-0 text-text-icons-gray-6"
59
59
  v-else-if="item.icon"
60
60
  :is="item.icon"
61
61
  />
@@ -71,90 +71,98 @@
71
71
  </Menu>
72
72
  </template>
73
73
 
74
- <script>
74
+ <script setup lang="ts">
75
75
  import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
76
76
  import Popover from './Popover.vue'
77
77
  import Button from './Button.vue'
78
78
  import FeatherIcon from './FeatherIcon.vue'
79
+ import { computed } from 'vue'
80
+ import { useRouter } from 'vue-router'
79
81
 
80
- export default {
81
- name: 'Dropdown',
82
- props: {
83
- button: {
84
- type: Object,
85
- default: null,
86
- },
87
- options: {
88
- type: Array,
89
- default: () => [],
90
- },
91
- placement: {
92
- type: String,
93
- default: 'left',
94
- },
95
- },
96
- components: {
97
- Menu,
98
- MenuButton,
99
- MenuItems,
100
- MenuItem,
101
- Button,
102
- FeatherIcon,
103
- Popover,
104
- },
105
- methods: {
106
- normalizeDropdownItem(option) {
107
- let onClick = option.onClick || null
108
- if (!onClick && option.route && this.$router) {
109
- onClick = () => this.$router.push(option.route)
110
- }
82
+ const router = useRouter()
111
83
 
112
- return {
113
- label: option.label,
114
- icon: option.icon,
115
- group: option.group,
116
- component: option.component,
117
- onClick,
118
- }
119
- },
120
- filterOptions(options) {
121
- return (options || [])
122
- .filter(Boolean)
123
- .filter((option) => (option.condition ? option.condition() : true))
124
- .map((option) => this.normalizeDropdownItem(option))
125
- },
126
- },
127
- computed: {
128
- groups() {
129
- let groups = this.options[0]?.group
130
- ? this.options
131
- : [{ group: '', items: this.options }]
84
+ type DropdownOption = {
85
+ label: string
86
+ icon?: string
87
+ group?: string
88
+ component?: any
89
+ onClick?: () => void
90
+ route?: string
91
+ condition?: () => boolean
92
+ }
93
+
94
+ type DropdownOptions = string | DropdownOption
95
+
96
+ type ButtonProps = {
97
+ label: string
98
+ icon?: string
99
+ }
100
+
101
+ interface DropdownProps {
102
+ button?: ButtonProps
103
+ options?: DropdownOptions[]
104
+ placement?: string
105
+ }
106
+
107
+ const props = withDefaults(defineProps<DropdownProps>(), {
108
+ options: () => [],
109
+ placement: 'left',
110
+ })
132
111
 
133
- return groups.map((group, i) => {
134
- return {
135
- key: i,
136
- group: group.group,
137
- hideLabel: group.hideLabel || false,
138
- items: this.filterOptions(group.items),
139
- }
140
- })
141
- },
142
- dropdownTransition() {
143
- return {
144
- enterActiveClass: 'transition duration-100 ease-out',
145
- enterFromClass: 'transform scale-95 opacity-0',
146
- enterToClass: 'transform scale-100 opacity-100',
147
- leaveActiveClass: 'transition duration-75 ease-in',
148
- leaveFromClass: 'transform scale-100 opacity-100',
149
- leaveToClass: 'transform scale-95 opacity-0',
150
- }
151
- },
152
- popoverPlacement() {
153
- if (this.placement === 'left') return 'bottom-start'
154
- if (this.placement === 'right') return 'bottom-end'
155
- if (this.placement === 'center') return 'bottom-center'
156
- return 'bottom'
157
- },
158
- },
112
+ const normalizeDropdownItem = (option: DropdownOption) => {
113
+ let onClick = option.onClick || null
114
+ if (!onClick && option.route && router) {
115
+ onClick = () => router.push(option.route!)
116
+ }
117
+
118
+ return {
119
+ label: option.label,
120
+ icon: option.icon,
121
+ group: option.group,
122
+ component: option.component,
123
+ onClick,
124
+ }
125
+ }
126
+
127
+ const filterOptions = (options: DropdownOptions) => {
128
+ return (options || [])
129
+ .filter(Boolean)
130
+ .filter((option: DropdownOption) =>
131
+ option.condition ? option.condition() : true,
132
+ )
133
+ .map((option: DropdownOption) => normalizeDropdownItem(option))
159
134
  }
135
+
136
+ const groups = computed(() => {
137
+ let groups = props.options[0]?.group
138
+ ? props.options
139
+ : [{ group: '', items: props.options }]
140
+
141
+ return groups.map((group, i) => {
142
+ return {
143
+ key: i,
144
+ group: group.group,
145
+ hideLabel: group.hideLabel || false,
146
+ items: filterOptions(group.items),
147
+ }
148
+ })
149
+ })
150
+
151
+ const dropdownTransition = computed(() => {
152
+ return {
153
+ enterActiveClass: 'transition duration-100 ease-out',
154
+ enterFromClass: 'transform scale-95 opacity-0',
155
+ enterToClass: 'transform scale-100 opacity-100',
156
+ leaveActiveClass: 'transition duration-75 ease-in',
157
+ leaveFromClass: 'transform scale-100 opacity-100',
158
+ leaveToClass: 'transform scale-95 opacity-0',
159
+ }
160
+ })
161
+
162
+ const popoverPlacement = computed(() => {
163
+ if (props.placement === 'left') return 'bottom-start'
164
+ if (props.placement === 'right') return 'bottom-end'
165
+ if (props.placement === 'center') return 'bottom-center'
166
+ return 'bottom'
167
+ })
160
168
  </script>
@@ -28,7 +28,18 @@ import FileUploadHandler from '../utils/fileUploadHandler'
28
28
 
29
29
  export default {
30
30
  name: 'FileUploader',
31
- props: ['fileTypes', 'uploadArgs', 'validateFile'],
31
+ props: {
32
+ fileTypes: {
33
+ type: [String, Array],
34
+ },
35
+ uploadArgs: {
36
+ type: Object,
37
+ },
38
+ validateFile: {
39
+ type: Function,
40
+ default: null,
41
+ },
42
+ },
32
43
  data() {
33
44
  return {
34
45
  uploader: null,
@@ -2,6 +2,7 @@
2
2
  <div v-if="type != 'checkbox'" :class="['space-y-1.5', attrs.class]">
3
3
  <label class="block" :class="labelClasses" v-if="label" :for="id">
4
4
  {{ label }}
5
+ <span class="text-red-500" v-if="required">*</span>
5
6
  </label>
6
7
  <Select
7
8
  v-if="type === 'select'"
@@ -61,6 +62,7 @@ interface FormControlProps {
61
62
  description?: string
62
63
  type?: TextInputTypes | 'textarea' | 'select' | 'checkbox' | 'autocomplete'
63
64
  size?: 'sm' | 'md'
65
+ required?: boolean
64
66
  }
65
67
 
66
68
  const id = useId()
@@ -0,0 +1,18 @@
1
+ <template>
2
+ <Story :layout="{ type: 'grid', width: 300 }">
3
+ <Variant title="default">
4
+ <div class="p-2">
5
+ <Rating v-bind="state" />
6
+ </div>
7
+ </Variant>
8
+ </Story>
9
+ </template>
10
+ <script setup lang="ts">
11
+ import { reactive } from 'vue'
12
+ import Rating from './Rating.vue'
13
+
14
+ const state = reactive({
15
+ size: 'md',
16
+ label: 'Rating',
17
+ })
18
+ </script>
@@ -0,0 +1,85 @@
1
+ <template>
2
+ <div class="space-y-1">
3
+ <label class="block text-xs text-text-icons-gray-5" v-if="label">
4
+ {{ label }}
5
+ </label>
6
+ <div class="flex text-center">
7
+ <div
8
+ v-for="index in rating_from"
9
+ :key="index"
10
+ @mouseover="() => !readonly && (hoveredRating = index)"
11
+ @mouseleave="() => !readonly && (hoveredRating = 0)"
12
+ >
13
+ <FeatherIcon
14
+ name="star"
15
+ class="fill-gray-400 text-text-icons-gray-1 stroke-1 mr-1"
16
+ :class="iconClasses(index)"
17
+ @click="markRating(index)"
18
+ />
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { ref, watch } from 'vue'
26
+ import FeatherIcon from '../FeatherIcon.vue'
27
+
28
+ interface RatingProps {
29
+ modelValue?: number
30
+ rating_from?: number
31
+ label?: string
32
+ readonly?: boolean
33
+ size?: 'sm' | 'md' | 'lg' | 'xl'
34
+ }
35
+
36
+ const props = withDefaults(defineProps<RatingProps>(), {
37
+ modelValue: 0,
38
+ rating_from: 5,
39
+ size: 'md',
40
+ readonly: false,
41
+ })
42
+
43
+ const emit = defineEmits(['update:modelValue'])
44
+ const rating = ref(props.modelValue)
45
+ const hoveredRating = ref(0)
46
+
47
+ const iconClasses = (index: number) => {
48
+ let classes = [
49
+ {
50
+ sm: 'size-4',
51
+ md: 'size-5',
52
+ lg: 'size-6',
53
+ xl: 'size-7',
54
+ }[props.size],
55
+ ]
56
+
57
+ if (index <= hoveredRating.value && index > rating.value) {
58
+ classes.push('fill-yellow-200')
59
+ } else if (index <= rating.value) {
60
+ classes.push('fill-yellow-500')
61
+ }
62
+
63
+ if (!props.readonly) {
64
+ classes.push('cursor-pointer')
65
+ }
66
+ return classes.join(' ')
67
+ }
68
+
69
+ const emitChange = (value: number) => {
70
+ emit('update:modelValue', value)
71
+ }
72
+
73
+ const markRating = (index: number) => {
74
+ if (props.readonly) return
75
+ emitChange(index)
76
+ rating.value = index
77
+ }
78
+
79
+ watch(
80
+ () => props.modelValue,
81
+ (newVal) => {
82
+ rating.value = newVal
83
+ },
84
+ )
85
+ </script>
@@ -50,6 +50,7 @@ export default {
50
50
  },
51
51
  emits: ['update:modelValue'],
52
52
  components: {
53
+ FeatherIcon,
53
54
  RadioGroup,
54
55
  RadioGroupOption,
55
56
  RadioGroupLabel,
@@ -14,7 +14,11 @@
14
14
  />
15
15
  </template>
16
16
  <template #actions>
17
- <Button variant="solid" @click="setLink(setLinkDialog.url)">
17
+ <Button
18
+ variant="solid"
19
+ @click="setLink(setLinkDialog.url)"
20
+ class="w-full"
21
+ >
18
22
  Save
19
23
  </Button>
20
24
  </template>
@@ -15,9 +15,7 @@
15
15
  >
16
16
  {{ title }}
17
17
  </p>
18
- <p v-if="text" class="text-base text-gray-600">
19
- {{ text }}
20
- </p>
18
+ <p v-if="text" class="text-base text-gray-600" v-html="text"></p>
21
19
  </slot>
22
20
  </div>
23
21
  <div class="ml-auto pl-2">
@@ -0,0 +1,152 @@
1
+ @layer base {
2
+ :root {
3
+ --outline-amber-1: rgb(254 237 169);
4
+ --outline-amber-2: rgb(251 204 85);
5
+ --outline-blue-1: rgb(167 215 253);
6
+ --outline-blue-2: rgb(2 137 247);
7
+ --outline-gray-1: rgb(237 237 237);
8
+ --outline-gray-2: rgb(226 226 226);
9
+ --outline-gray-3: rgb(199 199 199);
10
+ --outline-gray-4: rgb(153 153 153);
11
+ --outline-gray-5: rgb(56 56 56);
12
+ --outline-gray-modals: rgb(237 237 237);
13
+ --outline-green-1: rgb(185 238 204);
14
+ --outline-green-2: rgb(155 228 182);
15
+ --outline-orange-1: rgb(244 176 127);
16
+ --outline-red-1: rgb(255 216 216);
17
+ --outline-red-2: rgb(253 194 194);
18
+ --outline-red-3: rgb(247 149 150);
19
+ --outline-red-4: rgb(224 54 54);
20
+ --outline-white: rgb(255 255 255);
21
+ --surface-amber-1: rgb(255 247 211);
22
+ --surface-amber-2: rgb(219 119 6);
23
+ --surface-blue-1: rgb(230 244 255);
24
+ --surface-blue-2: rgb(2 137 247);
25
+ --surface-cards: rgb(255 255 255);
26
+ --surface-cyan-1: rgb(221 247 255);
27
+ --surface-gray-1: rgb(248 248 248);
28
+ --surface-gray-2: rgb(243 243 243);
29
+ --surface-gray-3: rgb(237 237 237);
30
+ --surface-gray-4: rgb(226 226 226);
31
+ --surface-gray-5: rgb(82 82 82);
32
+ --surface-gray-6: rgb(56 56 56);
33
+ --surface-gray-7: rgb(23 23 23);
34
+ --surface-green-1: rgb(242 253 244);
35
+ --surface-green-2: rgb(228 250 235);
36
+ --surface-green-3: rgb(48 166 109);
37
+ --surface-menu-bar: rgb(248 248 248);
38
+ --surface-modal: rgb(255 255 255);
39
+ --surface-orange-1: rgb(255 239 228);
40
+ --surface-pink-1: rgb(253 232 245);
41
+ --surface-red-1: rgb(255 231 231);
42
+ --surface-red-2: rgb(255 216 216);
43
+ --surface-red-3: rgb(253 194 194);
44
+ --surface-red-4: rgb(204 41 41);
45
+ --surface-red-5: rgb(181 42 42);
46
+ --surface-red-6: rgb(148 31 31);
47
+ --surface-selected: rgb(255 255 255);
48
+ --surface-violet-1: rgb(240 235 255);
49
+ --surface-white: rgb(255 255 255);
50
+ --text-icons-amber-1: rgb(255 247 211);
51
+ --text-icons-amber-2: rgb(231 153 19);
52
+ --text-icons-amber-3: rgb(219 119 6);
53
+ --text-icons-blue-1: rgb(230 244 255);
54
+ --text-icons-blue-2: rgb(0 123 224);
55
+ --text-icons-blue-3: rgb(0 92 163);
56
+ --text-icons-cyan-1: rgb(59 189 229);
57
+ --text-icons-gray-1: rgb(237 237 237);
58
+ --text-icons-gray-2: rgb(226 226 226);
59
+ --text-icons-gray-3: rgb(199 199 199);
60
+ --text-icons-gray-4: rgb(153 153 153);
61
+ --text-icons-gray-5: rgb(124 124 124);
62
+ --text-icons-gray-6: rgb(82 82 82);
63
+ --text-icons-gray-7: rgb(82 82 82);
64
+ --text-icons-gray-8: rgb(56 56 56);
65
+ --text-icons-gray-9: rgb(23 23 23);
66
+ --text-icons-green-1: rgb(242 253 244);
67
+ --text-icons-green-2: rgb(48 166 109);
68
+ --text-icons-green-3: rgb(22 121 76);
69
+ --text-icons-pink-1: rgb(227 74 166);
70
+ --text-icons-red-1: rgb(255 247 247);
71
+ --text-icons-red-2: rgb(247 149 150);
72
+ --text-icons-red-3: rgb(224 54 54);
73
+ --text-icons-red-4: rgb(204 41 41);
74
+ --text-icons-violet-1: rgb(104 70 227);
75
+ --text-icons-white: rgb(255 255 255);
76
+ }
77
+ [data-theme='dark'] {
78
+ --outline-amber-1: rgb(130 65 8);
79
+ --outline-amber-2: rgb(130 65 8);
80
+ --outline-blue-1: rgb(21 89 153);
81
+ --outline-blue-2: rgb(21 89 153);
82
+ --outline-gray-1: rgb(35 35 35);
83
+ --outline-gray-2: rgb(52 52 52);
84
+ --outline-gray-3: rgb(66 66 66);
85
+ --outline-gray-4: rgb(128 128 128);
86
+ --outline-gray-5: rgb(237 237 237);
87
+ --outline-gray-modals: rgb(52 52 52);
88
+ --outline-green-1: rgb(11 97 57);
89
+ --outline-green-2: rgb(10 63 39);
90
+ --outline-orange-1: rgb(130 57 6);
91
+ --outline-red-1: rgb(98 27 24);
92
+ --outline-red-2: rgb(76 24 24);
93
+ --outline-red-3: rgb(98 27 24);
94
+ --outline-red-4: rgb(156 32 32);
95
+ --outline-white: rgb(28 28 28);
96
+ --surface-amber-1: rgb(55 30 6);
97
+ --surface-amber-2: rgb(197 116 17);
98
+ --surface-blue-1: rgb(14 32 55);
99
+ --surface-blue-2: rgb(21 128 216);
100
+ --surface-cards: rgb(28 28 28);
101
+ --surface-cyan-1: rgb(11 37 45);
102
+ --surface-gray-1: rgb(35 35 35);
103
+ --surface-gray-2: rgb(255 255 255 / 0.1);
104
+ --surface-gray-3: rgb(255 255 255 / 0.18);
105
+ --surface-gray-4: rgb(66 66 66);
106
+ --surface-gray-5: rgb(212 212 212);
107
+ --surface-gray-6: rgb(175 175 175);
108
+ --surface-gray-7: rgb(212 212 212);
109
+ --surface-green-1: rgb(11 46 28);
110
+ --surface-green-2: rgb(10 63 39);
111
+ --surface-green-3: rgb(31 157 96);
112
+ --surface-menu-bar: rgb(15 15 15);
113
+ --surface-modal: rgb(35 35 35);
114
+ --surface-orange-1: rgb(64 31 7 / 0.8);
115
+ --surface-pink-1: rgb(71 20 50 / 0.8);
116
+ --surface-red-1: rgb(54 21 21 / 0.8);
117
+ --surface-red-2: rgb(76 24 24 / 0.9);
118
+ --surface-red-3: rgb(98 27 24);
119
+ --surface-red-4: rgb(228 56 56);
120
+ --surface-red-5: rgb(156 32 32);
121
+ --surface-red-6: rgb(98 27 24);
122
+ --surface-selected: rgb(255 255 255 / 0.1);
123
+ --surface-violet-1: rgb(34 28 66);
124
+ --surface-white: rgb(15 15 15);
125
+ --text-icons-amber-1: rgb(255 255 255);
126
+ --text-icons-amber-2: rgb(231 153 19);
127
+ --text-icons-amber-3: rgb(231 153 19);
128
+ --text-icons-blue-1: rgb(255 255 255);
129
+ --text-icons-blue-2: rgb(90 174 242);
130
+ --text-icons-blue-3: rgb(140 193 236);
131
+ --text-icons-cyan-1: rgb(60 184 220);
132
+ --text-icons-gray-1: rgb(35 35 35);
133
+ --text-icons-gray-2: rgb(66 66 66);
134
+ --text-icons-gray-3: rgb(113 113 113);
135
+ --text-icons-gray-4: rgb(113 113 113);
136
+ --text-icons-gray-5: rgb(128 128 128);
137
+ --text-icons-gray-6: rgb(153 153 153);
138
+ --text-icons-gray-7: rgb(175 175 175);
139
+ --text-icons-gray-8: rgb(212 212 212);
140
+ --text-icons-gray-9: rgb(248 248 248);
141
+ --text-icons-green-1: rgb(255 255 255);
142
+ --text-icons-green-2: rgb(53 174 116);
143
+ --text-icons-green-3: rgb(120 215 169);
144
+ --text-icons-pink-1: rgb(227 89 171);
145
+ --text-icons-red-1: rgb(255 255 255);
146
+ --text-icons-red-2: rgb(98 27 24);
147
+ --text-icons-red-3: rgb(235 77 82);
148
+ --text-icons-red-4: rgb(252 116 116);
149
+ --text-icons-violet-1: rgb(157 124 234);
150
+ --text-icons-white: rgb(15 15 15);
151
+ }
152
+ }
package/src/index.js CHANGED
@@ -26,6 +26,7 @@ export { default as LoadingIndicator } from './components/LoadingIndicator.vue'
26
26
  export { default as LoadingText } from './components/LoadingText.vue'
27
27
  export { default as Progress } from './components/Progress.vue'
28
28
  export { default as Popover } from './components/Popover.vue'
29
+ export { default as Rating } from './components/Rating/Rating.vue'
29
30
  export { default as Resource } from './components/Resource.vue'
30
31
  export { default as Select } from './components/Select.vue'
31
32
  export { default as Spinner } from './components/Spinner.vue'
package/src/style.css CHANGED
@@ -1,4 +1,5 @@
1
1
  @import './fonts/Inter/inter.css';
2
+ @import './espressoColors.css';
2
3
 
3
4
  @tailwind base;
4
5
  @tailwind components;
@@ -1,17 +1,11 @@
1
1
  import ConfirmDialog from '../components/ConfirmDialog.vue'
2
2
  import { h, ref } from 'vue'
3
3
 
4
- export function confirmDialog({
5
- title = 'Untitled',
6
- message = '',
7
- fields = [],
8
- onConfirm,
9
- }) {
4
+ export function confirmDialog({ title = 'Untitled', message = '', onConfirm }) {
10
5
  renderDialog(
11
6
  h(ConfirmDialog, {
12
7
  title,
13
8
  message,
14
- fields,
15
9
  onConfirm,
16
10
  }),
17
11
  )
@@ -0,0 +1,83 @@
1
+ const espressoColors = {
2
+ outline: {
3
+ 'amber-1': 'var(--outline-amber-1)',
4
+ 'amber-2': 'var(--outline-amber-2)',
5
+ 'blue-1': 'var(--outline-blue-1)',
6
+ 'blue-2': 'var(--outline-blue-2)',
7
+ 'gray-1': 'var(--outline-gray-1)',
8
+ 'gray-2': 'var(--outline-gray-2)',
9
+ 'gray-3': 'var(--outline-gray-3)',
10
+ 'gray-4': 'var(--outline-gray-4)',
11
+ 'gray-5': 'var(--outline-gray-5)',
12
+ 'gray-modals': 'var(--outline-gray-modals)',
13
+ 'green-1': 'var(--outline-green-1)',
14
+ 'green-2': 'var(--outline-green-2)',
15
+ 'orange-1': 'var(--outline-orange-1)',
16
+ 'red-1': 'var(--outline-red-1)',
17
+ 'red-2': 'var(--outline-red-2)',
18
+ 'red-3': 'var(--outline-red-3)',
19
+ 'red-4': 'var(--outline-red-4)',
20
+ white: 'var(--outline-white)',
21
+ },
22
+ surface: {
23
+ 'amber-1': 'var(--surface-amber-1)',
24
+ 'amber-2': 'var(--surface-amber-2)',
25
+ 'blue-1': 'var(--surface-blue-1)',
26
+ 'blue-2': 'var(--surface-blue-2)',
27
+ cards: 'var(--surface-cards)',
28
+ 'cyan-1': 'var(--surface-cyan-1)',
29
+ 'gray-1': 'var(--surface-gray-1)',
30
+ 'gray-2': 'var(--surface-gray-2)',
31
+ 'gray-3': 'var(--surface-gray-3)',
32
+ 'gray-4': 'var(--surface-gray-4)',
33
+ 'gray-5': 'var(--surface-gray-5)',
34
+ 'gray-6': 'var(--surface-gray-6)',
35
+ 'gray-7': 'var(--surface-gray-7)',
36
+ 'green-1': 'var(--surface-green-1)',
37
+ 'green-2': 'var(--surface-green-2)',
38
+ 'green-3': 'var(--surface-green-3)',
39
+ 'menu-bar': 'var(--surface-menu-bar)',
40
+ modal: 'var(--surface-modal)',
41
+ 'orange-1': 'var(--surface-orange-1)',
42
+ 'pink-1': 'var(--surface-pink-1)',
43
+ 'red-1': 'var(--surface-red-1)',
44
+ 'red-2': 'var(--surface-red-2)',
45
+ 'red-3': 'var(--surface-red-3)',
46
+ 'red-4': 'var(--surface-red-4)',
47
+ 'red-5': 'var(--surface-red-5)',
48
+ 'red-6': 'var(--surface-red-6)',
49
+ selected: 'var(--surface-selected)',
50
+ 'violet-1': 'var(--surface-violet-1)',
51
+ white: 'var(--surface-white)',
52
+ },
53
+ 'text-icons': {
54
+ 'amber-1': 'var(--text-icons-amber-1)',
55
+ 'amber-2': 'var(--text-icons-amber-2)',
56
+ 'amber-3': 'var(--text-icons-amber-3)',
57
+ 'blue-1': 'var(--text-icons-blue-1)',
58
+ 'blue-2': 'var(--text-icons-blue-2)',
59
+ 'blue-3': 'var(--text-icons-blue-3)',
60
+ 'cyan-1': 'var(--text-icons-cyan-1)',
61
+ 'gray-1': 'var(--text-icons-gray-1)',
62
+ 'gray-2': 'var(--text-icons-gray-2)',
63
+ 'gray-3': 'var(--text-icons-gray-3)',
64
+ 'gray-4': 'var(--text-icons-gray-4)',
65
+ 'gray-5': 'var(--text-icons-gray-5)',
66
+ 'gray-6': 'var(--text-icons-gray-6)',
67
+ 'gray-7': 'var(--text-icons-gray-7)',
68
+ 'gray-8': 'var(--text-icons-gray-8)',
69
+ 'gray-9': 'var(--text-icons-gray-9)',
70
+ 'green-1': 'var(--text-icons-green-1)',
71
+ 'green-2': 'var(--text-icons-green-2)',
72
+ 'green-3': 'var(--text-icons-green-3)',
73
+ 'pink-1': 'var(--text-icons-pink-1)',
74
+ 'red-1': 'var(--text-icons-red-1)',
75
+ 'red-2': 'var(--text-icons-red-2)',
76
+ 'red-3': 'var(--text-icons-red-3)',
77
+ 'red-4': 'var(--text-icons-red-4)',
78
+ 'violet-1': 'var(--text-icons-violet-1)',
79
+ white: 'var(--text-icons-white)',
80
+ },
81
+ }
82
+
83
+ module.exports = espressoColors
@@ -1,6 +1,8 @@
1
1
  const plugin = require('tailwindcss/plugin')
2
+ const espressoVariables = require('./espressoVariables.js')
2
3
 
3
4
  module.exports = {
5
+ darkMode: ['selector', '[data-theme="dark"]'],
4
6
  theme: {
5
7
  extend: {
6
8
  spacing: {
@@ -251,6 +253,7 @@ module.exports = {
251
253
  800: 'rgba(0, 0, 0, 0.81)',
252
254
  900: 'rgba(0, 0, 0, 0.90)',
253
255
  },
256
+ ...espressoVariables,
254
257
  }),
255
258
  borderRadius: {
256
259
  none: '0px', // 0