@tekkare/romulus 0.1.223 → 0.1.224

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/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "0.1.222",
7
+ "version": "0.1.223",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
3
- import { PhCaretDown, PhCheck, PhMagnifyingGlass } from '@phosphor-icons/vue'
3
+ import { PhCaretDown, PhCheck, PhMagnifyingGlass, PhPlus } from '@phosphor-icons/vue'
4
4
  import type { Component } from 'vue'
5
5
  import { rmPanelOpened, rmPanelRegister } from '../utils/panels'
6
6
 
@@ -37,8 +37,16 @@ const props = withDefaults(defineProps<{
37
37
  searchPlaceholder?: string
38
38
  /** Langue des textes propres au composant (tout selectionner, aucun resultat). */
39
39
  lang?: 'fr' | 'en'
40
+ /**
41
+ * La saisie de recherche peut devenir une valeur nouvelle (des tags) : une
42
+ * ligne « Creer « … » » apparait quand aucune option ne porte exactement ce
43
+ * libelle. La valeur est ajoutee a la selection et `create` previent
44
+ * l'appelant, qui l'ajoute a ses options. Implique la recherche.
45
+ */
46
+ creatable?: boolean
40
47
  }>(), {
41
48
  icon: undefined,
49
+ creatable: false,
42
50
  multiple: true,
43
51
  showSelectAll: false,
44
52
  searchable: 'auto',
@@ -47,8 +55,10 @@ const props = withDefaults(defineProps<{
47
55
  })
48
56
 
49
57
  const texts = computed(() => props.lang === 'en'
50
- ? { selectAll: 'Select all', deselectAll: 'Deselect all', noMatch: 'No match' }
51
- : { selectAll: 'Tout sélectionner', deselectAll: 'Tout désélectionner', noMatch: 'Aucun résultat' })
58
+ ? { selectAll: 'Select all', deselectAll: 'Deselect all', noMatch: 'No match', create: (q: string) => `Create « ${q} »` }
59
+ : { selectAll: 'Tout sélectionner', deselectAll: 'Tout désélectionner', noMatch: 'Aucun résultat', create: (q: string) => `Créer « ${q} »` })
60
+
61
+ const emit = defineEmits<{ create: [value: string] }>()
52
62
 
53
63
  const selected = defineModel<string[]>({ default: () => [] })
54
64
 
@@ -80,9 +90,27 @@ const displayLabel = computed(() => {
80
90
  })
81
91
 
82
92
  const hasSearch = computed(() =>
83
- props.searchable === 'auto' ? props.options.length > SEARCH_THRESHOLD : props.searchable,
93
+ props.creatable || (props.searchable === 'auto' ? props.options.length > SEARCH_THRESHOLD : props.searchable),
84
94
  )
85
95
 
96
+ /** La saisie, quand elle peut devenir une option nouvelle. */
97
+ const creatableValue = computed(() => {
98
+ const q = search.value.trim()
99
+ if (!props.creatable || !q) return ''
100
+ const exists = props.options.some(o => o.label.toLowerCase() === q.toLowerCase())
101
+ return exists ? '' : q
102
+ })
103
+
104
+ function createOption() {
105
+ const value = creatableValue.value
106
+ if (!value) return
107
+ hasUserChanged.value = true
108
+ emit('create', value)
109
+ selected.value = props.multiple ? [...selected.value, value] : [value]
110
+ search.value = ''
111
+ if (!props.multiple) isOpen.value = false
112
+ }
113
+
86
114
  const filteredOptions = computed(() => {
87
115
  if (!hasSearch.value || !search.value.trim()) return props.options
88
116
  const needle = search.value.trim().toLowerCase()
@@ -204,6 +232,7 @@ onUnmounted(() => {
204
232
  type="search"
205
233
  :placeholder="searchPlaceholder"
206
234
  @click.stop
235
+ @keydown.enter.prevent="createOption"
207
236
  />
208
237
  </label>
209
238
  <button
@@ -217,7 +246,16 @@ onUnmounted(() => {
217
246
  <span>{{ allSelected ? texts.deselectAll : texts.selectAll }}</span>
218
247
  </button>
219
248
  <div :class="{ 'filter-chip__options-group': showSelectAll && multiple && options.length > 0 }">
220
- <p v-if="hasSearch && filteredOptions.length === 0" class="filter-chip__no-match">
249
+ <button
250
+ v-if="creatableValue"
251
+ type="button"
252
+ class="filter-chip__option filter-chip__create"
253
+ @click.stop="createOption"
254
+ >
255
+ <PhPlus :size="12" weight="bold" />
256
+ <span>{{ texts.create(creatableValue) }}</span>
257
+ </button>
258
+ <p v-if="hasSearch && filteredOptions.length === 0 && !creatableValue" class="filter-chip__no-match">
221
259
  {{ texts.noMatch }}
222
260
  </p>
223
261
  <template v-for="row in rows" :key="row.key">
@@ -248,5 +286,5 @@ onUnmounted(() => {
248
286
  </template>
249
287
 
250
288
  <style scoped>
251
- .filter-chip-wrapper{position:relative}.filter-chip{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:6px;box-sizing:border-box;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);font-weight:var(--weight-medium);gap:var(--space-8);height:32px;padding:var(--space-8);transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap;width:193px}.filter-chip:hover{border-color:var(--border-default)}.filter-chip__icon{color:var(--color-healthcare);flex-shrink:0}.filter-chip--active{border-color:var(--color-healthcare);box-shadow:0 0 8px 0 rgba(var(--healthcare-rgb),.1)}.filter-chip__label{flex:1;min-width:0;overflow:hidden;text-align:left;text-overflow:ellipsis;white-space:nowrap}.filter-chip__badge{align-items:center;background-color:var(--color-healthcare);border-radius:100px;color:var(--color-text-inverse);display:inline-flex;flex-shrink:0;font-size:var(--font-11);font-weight:600;height:18px;justify-content:center;line-height:1;min-width:18px;padding:0 5px}.filter-chip__caret{color:var(--text-muted);flex-shrink:0;transition:transform var(--duration-fast) var(--ease-in-out)}.filter-chip__caret--open{transform:rotate(180deg)}.filter-chip__dropdown{background-color:var(--bg-surface);border:1px solid var(--border-default);border-radius:6px;box-shadow:0 4px 12px rgba(var(--shadow-rgb),.08),0 2px 4px rgba(var(--shadow-rgb),.04);left:0;max-height:300px;max-width:420px;min-width:380px;overflow-y:auto;padding:var(--space-4);position:absolute;top:calc(100% + 4px);width:-moz-max-content;width:max-content;z-index:50}.filter-chip__search{align-items:center;background-color:var(--bg-surface);border-bottom:1px solid var(--border-soft);color:var(--text-muted);display:flex;gap:var(--space-6);margin:-4px -4px 4px;padding:var(--space-8);position:sticky;top:-4px;z-index:1}.filter-chip__search input{background:none;border:none;color:var(--text-primary);flex:1;font-family:var(--font-sans);font-size:var(--font-14);min-width:0;outline:none}.filter-chip__search input::-webkit-search-cancel-button{cursor:pointer}.filter-chip__no-match{font-size:var(--font-14);margin:0;padding:var(--space-10) var(--space-8)}.filter-chip__no-match,.filter-chip__section{color:var(--text-muted);font-family:var(--font-sans)}.filter-chip__section{border-top:1px solid var(--border-default);font-size:var(--font-12);font-weight:var(--weight-bold);letter-spacing:.04em;margin:8px 0 2px;padding:var(--space-8) var(--space-8) 0;text-transform:uppercase}.filter-chip__section:first-child{border-top:none;margin-top:0;padding-top:0}.filter-chip__option{align-items:center;background:none;border:none;border-radius:4px;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);gap:var(--space-8);padding:var(--space-6) var(--space-8);text-align:left;transition:background-color var(--duration-fast) var(--ease-in-out);width:100%}.filter-chip__option:hover{background-color:var(--bg-surface-2)}.filter-chip__option--selected{color:var(--text-primary);font-weight:var(--weight-medium)}.filter-chip__checkbox{align-items:center;border:1.5px solid var(--border-default);border-radius:3px;display:flex;flex-shrink:0;height:16px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);width:16px}.filter-chip__checkbox--checked{background-color:var(--color-healthcare);border-color:var(--color-healthcare);color:var(--color-text-inverse)}.filter-chip__radio{align-items:center;border:1.5px solid var(--border-default);border-radius:100%;display:flex;flex-shrink:0;height:16px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);width:16px}.filter-chip__radio--checked{border-color:var(--color-healthcare)}.filter-chip__radio--checked:after{background-color:var(--color-healthcare);border-radius:100%;content:"";height:8px;width:8px}.filter-chip__select-all{align-items:center;background:none;border:none;border-radius:4px;color:var(--color-healthcare);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);font-weight:500;gap:var(--space-8);padding:var(--space-6) var(--space-8);text-align:left;width:100%}.filter-chip__select-all:hover{background-color:rgba(var(--healthcare-rgb),.04)}.filter-chip__options-group{border-left:1px solid var(--border-default);margin-left:14px;padding-left:var(--space-6)}.dropdown-enter-active,.dropdown-leave-active{transition:opacity var(--duration-fast) var(--ease-in-out),transform var(--duration-fast) var(--ease-in-out)}.dropdown-enter-from,.dropdown-leave-to{opacity:0;transform:translateY(-4px)}
289
+ .filter-chip-wrapper{position:relative}.filter-chip{align-items:center;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:6px;box-sizing:border-box;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);font-weight:var(--weight-medium);gap:var(--space-8);height:32px;padding:var(--space-8);transition:all var(--duration-fast) var(--ease-in-out);white-space:nowrap;width:193px}.filter-chip:hover{border-color:var(--border-default)}.filter-chip__icon{color:var(--color-healthcare);flex-shrink:0}.filter-chip--active{border-color:var(--color-healthcare);box-shadow:0 0 8px 0 rgba(var(--healthcare-rgb),.1)}.filter-chip__label{flex:1;min-width:0;overflow:hidden;text-align:left;text-overflow:ellipsis;white-space:nowrap}.filter-chip__badge{align-items:center;background-color:var(--color-healthcare);border-radius:100px;color:var(--color-text-inverse);display:inline-flex;flex-shrink:0;font-size:var(--font-11);font-weight:600;height:18px;justify-content:center;line-height:1;min-width:18px;padding:0 5px}.filter-chip__caret{color:var(--text-muted);flex-shrink:0;transition:transform var(--duration-fast) var(--ease-in-out)}.filter-chip__caret--open{transform:rotate(180deg)}.filter-chip__dropdown{background-color:var(--bg-surface);border:1px solid var(--border-default);border-radius:6px;box-shadow:0 4px 12px rgba(var(--shadow-rgb),.08),0 2px 4px rgba(var(--shadow-rgb),.04);left:0;max-height:300px;max-width:420px;min-width:380px;overflow-y:auto;padding:var(--space-4);position:absolute;top:calc(100% + 4px);width:-moz-max-content;width:max-content;z-index:50}.filter-chip__search{align-items:center;background-color:var(--bg-surface);border-bottom:1px solid var(--border-soft);color:var(--text-muted);display:flex;gap:var(--space-6);margin:-4px -4px 4px;padding:var(--space-8);position:sticky;top:-4px;z-index:1}.filter-chip__search input{background:none;border:none;color:var(--text-primary);flex:1;font-family:var(--font-sans);font-size:var(--font-14);min-width:0;outline:none}.filter-chip__search input::-webkit-search-cancel-button{cursor:pointer}.filter-chip__no-match{font-size:var(--font-14);margin:0;padding:var(--space-10) var(--space-8)}.filter-chip__no-match,.filter-chip__section{color:var(--text-muted);font-family:var(--font-sans)}.filter-chip__section{border-top:1px solid var(--border-default);font-size:var(--font-12);font-weight:var(--weight-bold);letter-spacing:.04em;margin:8px 0 2px;padding:var(--space-8) var(--space-8) 0;text-transform:uppercase}.filter-chip__section:first-child{border-top:none;margin-top:0;padding-top:0}.filter-chip__create{color:var(--color-primary);font-weight:var(--weight-medium)}.filter-chip__option{align-items:center;background:none;border:none;border-radius:4px;color:var(--text-secondary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);gap:var(--space-8);padding:var(--space-6) var(--space-8);text-align:left;transition:background-color var(--duration-fast) var(--ease-in-out);width:100%}.filter-chip__option:hover{background-color:var(--bg-surface-2)}.filter-chip__option--selected{color:var(--text-primary);font-weight:var(--weight-medium)}.filter-chip__checkbox{align-items:center;border:1.5px solid var(--border-default);border-radius:3px;display:flex;flex-shrink:0;height:16px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);width:16px}.filter-chip__checkbox--checked{background-color:var(--color-healthcare);border-color:var(--color-healthcare);color:var(--color-text-inverse)}.filter-chip__radio{align-items:center;border:1.5px solid var(--border-default);border-radius:100%;display:flex;flex-shrink:0;height:16px;justify-content:center;transition:all var(--duration-fast) var(--ease-in-out);width:16px}.filter-chip__radio--checked{border-color:var(--color-healthcare)}.filter-chip__radio--checked:after{background-color:var(--color-healthcare);border-radius:100%;content:"";height:8px;width:8px}.filter-chip__select-all{align-items:center;background:none;border:none;border-radius:4px;color:var(--color-healthcare);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:var(--font-14);font-weight:500;gap:var(--space-8);padding:var(--space-6) var(--space-8);text-align:left;width:100%}.filter-chip__select-all:hover{background-color:rgba(var(--healthcare-rgb),.04)}.filter-chip__options-group{border-left:1px solid var(--border-default);margin-left:14px;padding-left:var(--space-6)}.dropdown-enter-active,.dropdown-leave-active{transition:opacity var(--duration-fast) var(--ease-in-out),transform var(--duration-fast) var(--ease-in-out)}.dropdown-enter-from,.dropdown-leave-to{opacity:0;transform:translateY(-4px)}
252
290
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.223",
3
+ "version": "0.1.224",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",