mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 12:39:35 +01:00
Compare commits
16 Commits
feat/detec
...
v3.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be12aa4633 | ||
|
|
6058a13ff5 | ||
|
|
0c827d2981 | ||
|
|
408ebf0f00 | ||
|
|
63c4d97986 | ||
|
|
10259c477c | ||
|
|
3f0856c288 | ||
|
|
dbdb4b6a38 | ||
|
|
32b0fbf594 | ||
|
|
47be7e1b71 | ||
|
|
d623b37e76 | ||
|
|
6c52b5146d | ||
|
|
f92bac64a9 | ||
|
|
416342486d | ||
|
|
07781c3fb4 | ||
|
|
036ba4c57f |
42
CHANGELOG.md
42
CHANGELOG.md
@@ -1,5 +1,47 @@
|
||||
# Changelog
|
||||
|
||||
## [3.0.0-alpha.13](https://github.com/nuxt/ui/compare/v3.0.0-alpha.12...v3.0.0-alpha.13) (2025-02-17)
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **useToast:** don't return a promise on `add`
|
||||
* **Toast:** rename `click` to `onClick` for consistency
|
||||
* **Alert/Toast:** add `orientation` prop
|
||||
|
||||
### Features
|
||||
|
||||
* **Alert/Toast:** add `orientation` prop ([2c192ac](https://github.com/nuxt/ui/commit/2c192ac145e3550153821627a389f03e26f247b5))
|
||||
* **Badge:** add support within button groups ([#3224](https://github.com/nuxt/ui/issues/3224)) ([10fb843](https://github.com/nuxt/ui/commit/10fb843f8ffc2cda9cf9a29cdf37c6b5dae9ca17))
|
||||
* **Card:** add `variant` prop ([847d4aa](https://github.com/nuxt/ui/commit/847d4aa752decc8c21a8eb57bff32a371c800b6d))
|
||||
* **CommandPalette:** support link props in items ([e2b78a7](https://github.com/nuxt/ui/commit/e2b78a78a45c1b2339ba57e3ec1fcf2a1500b3af)), closes [#3190](https://github.com/nuxt/ui/issues/3190)
|
||||
* **ContextMenu/DropdownMenu/NavigationMenu:** add `external-icon` prop ([5846c1e](https://github.com/nuxt/ui/commit/5846c1e2ee9f0851e902550f7e873cc703fe7cb4)), closes [#2996](https://github.com/nuxt/ui/issues/2996)
|
||||
* **Drawer:** add `inset` prop ([6d9b9ed](https://github.com/nuxt/ui/commit/6d9b9edc5524ad32abdec925c276519e1a1a59e4)), closes [#2994](https://github.com/nuxt/ui/issues/2994)
|
||||
* **locale:** add Azerbaijani language ([#3209](https://github.com/nuxt/ui/issues/3209)) ([0fb6753](https://github.com/nuxt/ui/commit/0fb6753c9de9144b7958052ae287b34911afcbd7))
|
||||
* **locale:** add Bengali (বাংলা) language ([#3321](https://github.com/nuxt/ui/issues/3321)) ([1d09a2a](https://github.com/nuxt/ui/commit/1d09a2aa35944bc798cf53809ae227d05592a5bd))
|
||||
* **module:** generate `tailwindcss` theme colors ([#2967](https://github.com/nuxt/ui/issues/2967)) ([443a0be](https://github.com/nuxt/ui/commit/443a0be0174f84526145db8c0349136e5fc4bbf3))
|
||||
* **Table:** extends core options and support other options like `pagination` ([#3177](https://github.com/nuxt/ui/issues/3177)) ([4aa3179](https://github.com/nuxt/ui/commit/4aa317944e17956b08e5ded3fb564ae0bbd4e888))
|
||||
* **Toast:** handle vnodes in `title` and `description` ([abd2be1](https://github.com/nuxt/ui/commit/abd2be1aa667f91c47673450445e09211c821365)), closes [#3226](https://github.com/nuxt/ui/issues/3226)
|
||||
* **unplugin:** expose options for embedded plugins, throw warnings for duplication ([#3207](https://github.com/nuxt/ui/issues/3207)) ([6c20f8a](https://github.com/nuxt/ui/commit/6c20f8a9ea03273a795c5f88c071830decd54c1e))
|
||||
* **useToast:** proxy emits ([089185f](https://github.com/nuxt/ui/commit/089185fbe4a13fa3253bf49780c4d0a673eef59a))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **App:** wrap `ModalProvider` / `SlideoverProvider` inside `TooltipProvider` ([cd0a9d3](https://github.com/nuxt/ui/commit/cd0a9d39d879a80342462b1c553602177f1ae8ee)), closes [#3236](https://github.com/nuxt/ui/issues/3236)
|
||||
* **Badge:** missing `UAvatar` import ([49dd088](https://github.com/nuxt/ui/commit/49dd0885a043e736cfa335d7657bb68ae6142ccf)), closes [#3203](https://github.com/nuxt/ui/issues/3203)
|
||||
* **Calendar/InputMenu/Textarea:** add missing `PartialString` type on `ui` prop ([9d29e0b](https://github.com/nuxt/ui/commit/9d29e0b4078c4638365caca4784ecad569cd0464)), closes [#3299](https://github.com/nuxt/ui/issues/3299)
|
||||
* **Card:** remove `shadow-sm` for consistency ([8097fff](https://github.com/nuxt/ui/commit/8097fff79d3d9f63481c6cd8e3e724a67f7761df))
|
||||
* **Link:** allow usage without `vue-router` in vue ([f55e869](https://github.com/nuxt/ui/commit/f55e86963737238749a8d7e85bca1e724ae4c4c2)), closes [#3001](https://github.com/nuxt/ui/issues/3001)
|
||||
* **locale:** export `km` ([#3201](https://github.com/nuxt/ui/issues/3201)) ([995e07d](https://github.com/nuxt/ui/commit/995e07d6ffa47ee593b28aa587699676e2ad3b90))
|
||||
* **Modal/Slideover:** improve `title` & `description` accessibility ([e419dcb](https://github.com/nuxt/ui/commit/e419dcbe61e6949abc76b8a1fc2f088fd7a402a0)), closes [#3267](https://github.com/nuxt/ui/issues/3267) [#3215](https://github.com/nuxt/ui/issues/3215)
|
||||
* **Modal:** always fullscreen on mobile ([#2637](https://github.com/nuxt/ui/issues/2637)) ([7641d89](https://github.com/nuxt/ui/commit/7641d89552df1ed42e70bbac90f5486b58bd9349))
|
||||
* **NavigationMenu:** disable collapsible with `collapsed` prop ([07e1b4f](https://github.com/nuxt/ui/commit/07e1b4f1f44efe90ac16138de5dbd78faf66e974))
|
||||
* **NavigationMenu:** remove negative mb causing overflow issues ([0e46c3e](https://github.com/nuxt/ui/commit/0e46c3e8cf94fb52c47b9d46eaba2d18329a6f45))
|
||||
* **NavigationMenu:** wrong `level` compute on `vertical` orientation ([c1c9da4](https://github.com/nuxt/ui/commit/c1c9da4d38b7675ce6323d938030e1b9a577f7c4))
|
||||
* **SelectMenu:** wrap content with `FocusScope` ([e7e7585](https://github.com/nuxt/ui/commit/e7e75858d7c5f0d966d2b9b7a16bc95573a31025)), closes [#2657](https://github.com/nuxt/ui/issues/2657)
|
||||
* **Table:** proxy props without `useForwardProps` ([f0553eb](https://github.com/nuxt/ui/commit/f0553ebb496f2f4bad5fd5ab0b3006c9ee8edba3))
|
||||
* **Toast:** rename `click` to `onClick` for consistency ([533e889](https://github.com/nuxt/ui/commit/533e88958916b356c00511a90e16c8b11af0b521))
|
||||
* **useToast:** don't return a promise on `add` ([153f341](https://github.com/nuxt/ui/commit/153f341a8c1a09a6fd3069886a26d9a3f5de41de))
|
||||
|
||||
## [3.0.0-alpha.12](https://github.com/nuxt/ui/compare/v3.0.0-alpha.11...v3.0.0-alpha.12) (2025-01-27)
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
@@ -93,41 +93,3 @@ provide('navigation', mappedNavigation)
|
||||
</template>
|
||||
</UApp>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
@source "../content";
|
||||
|
||||
@theme {
|
||||
--container-8xl: 90rem;
|
||||
|
||||
--font-sans: 'Public Sans', sans-serif;
|
||||
|
||||
--color-green-50: #EFFDF5;
|
||||
--color-green-100: #D9FBE8;
|
||||
--color-green-200: #B3F5D1;
|
||||
--color-green-300: #75EDAE;
|
||||
--color-green-400: #00DC82;
|
||||
--color-green-500: #00C16A;
|
||||
--color-green-600: #00A155;
|
||||
--color-green-700: #007F45;
|
||||
--color-green-800: #016538;
|
||||
--color-green-900: #0A5331;
|
||||
--color-green-950: #052E16;
|
||||
}
|
||||
|
||||
:root {
|
||||
--ui-container: var(--container-8xl);
|
||||
}
|
||||
|
||||
html[data-framework="nuxt"] .vue-only,
|
||||
html[data-framework="vue"] .nuxt-only,
|
||||
html[data-module="ui-pro"] .ui-only,
|
||||
html[data-module="ui"] .ui-pro-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full h-64 !px-0 !py-0 !pt-0 !pb-0 !p-0 !justify-start !min-h-96 h-136 */
|
||||
</style>
|
||||
|
||||
35
docs/app/assets/css/main.css
Normal file
35
docs/app/assets/css/main.css
Normal file
@@ -0,0 +1,35 @@
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui-pro";
|
||||
|
||||
@source "../../../content";
|
||||
|
||||
@theme {
|
||||
--container-8xl: 90rem;
|
||||
|
||||
--font-sans: 'Public Sans', sans-serif;
|
||||
|
||||
--color-green-50: #EFFDF5;
|
||||
--color-green-100: #D9FBE8;
|
||||
--color-green-200: #B3F5D1;
|
||||
--color-green-300: #75EDAE;
|
||||
--color-green-400: #00DC82;
|
||||
--color-green-500: #00C16A;
|
||||
--color-green-600: #00A155;
|
||||
--color-green-700: #007F45;
|
||||
--color-green-800: #016538;
|
||||
--color-green-900: #0A5331;
|
||||
--color-green-950: #052E16;
|
||||
}
|
||||
|
||||
:root {
|
||||
--ui-container: var(--container-8xl);
|
||||
}
|
||||
|
||||
html[data-framework="nuxt"] .vue-only,
|
||||
html[data-framework="vue"] .nuxt-only,
|
||||
html[data-module="ui-pro"] .ui-only,
|
||||
html[data-module="ui"] .ui-pro-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Safelist (do not remove): [&>div]:*:my-0 [&>div]:*:w-full h-64 !px-0 !py-0 !pt-0 !pb-0 !p-0 !justify-start !min-h-96 h-136 */
|
||||
@@ -45,6 +45,8 @@ export default defineNuxtConfig({
|
||||
}
|
||||
},
|
||||
|
||||
css: ['~/assets/css/main.css'],
|
||||
|
||||
site: {
|
||||
url: 'https://ui3.nuxt.dev'
|
||||
},
|
||||
@@ -97,8 +99,7 @@ export default defineNuxtConfig({
|
||||
// '/api/pulls.json'
|
||||
],
|
||||
crawlLinks: true,
|
||||
autoSubfolderIndex: false,
|
||||
failOnError: false
|
||||
autoSubfolderIndex: false
|
||||
},
|
||||
cloudflare: {
|
||||
pages: {
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
"@nuxt/content": "^3.1.1",
|
||||
"@nuxt/image": "^1.9.0",
|
||||
"@nuxt/ui": "latest",
|
||||
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@703e687",
|
||||
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@07cf107",
|
||||
"@nuxthub/core": "^0.8.17",
|
||||
"@nuxtjs/plausible": "^1.2.0",
|
||||
"@octokit/rest": "^21.1.0",
|
||||
"@octokit/rest": "^21.1.1",
|
||||
"@rollup/plugin-yaml": "^4.1.2",
|
||||
"@vueuse/nuxt": "^12.6.1",
|
||||
"@vueuse/nuxt": "^12.7.0",
|
||||
"joi": "^17.13.3",
|
||||
"motion": "^12.4.2",
|
||||
"motion": "^12.4.3",
|
||||
"nuxt": "^3.15.4",
|
||||
"nuxt-component-meta": "^0.10.0",
|
||||
"nuxt-og-image": "^4.1.2",
|
||||
@@ -30,6 +30,6 @@
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"wrangler": "^3.109.0"
|
||||
"wrangler": "^3.109.1"
|
||||
}
|
||||
}
|
||||
|
||||
23
package.json
23
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@nuxt/ui",
|
||||
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
||||
"version": "3.0.0-alpha.12",
|
||||
"packageManager": "pnpm@10.4.0",
|
||||
"version": "3.0.0-alpha.13",
|
||||
"packageManager": "pnpm@10.4.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nuxt/ui.git"
|
||||
@@ -82,7 +82,7 @@
|
||||
"@iconify/vue": "^4.3.0",
|
||||
"@internationalized/date": "^3.7.0",
|
||||
"@internationalized/number": "^3.6.0",
|
||||
"@nuxt/devtools-kit": "^2.0.0",
|
||||
"@nuxt/devtools-kit": "^2.1.0",
|
||||
"@nuxt/fonts": "^0.10.3",
|
||||
"@nuxt/icon": "^1.10.3",
|
||||
"@nuxt/kit": "^3.15.4",
|
||||
@@ -92,8 +92,8 @@
|
||||
"@tailwindcss/vite": "^4.0.6",
|
||||
"@tanstack/vue-table": "^8.21.2",
|
||||
"@unhead/vue": "^1.11.19",
|
||||
"@vueuse/core": "^12.6.1",
|
||||
"@vueuse/integrations": "^12.6.1",
|
||||
"@vueuse/core": "^12.7.0",
|
||||
"@vueuse/integrations": "^12.7.0",
|
||||
"colortranslator": "^4.1.0",
|
||||
"consola": "^3.4.0",
|
||||
"defu": "^6.1.4",
|
||||
@@ -118,8 +118,8 @@
|
||||
"tailwindcss": "^4.0.6",
|
||||
"tinyglobby": "^0.2.10",
|
||||
"unplugin": "^2.2.0",
|
||||
"unplugin-auto-import": "^19.0.0",
|
||||
"unplugin-vue-components": "^28.0.0",
|
||||
"unplugin-auto-import": "^19.1.0",
|
||||
"unplugin-vue-components": "^28.1.0",
|
||||
"vaul-vue": "^0.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -152,15 +152,18 @@
|
||||
"@nuxt/ui": "workspace:*",
|
||||
"chokidar": "3.6.0",
|
||||
"debug": "4.3.7",
|
||||
"rollup": "^4.24.0",
|
||||
"rollup": "4.32.1",
|
||||
"typescript": "5.6.3",
|
||||
"unimport": "3.14.5",
|
||||
"unplugin": "^2.2.0",
|
||||
"vite": "6.0.11",
|
||||
"vue-tsc": "^2.2.0"
|
||||
"vue": "3.5.13",
|
||||
"vue-tsc": "2.2.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": ["better-sqlite3"]
|
||||
"onlyBuiltDependencies": [
|
||||
"better-sqlite3"
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
"nuxt-ui",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^6.0.11",
|
||||
"vue-tsc": "^2.2.0"
|
||||
}
|
||||
|
||||
@@ -115,24 +115,3 @@ defineShortcuts({
|
||||
</UModal>
|
||||
</UApp>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Public Sans', sans-serif;
|
||||
|
||||
--color-green-50: #EFFDF5;
|
||||
--color-green-100: #D9FBE8;
|
||||
--color-green-200: #B3F5D1;
|
||||
--color-green-300: #75EDAE;
|
||||
--color-green-400: #00DC82;
|
||||
--color-green-500: #00C16A;
|
||||
--color-green-600: #00A155;
|
||||
--color-green-700: #007F45;
|
||||
--color-green-800: #016538;
|
||||
--color-green-900: #0A5331;
|
||||
--color-green-950: #052E16;
|
||||
}
|
||||
</style>
|
||||
|
||||
18
playground-vue/src/assets/css/main.css
Normal file
18
playground-vue/src/assets/css/main.css
Normal file
@@ -0,0 +1,18 @@
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Public Sans', sans-serif;
|
||||
|
||||
--color-green-50: #EFFDF5;
|
||||
--color-green-100: #D9FBE8;
|
||||
--color-green-200: #B3F5D1;
|
||||
--color-green-300: #75EDAE;
|
||||
--color-green-400: #00DC82;
|
||||
--color-green-500: #00C16A;
|
||||
--color-green-600: #00A155;
|
||||
--color-green-700: #007F45;
|
||||
--color-green-800: #016538;
|
||||
--color-green-900: #0A5331;
|
||||
--color-green-950: #052E16;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import './assets/css/main.css'
|
||||
|
||||
import { createApp, defineAsyncComponent, ref } from 'vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import uiPlugin from '@nuxt/ui/vue-plugin'
|
||||
|
||||
@@ -120,24 +120,3 @@ defineShortcuts({
|
||||
<NuxtPage />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Public Sans', sans-serif;
|
||||
|
||||
--color-green-50: #EFFDF5;
|
||||
--color-green-100: #D9FBE8;
|
||||
--color-green-200: #B3F5D1;
|
||||
--color-green-300: #75EDAE;
|
||||
--color-green-400: #00DC82;
|
||||
--color-green-500: #00C16A;
|
||||
--color-green-600: #00A155;
|
||||
--color-green-700: #007F45;
|
||||
--color-green-800: #016538;
|
||||
--color-green-900: #0A5331;
|
||||
--color-green-950: #052E16;
|
||||
}
|
||||
</style>
|
||||
|
||||
18
playground/app/assets/css/main.css
Normal file
18
playground/app/assets/css/main.css
Normal file
@@ -0,0 +1,18 @@
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Public Sans', sans-serif;
|
||||
|
||||
--color-green-50: #EFFDF5;
|
||||
--color-green-100: #D9FBE8;
|
||||
--color-green-200: #B3F5D1;
|
||||
--color-green-300: #75EDAE;
|
||||
--color-green-400: #00DC82;
|
||||
--color-green-500: #00C16A;
|
||||
--color-green-600: #00A155;
|
||||
--color-green-700: #007F45;
|
||||
--color-green-800: #016538;
|
||||
--color-green-900: #0A5331;
|
||||
--color-green-950: #052E16;
|
||||
}
|
||||
@@ -12,6 +12,8 @@ export default defineNuxtConfig({
|
||||
enabled: true
|
||||
},
|
||||
|
||||
css: ['~/assets/css/main.css'],
|
||||
|
||||
ui: {
|
||||
fonts: !process.env.DEVTOOLS
|
||||
},
|
||||
|
||||
2842
pnpm-lock.yaml
generated
2842
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -46,7 +46,7 @@ import UIcon from './Icon.vue'
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
const props = withDefaults(defineProps<AvatarProps>(), { as: 'span' })
|
||||
const attrs = useAttrs()
|
||||
const attrs = useAttrs() as any
|
||||
|
||||
const fallbackProps = useForwardProps(reactivePick(props, 'delayMs'))
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ defineExpose({
|
||||
|
||||
<div v-if="currentStep?.content || !!slots.content || (currentStep?.slot && !!slots[currentStep.slot]) || (currentStep?.value && !!slots[currentStep.value])" :class="ui.content({ class: props.ui?.description })">
|
||||
<slot
|
||||
:name="!!slots[currentStep?.slot ?? currentStep.value] ? currentStep.slot ?? currentStep.value : 'content'"
|
||||
:name="!!slots[currentStep?.slot ?? currentStep.value!] ? currentStep.slot ?? currentStep.value : 'content'"
|
||||
:item="currentStep"
|
||||
>
|
||||
{{ currentStep?.content }}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { VariantProps } from 'tailwind-variants'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import type { RowData } from '@tanstack/table-core'
|
||||
import type {
|
||||
CellContext,
|
||||
ColumnDef,
|
||||
@@ -25,7 +26,6 @@ import type {
|
||||
PaginationOptions,
|
||||
PaginationState,
|
||||
Row,
|
||||
RowData,
|
||||
RowPinningOptions,
|
||||
RowPinningState,
|
||||
RowSelectionOptions,
|
||||
|
||||
@@ -40,12 +40,12 @@ describe('Table', () => {
|
||||
id: 'select',
|
||||
header: ({ table }) => h(UCheckbox, {
|
||||
'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
|
||||
'onUpdate:modelValue': (value: boolean | 'indeterminate' | undefined) => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all'
|
||||
}),
|
||||
cell: ({ row }) => h(UCheckbox, {
|
||||
'modelValue': row.getIsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
|
||||
'onUpdate:modelValue': (value: boolean | 'indeterminate' | undefined) => row.toggleSelected(!!value),
|
||||
'ariaLabel': 'Select row'
|
||||
}),
|
||||
enableSorting: false,
|
||||
|
||||
Reference in New Issue
Block a user