@open-commune/web 0.1.3

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 (132) hide show
  1. package/README.md +41 -0
  2. package/app.config.ts +10 -0
  3. package/app.vue +13 -0
  4. package/assets/css/main.css +13 -0
  5. package/components/data/display/row/data-avatar.vue +23 -0
  6. package/components/data/display/row/data-badge.vue +34 -0
  7. package/components/data/display/row/data-color.vue +17 -0
  8. package/components/data/display/row/data-display-badge-publish-status.vue +29 -0
  9. package/components/data/display/row/data-label.vue +37 -0
  10. package/components/data/display/row/data-last-update.vue +45 -0
  11. package/components/data/display/row/data-spacer.vue +4 -0
  12. package/components/data/display/row/data-summary.vue +44 -0
  13. package/components/data/display/row/data-text.vue +14 -0
  14. package/components/data/display/row/data-toggle.vue +16 -0
  15. package/components/data/display/row.vue +111 -0
  16. package/components/data/lists/data-list.vue +85 -0
  17. package/components/data/lists/data-lists-table.vue +155 -0
  18. package/components/elements/avatars/avatar-circular.vue +68 -0
  19. package/components/elements/avatars/avatar-with-text.vue +47 -0
  20. package/components/elements/badges/badge.vue +85 -0
  21. package/components/elements/buttons/primary.vue +32 -0
  22. package/components/elements/buttons/secondary.vue +35 -0
  23. package/components/elements/buttons/tertiary.vue +25 -0
  24. package/components/elements/dropdowns/dots.vue +75 -0
  25. package/components/elements/media/image.vue +82 -0
  26. package/components/elements/notifications/default.vue +90 -0
  27. package/components/forms/handlers/handler-collections.vue +189 -0
  28. package/components/forms/handlers/handler-publication-status.vue +193 -0
  29. package/components/forms/handlers/handler-records.vue +138 -0
  30. package/components/forms/handlers/handler-roles.vue +176 -0
  31. package/components/forms/handlers/handler-schedules.vue +218 -0
  32. package/components/forms/handlers/handler-steps.vue +147 -0
  33. package/components/forms/handlers/handler-tokens.vue +79 -0
  34. package/components/forms/inputs/input-color-picker.vue +159 -0
  35. package/components/forms/inputs/input-editor.vue +363 -0
  36. package/components/forms/inputs/input-files-media.vue +150 -0
  37. package/components/forms/inputs/input-files.vue +98 -0
  38. package/components/forms/inputs/input-json.vue +65 -0
  39. package/components/forms/inputs/input-location.vue +179 -0
  40. package/components/forms/inputs/input-text-area.vue +65 -0
  41. package/components/forms/inputs/input-text.vue +122 -0
  42. package/components/forms/inputs/input-toggle.vue +125 -0
  43. package/components/forms/inputs/media-section/media-editor.vue +95 -0
  44. package/components/forms/layouts/form.vue +203 -0
  45. package/components/forms/layouts/layout.vue +47 -0
  46. package/components/forms/layouts/modal.vue +96 -0
  47. package/components/forms/layouts/navigation.vue +48 -0
  48. package/components/forms/layouts/section.vue +118 -0
  49. package/components/forms/selects/select-collection-enum.vue +195 -0
  50. package/components/forms/selects/select-date.vue +245 -0
  51. package/components/forms/selects/select-enum.vue +186 -0
  52. package/components/forms/selects/select-record.vue +274 -0
  53. package/components/layout/headings/headings-collection.vue +29 -0
  54. package/components/layout/headings/headings-record.vue +33 -0
  55. package/components/layout/navigations/primary/dropdown-publish.vue +147 -0
  56. package/components/layout/navigations/primary.vue +132 -0
  57. package/components/layout/notifications/notifications-list.vue +19 -0
  58. package/composables/core/domains/use-definitions.ts +57 -0
  59. package/composables/core/domains/use-entries.ts +62 -0
  60. package/composables/core/domains/use-media.ts +82 -0
  61. package/composables/core/use-core.ts +12 -0
  62. package/composables/use-ability.js +38 -0
  63. package/composables/use-models.js +27 -0
  64. package/composables/use-session.ts +80 -0
  65. package/composables/use-timer.js +62 -0
  66. package/composables/use-trpc.js +17 -0
  67. package/editor/index.ts +44 -0
  68. package/editor/views/CalloutView.vue +22 -0
  69. package/editor/views/DetailsContentView.vue +11 -0
  70. package/editor/views/DetailsSummaryView.vue +11 -0
  71. package/editor/views/DetailsView.vue +72 -0
  72. package/editor/views/EmbedView.vue +53 -0
  73. package/editor/views/FileView.vue +79 -0
  74. package/editor/views/ImageView.vue +56 -0
  75. package/editor/views/UploadView.vue +71 -0
  76. package/layouts/default.vue +21 -0
  77. package/layouts/entrance.vue +10 -0
  78. package/layouts/record.vue +21 -0
  79. package/middleware/00.auth.global.js +16 -0
  80. package/middleware/01.instance.global.js +21 -0
  81. package/middleware/03.verify-permissions.global.js +11 -0
  82. package/middleware/04.prefetch-page.global.js +18 -0
  83. package/middleware/05.headings-page.global.js +25 -0
  84. package/models/_commun.js +243 -0
  85. package/models/_factory.js +34 -0
  86. package/models/collection.js +87 -0
  87. package/models/entrance.js +42 -0
  88. package/models/media.js +82 -0
  89. package/models/organization.js +92 -0
  90. package/models/record.js +165 -0
  91. package/models/role.js +66 -0
  92. package/models/token.js +56 -0
  93. package/models/user.js +97 -0
  94. package/nuxt.config.ts +151 -0
  95. package/package.json +71 -0
  96. package/pages/[collection]/[record]/index.vue +88 -0
  97. package/pages/[collection]/index.vue +55 -0
  98. package/pages/_lab/editor.vue +149 -0
  99. package/pages/auth/callback.vue +31 -0
  100. package/pages/index/index.vue +85 -0
  101. package/pages/index/password/define/[token].vue +79 -0
  102. package/pages/index/password/recover/index.vue +72 -0
  103. package/pages/index/welcome/[token].vue +90 -0
  104. package/pages/index.vue +44 -0
  105. package/pages/management/index.vue +15 -0
  106. package/pages/management/members.vue +176 -0
  107. package/pages/management/organizations.vue +192 -0
  108. package/pages/management/roles.vue +183 -0
  109. package/pages/overview.vue +15 -0
  110. package/pages/settings.vue +197 -0
  111. package/plugins/01.session.client.ts +5 -0
  112. package/plugins/02.vue-query.ts +25 -0
  113. package/public/favicon.ico +0 -0
  114. package/public/logo-black.svg +1 -0
  115. package/public/logo-white.svg +1 -0
  116. package/server/api/trpc/[...trpc].ts +28 -0
  117. package/server/api/v1/content/deployment.get.ts +45 -0
  118. package/server/api/v1/content/records.get.ts +12 -0
  119. package/server/middleware/0.security.ts +19 -0
  120. package/server/middleware/1.cors.ts +31 -0
  121. package/server/middleware/2.session.ts +20 -0
  122. package/server/middleware/3.api-token.ts +20 -0
  123. package/server/plugins/core.ts +8 -0
  124. package/server/routes/_tasks/[name].post.ts +20 -0
  125. package/server/routes/health.get.ts +6 -0
  126. package/server/tasks/deploy.ts +26 -0
  127. package/server/types.d.ts +8 -0
  128. package/server/utils/core.ts +18 -0
  129. package/store/layout/headings.js +21 -0
  130. package/store/layout/modal.js +80 -0
  131. package/store/layout/notifications.js +52 -0
  132. package/store/layout/workspace.js +80 -0
