mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-17 05:28:09 +01:00
Compare commits
2 Commits
issue-1057
...
issue-1987
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3aaf2f99bf | ||
|
|
c0e4a4fe5e |
@@ -20,7 +20,7 @@ Its goal is to provide everything related to UI when building a Nuxt app. This i
|
||||
- Keyboard shortcuts
|
||||
- Bundled icons
|
||||
- Fully typed
|
||||
- [Figma Kit](https://www.figma.com/community/file/1436401057300493073)
|
||||
- [Figma Kit](https://www.figma.com/community/file/1288455405058138934)
|
||||
|
||||
Read more on [ui.nuxt.com](https://ui.nuxt.com)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ const { $ui } = useNuxtApp()
|
||||
const links = [{
|
||||
icon: 'i-simple-icons-figma',
|
||||
label: 'Figma Kit',
|
||||
to: 'https://www.figma.com/community/file/1436401057300493073',
|
||||
to: 'https://www.figma.com/community/file/1288455405058138934',
|
||||
target: '_blank'
|
||||
}, {
|
||||
label: 'Playground',
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const options = ref([
|
||||
{ id: 1, name: 'bug', color: 'd73a4a' },
|
||||
{ id: 2, name: 'documentation', color: '0075ca' },
|
||||
{ id: 3, name: 'duplicate', color: 'cfd3d7' },
|
||||
{ id: 4, name: 'enhancement', color: 'a2eeef' },
|
||||
{ id: 5, name: 'good first issue', color: '7057ff' },
|
||||
{ id: 6, name: 'help wanted', color: '008672' },
|
||||
{ id: 7, name: 'invalid', color: 'e4e669' },
|
||||
{ id: 8, name: 'question', color: 'd876e3' },
|
||||
{ id: 9, name: 'wontfix', color: 'ffffff' }
|
||||
])
|
||||
|
||||
const selected = ref([])
|
||||
|
||||
const labels = computed({
|
||||
get: () => selected.value,
|
||||
set: async (labels) => {
|
||||
const promises = labels.map(async (label) => {
|
||||
if (label.id) {
|
||||
return label
|
||||
}
|
||||
|
||||
// In a real app, you would make an API call to create the label
|
||||
const response = {
|
||||
id: options.value.length + 1,
|
||||
name: label.name,
|
||||
color: generateColorFromString(label.name)
|
||||
}
|
||||
|
||||
options.value.push(response)
|
||||
|
||||
return response
|
||||
})
|
||||
|
||||
selected.value = await Promise.all(promises)
|
||||
}
|
||||
})
|
||||
|
||||
function hashCode(str) {
|
||||
let hash = 0
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = str.charCodeAt(i) + ((hash << 5) - hash)
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
function intToRGB(i) {
|
||||
const c = (i & 0x00FFFFFF)
|
||||
.toString(16)
|
||||
.toUpperCase()
|
||||
|
||||
return '00000'.substring(0, 6 - c.length) + c
|
||||
}
|
||||
|
||||
function generateColorFromString(str) {
|
||||
return intToRGB(hashCode(str))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
v-model="labels"
|
||||
by="id"
|
||||
name="labels"
|
||||
:options="options"
|
||||
option-attribute="name"
|
||||
clearable
|
||||
multiple
|
||||
searchable
|
||||
creatable
|
||||
>
|
||||
<template #label>
|
||||
<template v-if="labels.length">
|
||||
<span class="flex items-center -space-x-1">
|
||||
<span v-for="label of labels" :key="label.id" class="flex-shrink-0 w-2 h-2 mt-px rounded-full" :style="{ background: `#${label.color}` }" />
|
||||
</span>
|
||||
<span>{{ labels.length }} label{{ labels.length > 1 ? 's' : '' }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="text-gray-500 dark:text-gray-400 truncate">Select labels</span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<span
|
||||
class="flex-shrink-0 w-2 h-2 mt-px rounded-full"
|
||||
:style="{ background: `#${option.color}` }"
|
||||
/>
|
||||
<span class="truncate">{{ option.name }}</span>
|
||||
</template>
|
||||
|
||||
<template #option-create="{ option }">
|
||||
<span class="flex-shrink-0">New label:</span>
|
||||
<span
|
||||
class="flex-shrink-0 w-2 h-2 mt-px rounded-full -mx-1"
|
||||
:style="{ background: `#${generateColorFromString(option.name)}` }"
|
||||
/>
|
||||
<span class="block truncate">{{ option.name }}</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
@@ -1,105 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const options = ref([
|
||||
{ id: 1, name: 'bug', color: 'd73a4a' },
|
||||
{ id: 2, name: 'documentation', color: '0075ca' },
|
||||
{ id: 3, name: 'duplicate', color: 'cfd3d7' },
|
||||
{ id: 4, name: 'enhancement', color: 'a2eeef' },
|
||||
{ id: 5, name: 'good first issue', color: '7057ff' },
|
||||
{ id: 6, name: 'help wanted', color: '008672' },
|
||||
{ id: 7, name: 'invalid', color: 'e4e669' },
|
||||
{ id: 8, name: 'question', color: 'd876e3' },
|
||||
{ id: 9, name: 'wontfix', color: 'ffffff' }
|
||||
])
|
||||
|
||||
const selected = ref([])
|
||||
|
||||
const labels = computed({
|
||||
get: () => selected.value,
|
||||
set: async (labels) => {
|
||||
const promises = labels.map(async (label) => {
|
||||
if (label.id) {
|
||||
return label
|
||||
}
|
||||
|
||||
// In a real app, you would make an API call to create the label
|
||||
const response = {
|
||||
id: options.value.length + 1,
|
||||
name: label.name,
|
||||
color: generateColorFromString(label.name)
|
||||
}
|
||||
|
||||
options.value.push(response)
|
||||
|
||||
return response
|
||||
})
|
||||
|
||||
selected.value = await Promise.all(promises)
|
||||
}
|
||||
})
|
||||
|
||||
function hashCode(str) {
|
||||
let hash = 0
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = str.charCodeAt(i) + ((hash << 5) - hash)
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
function intToRGB(i) {
|
||||
const c = (i & 0x00FFFFFF)
|
||||
.toString(16)
|
||||
.toUpperCase()
|
||||
|
||||
return '00000'.substring(0, 6 - c.length) + c
|
||||
}
|
||||
|
||||
function generateColorFromString(str) {
|
||||
return intToRGB(hashCode(str))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
v-model="labels"
|
||||
by="id"
|
||||
name="labels"
|
||||
:options="options"
|
||||
option-attribute="name"
|
||||
clearable
|
||||
multiple
|
||||
searchable
|
||||
creatable
|
||||
>
|
||||
<template #label>
|
||||
<template v-if="labels.length">
|
||||
<span class="flex items-center -space-x-1">
|
||||
<span v-for="label of labels" :key="label.id" class="flex-shrink-0 w-2 h-2 mt-px rounded-full" :style="{ background: `#${label.color}` }" />
|
||||
</span>
|
||||
<span>{{ labels.length }} label{{ labels.length > 1 ? 's' : '' }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="text-gray-500 dark:text-gray-400 truncate">Select labels</span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<span
|
||||
class="flex-shrink-0 w-2 h-2 mt-px rounded-full"
|
||||
:style="{ background: `#${option.color}` }"
|
||||
/>
|
||||
<span class="truncate">{{ option.name }}</span>
|
||||
</template>
|
||||
|
||||
<template #option-create="{ option }">
|
||||
<span class="flex-shrink-0">New label:</span>
|
||||
<span
|
||||
class="flex-shrink-0 w-2 h-2 mt-px rounded-full -mx-1"
|
||||
:style="{ background: `#${generateColorFromString(option.name)}` }"
|
||||
/>
|
||||
<span class="block truncate">{{ option.name }}</span>
|
||||
</template>
|
||||
<template #clearable="{ onClear }">
|
||||
<UButton icon="i-heroicons-trash-20-solid" size="xs" class="text-gray-400 dark:text-gray-500" variant="ghost" @click.capture.stop="onClear" />
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
@@ -1,9 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
// Columns
|
||||
const columns = [{
|
||||
key: 'select',
|
||||
class: 'w-2'
|
||||
}, {
|
||||
key: 'id',
|
||||
label: '#',
|
||||
sortable: true
|
||||
@@ -23,7 +20,6 @@ const columns = [{
|
||||
|
||||
const selectedColumns = ref(columns)
|
||||
const columnsTable = computed(() => columns.filter(column => selectedColumns.value.includes(column)))
|
||||
const excludeSelectColumn = computed(() => columns.filter(v => v.key !== 'select'))
|
||||
|
||||
// Selected Rows
|
||||
const selectedRows = ref([])
|
||||
@@ -157,7 +153,7 @@ const { data: todos, status } = await useLazyAsyncData<{
|
||||
</UButton>
|
||||
</UDropdown>
|
||||
|
||||
<USelectMenu v-model="selectedColumns" :options="excludeSelectColumn" multiple>
|
||||
<USelectMenu v-model="selectedColumns" :options="columns" multiple>
|
||||
<UButton
|
||||
icon="i-heroicons-view-columns"
|
||||
color="gray"
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const people = [{
|
||||
id: 1,
|
||||
name: 'Lindsay Walton',
|
||||
title: 'Front-end Developer',
|
||||
email: 'lindsay.walton@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Courtney Henry',
|
||||
title: 'Designer',
|
||||
email: 'courtney.henry@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Tom Cook',
|
||||
title: 'Director of Product',
|
||||
email: 'tom.cook@example.com',
|
||||
role: 'Member'
|
||||
}, {
|
||||
id: 4,
|
||||
name: 'Whitney Francis',
|
||||
title: 'Copywriter',
|
||||
email: 'whitney.francis@example.com',
|
||||
role: 'Admin'
|
||||
}, {
|
||||
id: 5,
|
||||
name: 'Leonard Krasner',
|
||||
title: 'Senior Designer',
|
||||
email: 'leonard.krasner@example.com',
|
||||
role: 'Owner'
|
||||
}, {
|
||||
id: 6,
|
||||
name: 'Floyd Miles',
|
||||
title: 'Principal Designer',
|
||||
email: 'floyd.miles@example.com',
|
||||
role: 'Member'
|
||||
}]
|
||||
|
||||
const selected = ref([people[1]])
|
||||
|
||||
const columns = [{
|
||||
key: 'id',
|
||||
label: 'ID'
|
||||
}, {
|
||||
key: 'name',
|
||||
label: 'User name'
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Job position'
|
||||
}, {
|
||||
key: 'email',
|
||||
label: 'Email'
|
||||
}, {
|
||||
key: 'role'
|
||||
}, {
|
||||
key: 'select',
|
||||
class: 'w-2'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable v-model="selected" :rows="people" :columns="columns" />
|
||||
</template>
|
||||
@@ -16,7 +16,7 @@ Its goal is to provide everything related to UI when building a Nuxt app. This i
|
||||
- Keyboard shortcuts
|
||||
- Bundled icons
|
||||
- Fully typed
|
||||
- [Figma Kit](https://www.figma.com/community/file/1436401057300493073)
|
||||
- [Figma Kit](https://www.figma.com/community/file/1288455405058138934)
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
@@ -156,38 +156,7 @@ Use the `searchableLazy` prop to control the immediacy of data requests.
|
||||
---
|
||||
component: 'select-menu-example-search-async'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
## Clearable
|
||||
The `clearable` prop allows users to easily remove their selected option(s) with a clear button.
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'select-menu-example-clearable'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-52'
|
||||
---
|
||||
::
|
||||
|
||||
|
||||
### Customization
|
||||
#### Slot Props
|
||||
The slot provides four key props:
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `selected` | `Object` | The currently selected value/item in the component |
|
||||
| `disabled` | `Boolean` | Whether the component is in a disabled state |
|
||||
| `loading` | `Boolean` | Whether the component is in a loading state |
|
||||
| `onClear` | `Function` | Callback function to clear the selected value when the clear button is clicked |
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'select-menu-example-clearable-customization'
|
||||
componentProps:
|
||||
class: 'w-full lg:w-52'
|
||||
class: 'w-full lg:w-48'
|
||||
---
|
||||
::
|
||||
|
||||
|
||||
@@ -285,65 +285,6 @@ componentProps:
|
||||
---
|
||||
::
|
||||
|
||||
|
||||
#### Event Selectable
|
||||
The `UTable` component provides two key events for handling row selection:
|
||||
|
||||
##### ***@select:all***
|
||||
The `@select:all` event is emitted when the header checkbox in a selectable table is toggled. This event returns a boolean value indicating whether all rows are selected (true) or deselected (false).
|
||||
|
||||
##### ***@update:modelValue***
|
||||
The `@update:modelValue` event is emitted whenever the selection state changes, including both individual row selection and bulk selection. This event returns an array containing the currently selected rows.
|
||||
|
||||
Here's how to implement both events:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const selected = ref([])
|
||||
|
||||
const onHandleSelectAll = (isSelected: boolean) => {
|
||||
console.log('All rows selected:', isSelected)
|
||||
}
|
||||
|
||||
const onUpdateSelection = (selectedRows: any[]) => {
|
||||
console.log('Currently selected rows:', selectedRows)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable
|
||||
v-model="selected"
|
||||
:rows="people"
|
||||
@select:all="onHandleSelectAll"
|
||||
@update:modelValue="onUpdateSelection"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
|
||||
#### Single Select Mode
|
||||
Control how the select function allows only one row to be selected at a time.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<!-- Allow only one row to be selectable at a time -->
|
||||
<UTable :single-select="true" />
|
||||
</template>
|
||||
```
|
||||
|
||||
#### Checkbox Placement
|
||||
You can customize the checkbox column position by using the `select` key in the `columns` configuration.
|
||||
|
||||
::component-example{class="grid"}
|
||||
---
|
||||
extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-dynamically-render-selectable'
|
||||
componentProps:
|
||||
class: 'flex-1'
|
||||
---
|
||||
::
|
||||
|
||||
### Contextmenu
|
||||
|
||||
Use the `contextmenu` listener on your Table to make the rows righ-clickable. The function will receive the original event as the first argument and the row as the second argument.
|
||||
@@ -452,6 +393,7 @@ Controls whether multiple rows can be expanded simultaneously in the table.
|
||||
<!-- Or simply -->
|
||||
<UTable />
|
||||
</template>
|
||||
|
||||
```
|
||||
|
||||
#### Disable Row Expansion
|
||||
@@ -592,82 +534,6 @@ componentProps:
|
||||
---
|
||||
::
|
||||
|
||||
### `select-header`
|
||||
This slot allows you to customize the checkbox appearance in the table header for selecting all rows at once while using feature [Selectable](#selectable).
|
||||
|
||||
#### Usage
|
||||
```vue
|
||||
<template>
|
||||
<UTable v-model="selectable">
|
||||
<template #select-header="{ checked, change, indeterminate }">
|
||||
<!-- Place your custom component here -->
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `checked` | `Boolean` | Indicates if all rows are selected |
|
||||
| `change` | `Function` | Function to handle selection state changes. Must receive a boolean value (true/false) |
|
||||
| `indeterminate` | `Boolean` | Indicates partial selection (when some rows are selected) |
|
||||
|
||||
#### Example
|
||||
```vue
|
||||
<template>
|
||||
<UTable>
|
||||
<!-- Header checkbox customization -->
|
||||
<template #select-header="{ indeterminate, checked, change }">
|
||||
<input
|
||||
type="checkbox"
|
||||
:indeterminate="indeterminate"
|
||||
:checked="checked"
|
||||
@change="e => change(e.target.checked)"
|
||||
/>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
```
|
||||
|
||||
### `select-data`
|
||||
This slot allows you to customize the checkbox appearance for each row in the table while using feature [Selectable](#selectable).
|
||||
|
||||
#### Usage
|
||||
```vue
|
||||
<template>
|
||||
<UTable v-model="selectable">
|
||||
<template #select-data="{ checked, change }">
|
||||
<!-- Place your custom component here -->
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `checked` | `Boolean` | Indicates if the current row is selected |
|
||||
| `change` | `Function` | Function to handle selection state changes. Must receive a boolean value (true/false) |
|
||||
|
||||
#### Example
|
||||
```vue
|
||||
<template>
|
||||
<UTable>
|
||||
<!-- Row checkbox customization -->
|
||||
<template #select-data="{ checked, change }">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="checked"
|
||||
@change="e => change(e.target.checked)"
|
||||
/>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
```
|
||||
|
||||
### `expand-action`
|
||||
|
||||
The `#expand-action` slot allows you to customize the expansion control interface for expandable table rows. This feature provides a flexible way to implement custom expand/collapse functionality while maintaining access to essential row data and state.
|
||||
|
||||
@@ -98,7 +98,7 @@ const communityLinks = computed(() => [{
|
||||
const resourcesLinks = [{
|
||||
icon: 'i-simple-icons-figma',
|
||||
label: 'Figma Kit',
|
||||
to: 'https://www.figma.com/community/file/1436401057300493073',
|
||||
to: 'https://www.figma.com/community/file/1288455405058138934',
|
||||
target: '_blank'
|
||||
}, {
|
||||
label: 'Playground',
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"@headlessui/tailwindcss": "^0.2.1",
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
"@iconify-json/heroicons": "^1.2.1",
|
||||
"@nuxt/icon": "^1.7.2",
|
||||
"@nuxt/icon": "^1.6.1",
|
||||
"@nuxt/kit": "^3.14.159",
|
||||
"@nuxtjs/color-mode": "^3.5.2",
|
||||
"@nuxtjs/tailwindcss": "^6.12.2",
|
||||
@@ -80,8 +80,6 @@
|
||||
"resolutions": {
|
||||
"@nuxt/ui": "workspace:*",
|
||||
"@nuxt/content": "2.13.2",
|
||||
"@nuxtjs/mdc": "0.9.0",
|
||||
"nuxt": "3.13.2",
|
||||
"@nuxt/kit": "3.13.2"
|
||||
"@nuxtjs/mdc": "0.9.0"
|
||||
}
|
||||
}
|
||||
|
||||
1653
pnpm-lock.yaml
generated
1653
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -8,27 +8,28 @@
|
||||
</slot>
|
||||
<thead :class="ui.thead">
|
||||
<tr :class="ui.tr.base">
|
||||
<th v-if="modelValue" scope="col" :class="ui.checkbox.padding">
|
||||
<UCheckbox
|
||||
:model-value="isAllRowChecked"
|
||||
:indeterminate="indeterminate"
|
||||
v-bind="ui.default.checkbox"
|
||||
aria-label="Select all"
|
||||
@change="onChange"
|
||||
/>
|
||||
</th>
|
||||
|
||||
<th v-if="expand" scope="col" :class="ui.tr.base">
|
||||
<span class="sr-only">Expand</span>
|
||||
</th>
|
||||
|
||||
<th
|
||||
v-for="(column, index) in columns"
|
||||
:key="index"
|
||||
scope="col"
|
||||
:class="[ui.th.base, ui.th.padding, ui.th.color, ui.th.font, ui.th.size, column.key === 'select' && ui.checkbox.padding, column.class]"
|
||||
:class="[ui.th.base, ui.th.padding, ui.th.color, ui.th.font, ui.th.size, column.class]"
|
||||
:aria-sort="getAriaSort(column)"
|
||||
>
|
||||
<slot v-if="!singleSelect && modelValue && (column.key === 'select' || shouldRenderColumnInFirstPlace(index, 'select'))" name="select-header" :indeterminate="indeterminate" :checked="isAllRowChecked" :change="onChange">
|
||||
<UCheckbox
|
||||
:model-value="isAllRowChecked"
|
||||
:indeterminate="indeterminate"
|
||||
v-bind="ui.default.checkbox"
|
||||
aria-label="Select all"
|
||||
@change="onChange"
|
||||
/>
|
||||
</slot>
|
||||
|
||||
<slot v-else :name="`${column.key}-header`" :column="column" :sort="sort" :on-sort="onSort">
|
||||
<slot :name="`${column.key}-header`" :column="column" :sort="sort" :on-sort="onSort">
|
||||
<UButton
|
||||
v-if="column.sortable"
|
||||
v-bind="{ ...(ui.default.sortButton || {}), ...sortButton }"
|
||||
@@ -76,7 +77,16 @@
|
||||
|
||||
<template v-else>
|
||||
<template v-for="(row, index) in rows" :key="index">
|
||||
<tr :class="[ui.tr.base, isSelected(row) && ui.tr.selected, isExpanded(row) && ui.tr.expanded, $attrs.onSelect && ui.tr.active, row?.class]" @click="() => onSelect(row)" @contextmenu="(event) => onContextmenu(event, row)">
|
||||
<tr :class="[ui.tr.base, isSelected(row) && ui.tr.selected, isExpanded(row) && ui.tr.expanded, ($attrs.onSelect || $attrs.onContextmenu) && ui.tr.active, row?.class]" @click="() => onSelect(row)" @contextmenu="(event) => onContextmenu(event, row)">
|
||||
<td v-if="modelValue" :class="ui.checkbox.padding">
|
||||
<UCheckbox
|
||||
:model-value="isSelected(row)"
|
||||
v-bind="ui.default.checkbox"
|
||||
aria-label="Select row"
|
||||
@change="onChangeCheckbox($event, row)"
|
||||
@click.capture.stop="() => onSelect(row)"
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
v-if="expand"
|
||||
:class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size]"
|
||||
@@ -92,26 +102,8 @@
|
||||
@click.capture.stop="toggleOpened(row)"
|
||||
/>
|
||||
</td>
|
||||
<td v-for="(column, subIndex) in columns" :key="subIndex" :class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size, column?.rowClass, row[column.key]?.class, column.key === 'select' && ui.checkbox.padding]">
|
||||
<slot v-if="modelValue && (column.key === 'select' || shouldRenderColumnInFirstPlace(subIndex, 'select')) " name="select-data" :checked="isSelected(row)" :change="(ev: boolean) => onChangeCheckbox(ev, row)">
|
||||
<UCheckbox
|
||||
:model-value="isSelected(row)"
|
||||
v-bind="ui.default.checkbox"
|
||||
aria-label="Select row"
|
||||
@change="onChangeCheckbox($event, row)"
|
||||
@click.capture.stop="() => onSelect(row)"
|
||||
/>
|
||||
</slot>
|
||||
|
||||
<slot
|
||||
v-else
|
||||
:key="retriggerSlot"
|
||||
:name="`${column.key}-data`"
|
||||
:column="column"
|
||||
:row="row"
|
||||
:index="index"
|
||||
:get-row-data="(defaultValue) => getRowData(row, column.key, defaultValue)"
|
||||
>
|
||||
<td v-for="(column, subIndex) in columns" :key="subIndex" :class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size, column?.rowClass, row[column.key]?.class]">
|
||||
<slot :name="`${column.key}-data`" :column="column" :row="row" :index="index" :get-row-data="(defaultValue) => getRowData(row, column.key, defaultValue)">
|
||||
{{ getRowData(row, column.key) }}
|
||||
</slot>
|
||||
</td>
|
||||
@@ -133,12 +125,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, toRaw, toRef, watch } from 'vue'
|
||||
import { computed, defineComponent, toRaw, toRef } from 'vue'
|
||||
import type { PropType, AriaAttributes } from 'vue'
|
||||
import { upperFirst } from 'scule'
|
||||
import { defu } from 'defu'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { isEqual } from 'ohash'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import UProgress from '../elements/Progress.vue'
|
||||
@@ -153,7 +144,7 @@ import { table } from '#ui/ui.config'
|
||||
const config = mergeConfig<typeof table>(appConfig.ui.strategy, appConfig.ui.table, table)
|
||||
|
||||
function defaultComparator<T>(a: T, z: T): boolean {
|
||||
return isEqual(a, z)
|
||||
return JSON.stringify(a) === JSON.stringify(z)
|
||||
}
|
||||
|
||||
function defaultSort(a: any, b: any, direction: 'asc' | 'desc') {
|
||||
@@ -168,14 +159,6 @@ function defaultSort(a: any, b: any, direction: 'asc' | 'desc') {
|
||||
}
|
||||
}
|
||||
|
||||
function getStringifiedSet(arr: TableRow[]) {
|
||||
return new Set(arr.map(item => JSON.stringify(item)))
|
||||
}
|
||||
|
||||
function accessor<T extends Record<string, any>>(key: string) {
|
||||
return (obj: T) => get(obj, key)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
UIcon,
|
||||
@@ -238,7 +221,7 @@ export default defineComponent({
|
||||
default: false
|
||||
},
|
||||
loadingState: {
|
||||
type: Object as PropType<{ icon: string, label: string } | null>,
|
||||
type: Object as PropType<{ icon: string, label: string }>,
|
||||
default: () => config.default.loadingState
|
||||
},
|
||||
emptyState: {
|
||||
@@ -264,13 +247,9 @@ export default defineComponent({
|
||||
multipleExpand: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
singleSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'update:sort', 'update:expand', 'select:all'],
|
||||
emits: ['update:modelValue', 'update:sort', 'update:expand'],
|
||||
setup(props, { emit, attrs: $attrs }) {
|
||||
const { ui, attrs } = useUI('table', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
@@ -285,8 +264,6 @@ export default defineComponent({
|
||||
})
|
||||
})
|
||||
|
||||
const retriggerSlot = ref(null)
|
||||
|
||||
const savedSort = { column: sort.value.column, direction: null }
|
||||
|
||||
const rows = computed(() => {
|
||||
@@ -315,6 +292,8 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
const getStringifiedSet = (arr: TableRow[]) => new Set(arr.map(item => JSON.stringify(item)))
|
||||
|
||||
const totalRows = computed(() => props.rows.length)
|
||||
|
||||
const countCheckedRow = computed(() => {
|
||||
@@ -349,6 +328,10 @@ export default defineComponent({
|
||||
return props.by(a, z)
|
||||
}
|
||||
|
||||
function accessor<T extends Record<string, any>>(key: string) {
|
||||
return (obj: T) => get(obj, key)
|
||||
}
|
||||
|
||||
function isSelected(row: TableRow) {
|
||||
if (!props.modelValue) {
|
||||
return false
|
||||
@@ -372,11 +355,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function onSelect(row: TableRow) {
|
||||
const selection = window.getSelection()
|
||||
if (selection && selection.toString().length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!$attrs.onSelect) {
|
||||
return
|
||||
}
|
||||
@@ -415,12 +393,11 @@ export default defineComponent({
|
||||
} else {
|
||||
selected.value = []
|
||||
}
|
||||
emit('select:all', checked)
|
||||
}
|
||||
|
||||
function onChangeCheckbox(checked: boolean, row: TableRow) {
|
||||
if (checked) {
|
||||
selected.value = props.singleSelect ? [row] : [...selected.value, row]
|
||||
selected.value.push(row)
|
||||
} else {
|
||||
const index = selected.value.findIndex(item => compare(item, row))
|
||||
selected.value.splice(index, 1)
|
||||
@@ -435,13 +412,6 @@ export default defineComponent({
|
||||
return expand.value?.openedRows ? expand.value.openedRows.some(openedRow => compare(openedRow, row)) : false
|
||||
}
|
||||
|
||||
function shouldRenderColumnInFirstPlace(index: number, key: string) {
|
||||
if (!props.columns) {
|
||||
return index === 0
|
||||
}
|
||||
return index === 0 && !props.columns.find(col => col.key === key)
|
||||
}
|
||||
|
||||
function toggleOpened(row: TableRow) {
|
||||
expand.value = {
|
||||
openedRows: isExpanded(row) ? expand.value.openedRows.filter(v => !compare(v, row)) : props.multipleExpand ? [...expand.value.openedRows, row] : [row],
|
||||
@@ -469,12 +439,6 @@ export default defineComponent({
|
||||
return undefined
|
||||
}
|
||||
|
||||
watch(rows, () => {
|
||||
retriggerSlot.value = new Date()
|
||||
}, {
|
||||
deep: true
|
||||
})
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
@@ -501,9 +465,7 @@ export default defineComponent({
|
||||
getRowData,
|
||||
toggleOpened,
|
||||
getAriaSort,
|
||||
isExpanded,
|
||||
shouldRenderColumnInFirstPlace,
|
||||
retriggerSlot
|
||||
isExpanded
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -252,10 +252,10 @@ async function validateJoiSchema(
|
||||
schema: JoiSchema
|
||||
): Promise<ValidateReturnSchema<typeof state>> {
|
||||
try {
|
||||
const result = await schema.validateAsync(state, { abortEarly: false })
|
||||
await schema.validateAsync(state, { abortEarly: false })
|
||||
return {
|
||||
errors: null,
|
||||
result
|
||||
result: state
|
||||
}
|
||||
} catch (error) {
|
||||
if (isJoiError(error)) {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
v-slot="{ active, selected, disabled: optionDisabled }"
|
||||
:key="index"
|
||||
as="template"
|
||||
:value="valueAttribute ? accessor(option, valueAttribute) : option"
|
||||
:value="valueAttribute ? option[valueAttribute] : option"
|
||||
:disabled="option.disabled"
|
||||
>
|
||||
<li :class="[uiMenu.option.base, uiMenu.option.rounded, uiMenu.option.padding, uiMenu.option.size, uiMenu.option.color, active ? uiMenu.option.active : uiMenu.option.inactive, selected && uiMenu.option.selected, optionDisabled && uiMenu.option.disabled]">
|
||||
@@ -104,7 +104,6 @@ import {
|
||||
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
||||
import { defu } from 'defu'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { isEqual } from 'ohash'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
@@ -309,28 +308,8 @@ export default defineComponent({
|
||||
return
|
||||
}
|
||||
|
||||
function getValue(value: any) {
|
||||
if (props.valueAttribute) {
|
||||
return accessor(value, props.valueAttribute)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
function compareValues(value1: any, value2: any) {
|
||||
if (props.by && typeof value1 === 'object' && typeof value2 === 'object') {
|
||||
return isEqual(value1[props.by], value2[props.by])
|
||||
}
|
||||
return isEqual(value1, value2)
|
||||
}
|
||||
|
||||
if (props.valueAttribute) {
|
||||
const option = options.value.find((option) => {
|
||||
const optionValue = getValue(option)
|
||||
|
||||
return compareValues(optionValue, props.modelValue)
|
||||
})
|
||||
|
||||
const option = options.value.find(option => option[props.valueAttribute] === props.modelValue)
|
||||
return option ? accessor(option, props.optionAttribute) : null
|
||||
} else {
|
||||
return ['string', 'number'].includes(typeof props.modelValue) ? props.modelValue : accessor(props.modelValue as Record<string, any>, props.optionAttribute)
|
||||
|
||||
@@ -39,18 +39,6 @@
|
||||
<span v-if="label" :class="uiMenu.label">{{ label }}</span>
|
||||
<span v-else :class="uiMenu.label">{{ placeholder || ' ' }}</span>
|
||||
</slot>
|
||||
<span v-if="canClearValue" :class="clearableWrapperClass">
|
||||
<slot name="clearable" :selected="selected" :disabled="disabled" :loading="loading" @clear="onClear">
|
||||
<UButton
|
||||
:icon="clearableIcon"
|
||||
size="xs"
|
||||
class="p-0"
|
||||
:class="clearableButtonClass"
|
||||
variant="ghost"
|
||||
@click.capture.stop="onClear"
|
||||
/>
|
||||
</slot>
|
||||
</span>
|
||||
|
||||
<span v-if="(isTrailing && trailingIconName) || $slots.trailing" :class="trailingWrapperIconClass">
|
||||
<slot name="trailing" :selected="selected" :disabled="disabled" :loading="loading">
|
||||
@@ -83,7 +71,7 @@
|
||||
v-slot="{ active, selected: optionSelected, disabled: optionDisabled }"
|
||||
:key="index"
|
||||
as="template"
|
||||
:value="valueAttribute ? accessor(option, valueAttribute) : option"
|
||||
:value="valueAttribute ? option[valueAttribute] : option"
|
||||
:disabled="option.disabled"
|
||||
>
|
||||
<li :class="[uiMenu.option.base, uiMenu.option.rounded, uiMenu.option.padding, uiMenu.option.size, uiMenu.option.color, active ? uiMenu.option.active : uiMenu.option.inactive, optionSelected && uiMenu.option.selected, optionDisabled && uiMenu.option.disabled]">
|
||||
@@ -152,7 +140,6 @@ import {
|
||||
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
||||
import { defu } from 'defu'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { isEqual } from 'ohash'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
@@ -161,7 +148,6 @@ import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { get, mergeConfig } from '../../utils'
|
||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { SelectSize, SelectColor, SelectVariant, PopperOptions, Strategy, DeepPartial } from '../../types/index'
|
||||
import type { Button } from '../../types/button'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { select, selectMenu } from '#ui/ui.config'
|
||||
@@ -346,18 +332,9 @@ export default defineComponent({
|
||||
uiMenu: {
|
||||
type: Object as PropType<DeepPartial<typeof configMenu> & { strategy?: Strategy }>,
|
||||
default: () => ({})
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
clearableIcon: {
|
||||
type: String,
|
||||
default: () => config.default.clerableIcon
|
||||
}
|
||||
|
||||
},
|
||||
emits: ['update:modelValue', 'update:query', 'open', 'close', 'change', 'clear'],
|
||||
emits: ['update:modelValue', 'update:query', 'open', 'close', 'change'],
|
||||
setup(props, { emit, slots }) {
|
||||
if (import.meta.dev && props.multiple && !Array.isArray(props.modelValue)) {
|
||||
console.warn(`[@nuxt/ui] The USelectMenu components needs to have a modelValue of type Array when using the multiple prop. Got '${typeof props.modelValue}' instead.`, props.modelValue)
|
||||
@@ -387,53 +364,39 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const selected = computed(() => {
|
||||
function compareValues(value1: any, value2: any) {
|
||||
if (props.by && typeof value1 === 'object' && typeof value2 === 'object') {
|
||||
return isEqual(value1[props.by], value2[props.by])
|
||||
}
|
||||
return isEqual(value1, value2)
|
||||
}
|
||||
|
||||
function getValue(value: any) {
|
||||
if (props.valueAttribute) {
|
||||
return accessor(value, props.valueAttribute)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
if (props.multiple) {
|
||||
const modelValue = props.modelValue
|
||||
if (!Array.isArray(modelValue) || !modelValue.length) {
|
||||
if (!Array.isArray(props.modelValue) || !props.modelValue.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
return options.value.filter((option) => {
|
||||
const optionValue = getValue(option)
|
||||
return modelValue.some(value => compareValues(value, optionValue))
|
||||
})
|
||||
if (props.valueAttribute) {
|
||||
return options.value.filter(option => (props.modelValue as any[]).includes(option[props.valueAttribute]))
|
||||
}
|
||||
return options.value.filter(option => (props.modelValue as any[]).includes(option))
|
||||
}
|
||||
|
||||
return options.value.find((option) => {
|
||||
const optionValue = getValue(option)
|
||||
return compareValues(optionValue, toRaw(props.modelValue))
|
||||
}) ?? props.modelValue
|
||||
if (props.valueAttribute) {
|
||||
return options.value.find(option => option[props.valueAttribute] === props.modelValue)
|
||||
}
|
||||
return options.value.find(option => option === props.modelValue)
|
||||
})
|
||||
|
||||
const label = computed(() => {
|
||||
if (!selected.value) return null
|
||||
|
||||
if (props.valueAttribute) {
|
||||
return accessor(selected.value as Record<string, any>, props.optionAttribute)
|
||||
if (props.multiple) {
|
||||
if (Array.isArray(props.modelValue) && props.modelValue.length) {
|
||||
return `${selected.value.length} selected`
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} else if (props.modelValue !== undefined && props.modelValue !== null) {
|
||||
if (props.valueAttribute) {
|
||||
return accessor(selected.value, props.optionAttribute) ?? null
|
||||
} else {
|
||||
return ['string', 'number'].includes(typeof props.modelValue) ? props.modelValue : accessor(props.modelValue as Record<string, any>, props.optionAttribute)
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(props.modelValue) && props.modelValue.length) {
|
||||
return `${props.modelValue.length} selected`
|
||||
} else if (['string', 'number'].includes(typeof props.modelValue)) {
|
||||
return props.modelValue
|
||||
}
|
||||
|
||||
return accessor(props.modelValue as Record<string, any>, props.optionAttribute)
|
||||
return null
|
||||
})
|
||||
|
||||
const selectClass = computed(() => {
|
||||
@@ -468,23 +431,6 @@ export default defineComponent({
|
||||
return props.leadingIcon || props.icon
|
||||
})
|
||||
|
||||
const canClearValue = computed(() => props.clearable && (Array.isArray(selected.value) ? selected.value.length > 0 : !!selected.value))
|
||||
|
||||
const clearableWrapperClass = computed(() => {
|
||||
return twJoin(
|
||||
ui.value.icon.clearable.wrapper,
|
||||
ui.value.icon.clearable.padding[size.value]
|
||||
)
|
||||
})
|
||||
|
||||
const clearableButtonClass = computed(() => {
|
||||
return twJoin(
|
||||
ui.value.icon.base,
|
||||
color.value && appConfig.ui.colors.includes(color.value) && ui.value.icon.color.replaceAll('{color}', color.value),
|
||||
props.loading && ui.value.icon.loading
|
||||
)
|
||||
})
|
||||
|
||||
const trailingIconName = computed(() => {
|
||||
if (props.loading && !isLeading.value) {
|
||||
return props.loadingIcon
|
||||
@@ -495,9 +441,9 @@ export default defineComponent({
|
||||
|
||||
const leadingWrapperIconClass = computed(() => {
|
||||
return twJoin(
|
||||
ui.value.icon.leading.wrapper,
|
||||
ui.value.icon.leading.pointer,
|
||||
ui.value.icon.leading.padding[size.value]
|
||||
uiMenu.value.icon.leading.wrapper,
|
||||
uiMenu.value.icon.leading.pointer,
|
||||
uiMenu.value.icon.leading.padding[size.value]
|
||||
)
|
||||
})
|
||||
|
||||
@@ -512,8 +458,9 @@ export default defineComponent({
|
||||
|
||||
const trailingWrapperIconClass = computed(() => {
|
||||
return twJoin(
|
||||
ui.value.icon.trailing.wrapper,
|
||||
ui.value.icon.trailing.padding[size.value]
|
||||
uiMenu.value.icon.trailing.wrapper,
|
||||
uiMenu.value.icon.trailing.pointer,
|
||||
uiMenu.value.icon.trailing.padding[size.value]
|
||||
)
|
||||
})
|
||||
|
||||
@@ -587,7 +534,7 @@ export default defineComponent({
|
||||
return ['string', 'number'].includes(typeof props.modelValue) ? query.value : { [props.optionAttribute]: query.value }
|
||||
})
|
||||
|
||||
function handleClearSearchOnClose() {
|
||||
function clearOnClose() {
|
||||
if (props.clearSearchOnClose) {
|
||||
query.value = ''
|
||||
}
|
||||
@@ -597,7 +544,7 @@ export default defineComponent({
|
||||
if (value) {
|
||||
emit('open')
|
||||
} else {
|
||||
handleClearSearchOnClose()
|
||||
clearOnClose()
|
||||
emit('close')
|
||||
emitFormBlur()
|
||||
}
|
||||
@@ -617,28 +564,6 @@ export default defineComponent({
|
||||
query.value = event.target.value
|
||||
}
|
||||
|
||||
function onClear() {
|
||||
if (canClearValue.value) {
|
||||
emit('update:modelValue', props.multiple ? [] : null)
|
||||
emit('clear')
|
||||
emitFormChange()
|
||||
}
|
||||
}
|
||||
|
||||
function trailingSlotProps() {
|
||||
const slotProps: Record<string, any> = {
|
||||
selected: selected.value,
|
||||
loading: props.loading,
|
||||
disabled: props.disabled
|
||||
}
|
||||
|
||||
if (props.clearable) {
|
||||
slotProps.onClear = onClear
|
||||
}
|
||||
|
||||
return slotProps
|
||||
}
|
||||
|
||||
provideUseId(() => useId())
|
||||
|
||||
return {
|
||||
@@ -658,7 +583,6 @@ export default defineComponent({
|
||||
label,
|
||||
accessor,
|
||||
isLeading,
|
||||
onClear,
|
||||
isTrailing,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
selectClass,
|
||||
@@ -673,11 +597,7 @@ export default defineComponent({
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
query,
|
||||
onUpdate,
|
||||
onQueryChange,
|
||||
trailingSlotProps,
|
||||
canClearValue,
|
||||
clearableWrapperClass,
|
||||
clearableButtonClass
|
||||
onQueryChange
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="notifications.length" :class="wrapperClass" role="region" v-bind="attrs">
|
||||
<div :class="ui.container">
|
||||
<div :class="wrapperClass" role="region" v-bind="attrs">
|
||||
<div v-if="notifications.length" :class="ui.container">
|
||||
<div v-for="notification of notifications" :key="notification.id">
|
||||
<UNotification
|
||||
v-bind="notification"
|
||||
|
||||
@@ -98,18 +98,6 @@ export default {
|
||||
'lg': 'px-3.5',
|
||||
'xl': 'px-3.5'
|
||||
}
|
||||
},
|
||||
clearable: {
|
||||
wrapper: 'absolute inset-y-0 end-6 flex items-center',
|
||||
pointer: 'pointer-events-auto',
|
||||
padding: {
|
||||
'2xs': 'px-2',
|
||||
'xs': 'px-2.5',
|
||||
'sm': 'px-2.5',
|
||||
'md': 'px-3',
|
||||
'lg': 'px-3.5',
|
||||
'xl': 'px-3.5'
|
||||
}
|
||||
}
|
||||
},
|
||||
default: {
|
||||
|
||||
@@ -9,7 +9,6 @@ export default {
|
||||
color: 'white',
|
||||
variant: 'outline',
|
||||
loadingIcon: 'i-heroicons-arrow-path-20-solid',
|
||||
trailingIcon: 'i-heroicons-chevron-down-20-solid',
|
||||
clerableIcon: 'i-heroicons-x-mark-20-solid'
|
||||
trailingIcon: 'i-heroicons-chevron-down-20-solid'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { arrow } from '../popper'
|
||||
import inputMenu from './inputMenu'
|
||||
import input from './input'
|
||||
|
||||
export default {
|
||||
...inputMenu,
|
||||
select: 'inline-flex items-center text-left cursor-default',
|
||||
select: 'inline-flex items-center text-left cursor-default [&:disabled_*]:pointer-events-none',
|
||||
input: 'block w-[calc(100%+0.5rem)] focus:ring-transparent text-sm px-3 py-1.5 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 border-0 border-b border-gray-200 dark:border-gray-700 sticky -top-1 -mt-1 mb-1 -mx-1 z-10 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none',
|
||||
required: 'absolute inset-0 w-px opacity-0 cursor-default',
|
||||
label: 'block truncate',
|
||||
@@ -20,6 +21,18 @@ export default {
|
||||
popper: {
|
||||
placement: 'bottom-end'
|
||||
},
|
||||
icon: {
|
||||
...input.icon,
|
||||
leading: {
|
||||
...input.icon.leading,
|
||||
pointer: 'pointer-events-auto'
|
||||
},
|
||||
trailing: {
|
||||
...input.icon.trailing,
|
||||
pointer: 'pointer-events-auto'
|
||||
}
|
||||
|
||||
},
|
||||
default: {
|
||||
selectedIcon: 'i-heroicons-check-20-solid',
|
||||
clearSearchOnClose: false,
|
||||
|
||||
Reference in New Issue
Block a user