@@ -0,0 +1,218 @@
1
+ <template>
2
+ <div class="relative block w-full">
3
+ <label
4
+ v-if="label"
5
+ id="listbox-label"
6
+ class="block text-2xs font-light tracking-wider text-gray-500 uppercase"
7
+ >
8
+ {{ label }}
9
+ </label>
10
+ <list-table
11
+ v-if="periodsDataList.length"
12
+ :headers="TABLE_HEADERS"
13
+ :items="periodsDataList"
14
+ :actions="['remove']"
15
+ :class="{ 'mt-1': label }"
16
+ class="col-span-4 bg-gray-100 p-2"
17
+ @remove="_removePeriod"
18
+ />
19
+ <div v-if="displayPeriodHandler" class="flex flex-col space-y-1 my-4">
20
+ <div class="inline-flex w-full items-center space-x-2">
21
+ <select-date
22
+ :time-picker="enableTime"
23
+ :time-end="!enableToDate"
24
+ :value="periodFromDate"
25
+ class="w-1/4"
26
+ @change="_updateCurrentPeriod('begin', $event)"
27
+ />
28
+ <select-date
29
+ v-if="enableToDate"
30
+ :time-picker="enableTime"
31
+ :value="periodToDate"
32
+ class="w-1/4"
33
+ @change="_updateCurrentPeriod('end', $event)"
34
+ />
35
+ <select-enum
36
+ v-if="enablePeriodicity"
37
+ :items="PERIODICITY"
38
+ :item-key="'label'"
39
+ :item-value="'value'"
40
+ :value="periodPeriodicity"
41
+ place-holder="Récurrence"
42
+ class="w-1/4"
43
+ @change="periodPeriodicity = $event"
44
+ />
45
+ <button-secondary label="Ajouter date(s)" class="whitespace-nowrap" @click="_addPeriod" />
46
+ <button-tertiary v-if="selection.periods?.length" label="Annuler" class="!px-0" @click="displayPeriodHandler = false" />
47
+ </div>
48
+ <div class="flex flex-col justify-start">
49
+ <input-toggle
50
+ :value="enableToDate || enablePeriodicity"
51
+ :default-value="false"
52
+ :description="'Date de fin différente'"
53
+ :icon="false"
54
+ small
55
+ @change="_toggleToDate"
56
+ />
57
+ <input-toggle
58
+ :value="enableTime"
59
+ :default-value="false"
60
+ :description="'Inclure des horaires'"
61
+ :icon="false"
62
+ small
63
+ @change="_toggleTime"
64
+ />
65
+ <input-toggle
66
+ :value="enablePeriodicity"
67
+ :default-value="false"
68
+ :description="'Récurrent'"
69
+ :icon="false"
70
+ small
71
+ @change="_togglePeriodicity"
72
+ />
73
+ </div>
74
+ </div>
75
+ <div v-else class="inline-flex w-full justify-end">
76
+ <button-tertiary label="Ajouter une autre date" class="!px-0" @click="displayPeriodHandler = true" />
77
+ </div>
78
+ </div>
79
+ </template>
80
+
81
+ <script setup>
82
+ import { DateTime, Interval } from 'luxon'
83
+ import listTable from '~/components/data/lists/data-lists-table'
84
+ import selectDate from '~/components/forms/selects/select-date'
85
+ import selectEnum from '~/components/forms/selects/select-enum'
86
+ import inputToggle from '~/components/forms/inputs/input-toggle'
87
+ import buttonSecondary from '~/components/elements/buttons/secondary'
88
+ import buttonTertiary from '~/components/elements/buttons/tertiary'
89
+
90
+ // Prepare constants
91
+ const PERIODICITY = [
92
+ { label: 'Tous les jours', value: 'DAILY' },
93
+ { label: 'Tous les semaines', value: 'WEEKLY' },
94
+ { label: 'Tous les mois', value: 'MONTHLY' },
95
+ { label: 'Chaque année', value: 'YEARLY' },
96
+ ]
97
+ const TABLE_HEADERS = [
98
+ {
99
+ property: 'Période',
100
+ component: 'data-summary',
101
+ nestedValue: false
102
+ },
103
+ {
104
+ title: '',
105
+ component: 'actions',
106
+ componentOptions: {
107
+ options: [
108
+ { label: 'Supprimer', action: 'remove' }
109
+ ]
110
+ }
111
+ }
112
+ ]
113
+
114
+ // Prepare props
115
+ const $props = defineProps({
116
+ label: {
117
+ type: String,
118
+ default: ''
119
+ },
120
+ value: {
121
+ type: Array,
122
+ default: () => []
123
+ }
124
+ })
125
+
126
+ // Prepare $emitters
127
+ const $emitters = defineEmits(['keyup.enter', 'change'])
128
+
129
+ // Prepare data
130
+ const selection = ref({})
131
+ const displayPeriodHandler = ref(true)
132
+ const periodFromDate = ref('')
133
+ const periodToDate = ref('')
134
+ const periodPeriodicity = ref('UNIQUE')
135
+ const enableToDate = ref(false)
136
+ const enableTime = ref(false)
137
+ const enablePeriodicity = ref(false)
138
+
139
+ // Prepare computed
140
+ const periodsDataList = computed(
141
+ () => selection.value?.periods?.map(
142
+ ({ fromDate, toDate, periodicity }) => ({
143
+ title: Interval.fromISO(`${fromDate}/${toDate}`, { locale: 'fr' }).toFormat('ccc d LLL à H:mm', { locale: 'fr' }),
144
+ description: PERIODICITY.find(({ value }) => value === periodicity)?.label || 'Une seule fois',
145
+ fromDate,
146
+ toDate
147
+ })
148
+ ) || []
149
+ )
150
+ // Prepare methods
151
+ const _resetDisplay = () => {
152
+ periodFromDate.value = ''
153
+ periodToDate.value = ''
154
+ periodPeriodicity.value = 'UNIQUE'
155
+ enableToDate.value = false
156
+ enableTime.value = false
157
+ enablePeriodicity.value = false
158
+ displayPeriodHandler.value = false
159
+ }
160
+ const _togglePeriodicity = (value) => {
161
+ enablePeriodicity.value = value
162
+ enableToDate.value = value
163
+ }
164
+ const _toggleToDate = (value) => {
165
+ enableToDate.value = value
166
+ }
167
+ const _toggleTime = (value) => {
168
+ enableTime.value = value
169
+ }
170
+ const _updateCurrentPeriod = (beginOrEnd, data) => {
171
+ if (beginOrEnd === 'begin') {
172
+ periodFromDate.value = data.beginTime
173
+ if (!enableToDate.value) {
174
+ periodToDate.value = data.endTime
175
+ }
176
+ } else {
177
+ periodToDate.value = data.date
178
+ }
179
+ }
180
+ const _addPeriod = () => {
181
+ if (!selection.value?.periods) {
182
+ selection.value = { periods: [] }
183
+ }
184
+ // La date AFFICHÉE par défaut compte même sans clic dans le calendrier —
185
+ // le widget hérité enregistrait des périodes VIDES dans ce cas (fromDate '').
186
+ const fromDate = periodFromDate.value ||
187
+ DateTime.now().setZone('Europe/Paris').set({ second: 0, millisecond: 0 }).toISO()
188
+ const toDate = periodToDate.value || fromDate
189
+ selection.value.periods.push({
190
+ fromDate,
191
+ toDate,
192
+ periodicity: periodPeriodicity.value
193
+ })
194
+ $emitters('change', selection.value)
195
+ _resetDisplay()
196
+ }
197
+ const _removePeriod = (val) => {
198
+ const indexToRemove = selection.value.periods?.findIndex(
199
+ ({ fromDate, toDate }) => fromDate === val.fromDate && toDate === val.toDate
200
+ )
201
+ if (indexToRemove >= 0) {
202
+ selection.value.periods?.splice(indexToRemove, 1)
203
+ }
204
+ $emitters('change', selection.value)
205
+ }
206
+
207
+ // prepare watchers
208
+ watch(
209
+ () => $props.value,
210
+ (newValue) => {
211
+ if (newValue?.periods?.length) {
212
+ selection.value = newValue
213
+ displayPeriodHandler.value = !selection.value?.periods?.length
214
+ }
215
+ },
216
+ { immediate: true }
217
+ )
218
+ </script>
@@ -0,0 +1,147 @@
1
+ <template>
2
+ <div class="relative block w-full">
3
+ <label v-if="label" id="listbox-label" class="block text-2xs font-light tracking-wider text-gray-500 uppercase">
4
+ {{ label }}
5
+ </label>
6
+ <!-- <div class="bg-gray-200 text-gray-500 text-sm rounded-md w-full p-4">
7
+ /!\ Navigation should be based on organization.collections array only<br>
8
+ Collections that does not belong to organization can't be updated or deleted only enabled / disabled<br>
9
+ Let user duplicate an existing collection for this organization only
10
+ </div> -->
11
+ <button-secondary class="my-1" label="Ajouter une étape" @click="_openModalForm('add')" />
12
+ <list-table
13
+ :headers="TABLE_HEADERS"
14
+ :items="selection"
15
+ :actions="['update', 'remove']"
16
+ @update="_openModalForm('update', $event)"
17
+ @remove="_handleStep('remove', $event)"
18
+ />
19
+ </div>
20
+ </template>
21
+
22
+ <script setup>
23
+ import listTable from '~/components/data/lists/data-lists-table'
24
+ import buttonSecondary from '~/components/elements/buttons/secondary'
25
+ import { useModalStore } from '~/store/layout/modal'
26
+
27
+ const { open } = useModalStore()
28
+
29
+ // prepare constants
30
+ const TABLE_HEADERS = [
31
+ {
32
+ title: 'Nom',
33
+ property: 'name',
34
+ component: 'data-text'
35
+ },
36
+ {
37
+ title: 'Statut',
38
+ property: 'status',
39
+ component: 'data-text'
40
+ },
41
+ {
42
+ title: '',
43
+ component: 'actions'
44
+ }
45
+ ]
46
+
47
+ // Prepare props
48
+ const $props = defineProps({
49
+ label: {
50
+ type: String,
51
+ default: ''
52
+ },
53
+ value: {
54
+ type: Array,
55
+ default: () => []
56
+ }
57
+ })
58
+
59
+ // Prepare $emitters
60
+ const $emitters = defineEmits(['keyup.enter', 'change'])
61
+
62
+ // Prepare reactive data
63
+ const selection = ref([])
64
+
65
+ // Prepare methods
66
+ const _handleStep = async (action, data = {}) => {
67
+ switch (action) {
68
+ case 'add':
69
+ selection.value.push({ ...data, _id: selection.value.length })
70
+ break
71
+ case 'update':
72
+ selection.value[data._id] = data
73
+ break
74
+ case 'remove':
75
+ selection.value.splice(data._id, 1)
76
+ selection.value = selection.value.map((val, index) => ({ ...val, _id: index }))
77
+ break
78
+ default:
79
+ throw new Error('E_UNHANDLED')
80
+ }
81
+ }
82
+
83
+ const _openModalForm = (action, record) => {
84
+ open({
85
+ onSubmit: async (data) => _handleStep(action, { _id: record?._id, ...data }),
86
+ details: {
87
+ title: `${action === 'add' ? 'Créer' : 'Modifier'} une étape`,
88
+ subTitle: 'Vous pouvez détailler votre étape ici.',
89
+ acceptButton: `${action === 'add' ? 'Créer' : 'Modifier'}`,
90
+ closeButton: 'Annuler'
91
+ },
92
+ attributes: [
93
+ {
94
+ property: 'name',
95
+ componentOptions: {
96
+ label: 'Titre de l\'étape',
97
+ name: 'name'
98
+ },
99
+ component: 'input-text'
100
+ },
101
+ {
102
+ property: 'description',
103
+ componentOptions: {
104
+ label: 'Description de l\'étape',
105
+ name: 'description'
106
+ },
107
+ component: 'input-text-area'
108
+ },
109
+ {
110
+ property: 'status',
111
+ componentOptions: {
112
+ label: 'Etat de l\'étape',
113
+ name: 'status',
114
+ items: ['Planifié', 'En cours', 'Réalisé'],
115
+ object: false
116
+ },
117
+ component: 'select-enum'
118
+ },
119
+ {
120
+ property: 'content',
121
+ componentOptions: {
122
+ label: 'Contenu de l\'étape',
123
+ name: 'content'
124
+ },
125
+ component: 'input-wysiwyg'
126
+ }
127
+ ],
128
+ record
129
+ })
130
+ }
131
+
132
+ // prepare watchers
133
+ watch(
134
+ () => $props.value,
135
+ (newValue) => {
136
+ if (newValue) {
137
+ selection.value = newValue.map((val, index) => ({ _id: index, ...val }))
138
+ }
139
+ },
140
+ { immediate: true }
141
+ )
142
+ watch(
143
+ selection,
144
+ (newValue) => $emitters('change', newValue),
145
+ { immediate: true }
146
+ )
147
+ </script>
@@ -0,0 +1,79 @@
1
+ <template>
2
+ <div class="relative block w-full">
3
+ <button-secondary class="my-1" label="Ajouter un token" @click="_openModalForm" />
4
+ <list-table
5
+ :headers="TABLE_HEADERS"
6
+ :items="tokens"
7
+ :actions="['remove']"
8
+ @remove="_removeToken($event)"
9
+ />
10
+ </div>
11
+ </template>
12
+
13
+ <script setup>
14
+ import listTable from '~/components/data/lists/data-lists-table'
15
+ import buttonSecondary from '~/components/elements/buttons/secondary'
16
+ import { useModalStore } from '~/store/layout/modal'
17
+
18
+ const { Token } = useModels()
19
+ const { open } = useModalStore()
20
+
21
+ // Prepare constants
22
+ const TABLE_HEADERS = [
23
+ {
24
+ title: 'Nom',
25
+ property: 'name',
26
+ component: 'data-text'
27
+ },
28
+ {
29
+ title: 'Token',
30
+ property: 'token',
31
+ component: 'data-text'
32
+ },
33
+ {
34
+ title: '',
35
+ component: 'actions'
36
+ }
37
+ ]
38
+
39
+ // Prepare computed data
40
+ const tokens = computed(() => Token.repo.all())
41
+
42
+ // Prepare methods
43
+ const _createToken = async (data) => {
44
+ await Token.create(data)
45
+ }
46
+
47
+ const _removeToken = async ({ _id }) => {
48
+ await Token.remove(_id)
49
+ }
50
+
51
+ const _openModalForm = (action, record) => {
52
+ open({
53
+ onSubmit: _createToken,
54
+ details: {
55
+ title: 'Créer un token',
56
+ subTitle: 'Entrez un nom pour votre token afin de l\'identifier facilement.',
57
+ acceptButton: 'Créer',
58
+ closeButton: 'Annuler'
59
+ },
60
+ attributes: [
61
+ {
62
+ property: 'name',
63
+ componentOptions: {
64
+ placeHolder: 'ex: landing pages, mobile app, ... ',
65
+ name: 'name'
66
+ },
67
+ component: 'input-text'
68
+ }
69
+ ]
70
+ })
71
+ }
72
+
73
+ // Prepare lifecle hooks
74
+ onMounted(async () => {
75
+ if (!tokens.value.length) {
76
+ await Token.list()
77
+ }
78
+ })
79
+ </script>
@@ -0,0 +1,159 @@
1
+ <template>
2
+ <div class="relative">
3
+ <label
4
+ :for="name"
5
+ class="block text-2xs font-light tracking-wider text-gray-500 uppercase"
6
+ >
7
+ {{ label }}
8
+ </label>
9
+ <div class="inline-flex items-center w-full space-x-2">
10
+ <div class="relative mt-1 rounded-md shadow-sm">
11
+ <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
12
+ <div class="h-5 w-5 mt-1 rounded-full bg-white" :style="`background-color: ${data}`" />
13
+ </div>
14
+ <input
15
+ :id="name"
16
+ v-model="data"
17
+ :name="name"
18
+ :class="{ 'mt-1':!!label }"
19
+ :placeholder="placeHolder"
20
+ :disabled="disabled"
21
+ type="text"
22
+ class="block w-full rounded-md py-2 px-3 pl-10 shadow-xs border border-gray-200 disabled:bg-gray-100 focus:outline-none focus:ring-gray-50 focus:border-black sm:text-sm"
23
+ @keyup.enter="$emit('enter')"
24
+ >
25
+ </div>
26
+ <button
27
+ :class="{ 'mt-1':!!label }"
28
+ class="border border-gray-200 w-auto h-full text-sm py-1.5 px-2 font-medium rounded-md text-gray-900 hover:text-black bg-transparent focus:outline-none focus:ring-gray-50 focus:border-black"
29
+ @click="_toggle"
30
+ >
31
+ <svg
32
+ class="w-6 h-6"
33
+ fill="none"
34
+ stroke="currentColor"
35
+ viewBox="0 0 26 26"
36
+ xmlns="http://www.w3.org/2000/svg"
37
+ >
38
+ <path
39
+ stroke-linecap="round"
40
+ stroke-linejoin="round"
41
+ stroke-width="2"
42
+ d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"
43
+ />
44
+ </svg>
45
+ </button>
46
+ </div>
47
+ <transition
48
+ leave-active-class="transition ease-in duration-100"
49
+ leave-from-class="opacity-100"
50
+ leave-to-class="opacity-0"
51
+ >
52
+ <div
53
+ v-if="display"
54
+ ref="inputColorPicker"
55
+ class="border border-gray-300 origin-top-right absolute right-0 top-full mt-2 rounded-md shadow-lg z-20"
56
+ >
57
+ <div class="rounded-md bg-white shadow-xs p-2">
58
+ <div class="flex">
59
+ <div v-for="(color, i) in COLORS" :key="i">
60
+ <div
61
+ v-for="variant in color"
62
+ :key="variant"
63
+ :style="`background-color: ${variant};`"
64
+ class="cursor-pointer w-6 h-6 rounded-full mx-1 my-1"
65
+ @click="_select(variant)"
66
+ />
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </transition>
72
+ </div>
73
+ </template>
74
+
75
+ <script setup>
76
+ import { onClickOutside } from '@vueuse/core'
77
+
78
+ // Prepare constants
79
+ const COLORS = [
80
+ ['#E2E8F0', '#64748B', '#1E293B'],
81
+ ['#E5E7EB', '#6B7280', '#1F2937'],
82
+ ['#FECACA', '#EF4444', '#991B1B'],
83
+ ['#FED7AA', '#F97316', '#9A3412'],
84
+ ['#FDE68A', '#F59E0B', '#92400E'],
85
+ ['#FEF08A', '#EAB308', '#854D0E'],
86
+ ['#D9F99D', '#84CC16', '#3F6212'],
87
+ ['#BBF7D0', '#22C55E', '#166534'],
88
+ ['#A7F3D0', '#10B981', '#065F46'],
89
+ ['#99F6E4', '#14B8A6', '#115E59'],
90
+ ['#A5F3FC', '#06B6D4', '#155E75'],
91
+ ['#BAE6FD', '#0EA5E9', '#075985'],
92
+ ['#BFDBFE', '#3B82F6', '#1E40AF'],
93
+ ['#C7D2FE', '#6366F1', '#3730A3'],
94
+ ['#DDD6FE', '#8B5CF6', '#5B21B6'],
95
+ ['#E9D5FF', '#A855F7', '#6B21A8'],
96
+ ['#F5D0FE', '#D946EF', '#86198F'],
97
+ ['#FBCFE8', '#EC4899', '#9D174D'],
98
+ ['#FECDD3', '#F43F5E', '#9F1239']
99
+ ]
100
+
101
+ // Prepare $emitters
102
+ const $emitters = defineEmits(['change'])
103
+
104
+ // Prepare props
105
+ const $props = defineProps({
106
+ disabled: {
107
+ type: Boolean,
108
+ default: false
109
+ },
110
+ label: {
111
+ type: String,
112
+ default: ''
113
+ },
114
+ name: {
115
+ type: String,
116
+ default: 'input'
117
+ },
118
+ placeHolder: {
119
+ type: String,
120
+ default: ''
121
+ },
122
+ value: {
123
+ type: String,
124
+ default: ''
125
+ }
126
+ })
127
+
128
+ // Prepare reactive data
129
+ const inputColorPicker = ref(null)
130
+ const display = ref(false)
131
+ const data = ref('')
132
+
133
+ // Prepare watchers
134
+ watch(
135
+ () => $props.value,
136
+ (newValue) => {
137
+ data.value = newValue
138
+ },
139
+ { immediate: true }
140
+ )
141
+ watch(
142
+ data,
143
+ (newValue) => $emitters('change', newValue)
144
+ )
145
+
146
+ // Prepare methods
147
+ const _toggle = () => {
148
+ display.value = !display.value
149
+ }
150
+ const _close = () => {
151
+ display.value = false
152
+ }
153
+ const _select = (color) => {
154
+ data.value = color
155
+ _close()
156
+ }
157
+ // Prepare directives
158
+ onClickOutside(inputColorPicker, _close)
159
+ </script>