├── gloabl.d.ts ├── .npmrc ├── CONTRIBUTING.md ├── packages ├── components │ ├── dialog │ │ ├── src │ │ │ ├── index.ts │ │ │ └── components │ │ │ │ └── index.tsx │ │ ├── tsup.config.ts │ │ └── package.json │ ├── form │ │ ├── src │ │ │ ├── utils │ │ │ │ ├── index.ts │ │ │ │ ├── render.tsx │ │ │ │ └── fields.tsx │ │ │ ├── types │ │ │ │ ├── index.ts │ │ │ │ ├── instance.ts │ │ │ │ └── data.ts │ │ │ ├── composables │ │ │ │ ├── index.ts │ │ │ │ ├── useRules.ts │ │ │ │ ├── useValues.ts │ │ │ │ ├── useMetadata.ts │ │ │ │ └── useData.ts │ │ │ ├── index.ts │ │ │ ├── config │ │ │ │ └── index.ts │ │ │ └── define │ │ │ │ └── index.ts │ │ ├── tsup.config.ts │ │ └── package.json │ ├── actions │ │ ├── src │ │ │ ├── index.ts │ │ │ ├── components │ │ │ │ └── index.ts │ │ │ ├── types │ │ │ │ └── index.ts │ │ │ ├── composables │ │ │ │ └── index.ts │ │ │ └── define │ │ │ │ └── index.tsx │ │ ├── tsup.config.ts │ │ └── package.json │ ├── radio │ │ ├── src │ │ │ ├── index.ts │ │ │ └── components │ │ │ │ └── index.tsx │ │ ├── tsup.config.ts │ │ ├── package.json │ │ └── README.md │ ├── checkbox │ │ ├── src │ │ │ ├── index.ts │ │ │ └── components │ │ │ │ └── index.tsx │ │ ├── tsup.config.ts │ │ └── package.json │ ├── provider │ │ ├── src │ │ │ ├── components │ │ │ │ ├── index.ts │ │ │ │ ├── GlobalLoadingBar.tsx │ │ │ │ ├── GlobalNotification.tsx │ │ │ │ ├── InstallProvider.tsx │ │ │ │ ├── GlobalMessage.tsx │ │ │ │ ├── GlobalProvider.tsx │ │ │ │ └── GlobalDialog.tsx │ │ │ ├── index.ts │ │ │ ├── global.window.d.ts │ │ │ ├── global.ts │ │ │ ├── utils │ │ │ │ └── index.ts │ │ │ └── global.naive.ts │ │ ├── tsup.config.ts │ │ └── package.json │ ├── table │ │ ├── src │ │ │ ├── index.ts │ │ │ ├── composables │ │ │ │ ├── index.ts │ │ │ │ ├── useTableMinWidth.ts │ │ │ │ ├── useColumnIndexes.ts │ │ │ │ ├── useColumnLink.ts │ │ │ │ ├── useMetadata.ts │ │ │ │ ├── useColumns.ts │ │ │ │ └── useOffsetPagination.ts │ │ │ ├── types │ │ │ │ └── index.ts │ │ │ ├── define │ │ │ │ └── index.ts │ │ │ └── components │ │ │ │ └── index.tsx │ │ ├── tsup.config.ts │ │ ├── package.json │ │ └── README.md │ └── main │ │ ├── src │ │ ├── resolver.ts │ │ ├── imports.ts │ │ └── index.ts │ │ ├── tsup.config.ts │ │ └── package.json ├── utils │ ├── src │ │ ├── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── components │ │ │ └── index.ts │ │ ├── composables │ │ │ └── index.ts │ │ └── utils │ │ │ └── index.ts │ ├── tsup.config.ts │ └── package.json └── config │ ├── package.json │ ├── index.d.ts │ └── index.js ├── .gitignore ├── test └── index.test.ts ├── docs ├── .vitepress │ ├── theme │ │ ├── naive-ui.css │ │ ├── index.codeeditor.ts │ │ ├── components │ │ │ └── Outline │ │ │ │ ├── utils │ │ │ │ └── index.ts │ │ │ │ ├── components │ │ │ │ └── VPDocOutlineItem.vue │ │ │ │ └── index.vue │ │ ├── index.codeeditor.files.ts │ │ └── index.ts │ ├── vite-env.d.ts │ ├── runtime.d.ts │ └── vite.config.ts ├── components │ ├── form │ │ ├── demo │ │ │ ├── form-item-props.vue │ │ │ ├── slots.vue │ │ │ ├── array.vue │ │ │ ├── grid.vue │ │ │ ├── props.vue │ │ │ ├── basic.vue │ │ │ ├── data-transform.vue │ │ │ ├── toolbars.vue │ │ │ ├── field.vue │ │ │ ├── field-render.vue │ │ │ ├── clone.vue │ │ │ ├── form.vue │ │ │ └── field-context.vue │ │ ├── field-context.md │ │ ├── data-trans.md │ │ ├── field-item.md │ │ └── field-api.md │ ├── checkbox │ │ ├── demo │ │ │ ├── basic.vue │ │ │ ├── events.vue │ │ │ └── focus-blur.vue │ │ └── index.md │ ├── radio │ │ ├── demo │ │ │ ├── basic.vue │ │ │ ├── buttons.vue │ │ │ └── sizes.vue │ │ └── index.md │ ├── actions │ │ ├── demo │ │ │ ├── basic.vue │ │ │ ├── custom.vue │ │ │ ├── form.vue │ │ │ └── table.vue │ │ └── index.md │ ├── table │ │ ├── demo │ │ │ ├── basic.vue │ │ │ ├── pagination.vue │ │ │ ├── form.vue │ │ │ ├── watcher.vue │ │ │ ├── hooks.vue │ │ │ └── request.vue │ │ ├── hooks.md │ │ ├── actions.md │ │ └── index.md │ └── provider │ │ ├── demo │ │ └── basic.vue │ │ └── index.md ├── zh-CN │ ├── components │ │ ├── form │ │ │ ├── demo │ │ │ │ ├── form-item-props.vue │ │ │ │ ├── slots.vue │ │ │ │ ├── array.vue │ │ │ │ ├── props.vue │ │ │ │ ├── grid.vue │ │ │ │ ├── basic.vue │ │ │ │ ├── field.vue │ │ │ │ ├── data-transform.vue │ │ │ │ ├── toolbars.vue │ │ │ │ ├── field-render.vue │ │ │ │ ├── clone.vue │ │ │ │ ├── form.vue │ │ │ │ └── field-context.vue │ │ │ ├── field-context.md │ │ │ ├── data-trans.md │ │ │ ├── field-item.md │ │ │ └── field-api.md │ │ ├── checkbox │ │ │ ├── demo │ │ │ │ ├── basic.vue │ │ │ │ ├── events.vue │ │ │ │ └── focus-blur.vue │ │ │ └── index.md │ │ ├── radio │ │ │ ├── demo │ │ │ │ ├── basic.vue │ │ │ │ ├── buttons.vue │ │ │ │ └── sizes.vue │ │ │ └── index.md │ │ ├── actions │ │ │ ├── demo │ │ │ │ ├── basic.vue │ │ │ │ ├── custom.vue │ │ │ │ ├── form.vue │ │ │ │ └── table.vue │ │ │ └── index.md │ │ ├── table │ │ │ ├── demo │ │ │ │ ├── basic.vue │ │ │ │ ├── pagination.vue │ │ │ │ ├── form.vue │ │ │ │ ├── watcher.vue │ │ │ │ ├── hooks.vue │ │ │ │ └── request.vue │ │ │ ├── hooks.md │ │ │ ├── actions.md │ │ │ └── index.md │ │ └── provider │ │ │ ├── demo │ │ │ └── basic.vue │ │ │ └── index.md │ ├── index.md │ └── guide │ │ ├── intro.md │ │ ├── faq.md │ │ └── install.md ├── auto-imports.d.ts ├── tsconfig.json ├── components.d.ts ├── package.json ├── index.md ├── public │ └── logo.svg └── guide │ ├── intro.md │ ├── faq.md │ └── install.md ├── eslint.config.js ├── tsconfig.json ├── .github └── workflows │ ├── release.yml │ └── ci.yml ├── LICENSE.md ├── .vscode └── settings.json ├── pnpm-workspace.yaml ├── package.json └── README.md /gloabl.d.ts: -------------------------------------------------------------------------------- 1 | import 'vue/jsx-runtime' 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | shell-emulator=true 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please refer to https://github.com/antfu/contribute 2 | -------------------------------------------------------------------------------- /packages/components/dialog/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components' 2 | -------------------------------------------------------------------------------- /packages/components/form/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fields' 2 | export * from './render' 3 | -------------------------------------------------------------------------------- /packages/components/actions/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components' 2 | export * from './define' 3 | export * from './types' 4 | -------------------------------------------------------------------------------- /packages/components/form/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config' 2 | export * from './data' 3 | export * from './instance' 4 | -------------------------------------------------------------------------------- /packages/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components' 2 | export * from './composables' 3 | export * from './types' 4 | export * from './utils' 5 | -------------------------------------------------------------------------------- /packages/components/radio/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NuRadioGroup } from './components' 2 | 3 | export * from './components' 4 | export default NuRadioGroup 5 | -------------------------------------------------------------------------------- /packages/components/checkbox/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NuCheckboxGroup } from './components' 2 | 3 | export * from './components' 4 | 5 | export default NuCheckboxGroup 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .DS_Store 3 | .idea 4 | *.log 5 | *.tgz 6 | coverage 7 | dist 8 | lib-cov 9 | logs 10 | node_modules 11 | temp 12 | cache 13 | .eslintcache 14 | -------------------------------------------------------------------------------- /packages/components/form/src/composables/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useData' 2 | export * from './useMetadata' 3 | export * from './useRules' 4 | export * from './useValues' 5 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | describe('should', () => { 4 | it('exported', () => { 5 | expect(1).toEqual(1) 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /packages/utils/src/types/index.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue' 2 | 3 | export type NestedRefs = { [K in keyof T]: Ref | T[K] } & Record 4 | export type AnyFunction = (...args: any[]) => any 5 | -------------------------------------------------------------------------------- /packages/components/form/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NuForm } from './components' 2 | 3 | export * from './components' 4 | export * from './define' 5 | export * from './types' 6 | export * from './utils' 7 | 8 | export default NuForm 9 | -------------------------------------------------------------------------------- /packages/components/provider/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './GlobalDialog' 2 | export * from './GlobalMessage' 3 | export * from './GlobalNotification' 4 | export * from './GlobalProvider' 5 | export * from './InstallProvider' 6 | -------------------------------------------------------------------------------- /packages/components/table/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NuTable } from './components' 2 | 3 | export * from './components' 4 | export * from './composables' 5 | export * from './define' 6 | export * from './types' 7 | 8 | export default NuTable 9 | -------------------------------------------------------------------------------- /packages/components/table/src/composables/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useColumnIndexes' 2 | export * from './useColumnLink' 3 | export * from './useColumns' 4 | export * from './useMetadata' 5 | export * from './useOffsetPagination' 6 | export * from './useTableMinWidth' 7 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/naive-ui.css: -------------------------------------------------------------------------------- 1 | .VPImage.logo { 2 | width: 32px; 3 | height: 32px; 4 | } 5 | .VPNavBarTitle .title span{ 6 | font-size: 18px; 7 | font-weight: normal; 8 | } 9 | 10 | button span { 11 | font-size: 14px; 12 | font-weight: 400; 13 | font-family: inherit; 14 | } -------------------------------------------------------------------------------- /packages/components/provider/src/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { NuGlobalProvider } from './components' 4 | 5 | export * from './components' 6 | export * from './global' 7 | 8 | export * from './global.naive' 9 | 10 | export default NuGlobalProvider 11 | -------------------------------------------------------------------------------- /docs/.vitepress/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable ts/no-empty-object-type */ 2 | /// 3 | 4 | declare module '*.vue' { 5 | import type { DefineComponent } from 'vue' 6 | 7 | const component: DefineComponent<{}, {}, any> 8 | export default component 9 | } 10 | -------------------------------------------------------------------------------- /packages/components/provider/src/global.window.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | $loadingBar: import('naive-ui').LoadingBarProviderInst 3 | $dialog: import('naive-ui').XDialogProviderInst 4 | $message: import('naive-ui').XMessageProviderInst 5 | $notification: import('naive-ui').NotificationProviderInst 6 | } 7 | -------------------------------------------------------------------------------- /packages/config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsup-config", 3 | "version": "0.5.0", 4 | "private": true, 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "devDependencies": { 8 | "esbuild-plugin-globals": "catalog:bundler", 9 | "unplugin-vue-jsx": "catalog:bundler" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/components/provider/src/global.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | const $loadingBar: import('naive-ui').LoadingBarProviderInst 3 | const $dialog: import('naive-ui').XDialogProviderInst 4 | const $message: import('naive-ui').XMessageProviderInst 5 | const $notification: import('naive-ui').NotificationProviderInst 6 | } 7 | 8 | export {} 9 | -------------------------------------------------------------------------------- /packages/utils/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'tsup-config' 2 | 3 | export default defineBuildConfig({ 4 | format: ['cjs', 'esm', 'iife', 'iife-min'], 5 | entry: ['src/index.ts'], 6 | name: 'ultra-utils', 7 | globalName: 'NaiveUltraUtils', 8 | globals: { 9 | vue: 'Vue', 10 | }, 11 | clean: true, 12 | }) 13 | -------------------------------------------------------------------------------- /docs/components/form/demo/form-item-props.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /packages/components/actions/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'tsup-config' 2 | 3 | export default defineBuildConfig({ 4 | format: ['cjs', 'esm', 'iife', 'iife-min'], 5 | entry: ['src/index.ts'], 6 | name: 'ultra-actions', 7 | globalName: 'NaiveUltraActions', 8 | globals: { 9 | vue: 'Vue', 10 | }, 11 | clean: true, 12 | }) 13 | -------------------------------------------------------------------------------- /packages/components/provider/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'tsup-config' 2 | 3 | export default defineBuildConfig({ 4 | format: ['cjs', 'esm', 'iife', 'iife-min'], 5 | entry: ['src/index.ts'], 6 | name: 'ultra-globals', 7 | globalName: 'NaiveUltraGlobals', 8 | globals: { 9 | vue: 'Vue', 10 | }, 11 | clean: true, 12 | }) 13 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/demo/form-item-props.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /docs/.vitepress/runtime.d.ts: -------------------------------------------------------------------------------- 1 | import type { BundledLanguage, BundledTheme, CodeToHastOptions } from 'shiki' 2 | 3 | declare module '@vue/runtime-core' { 4 | interface ComponentCustomProperties { 5 | $highlighter: import('shiki').Highlighter 6 | $c2h: (code: string, options: CodeToHastOptions) => string 7 | } 8 | } 9 | export {} 10 | -------------------------------------------------------------------------------- /packages/config/index.d.ts: -------------------------------------------------------------------------------- 1 | import type Globals from 'esbuild-plugin-globals' 2 | import type { Options as _Options } from 'tsup' 3 | 4 | export interface Options extends Omit<_Options, 'format'> { 5 | globals?: Parameters[0] 6 | format?: ('cjs' | 'esm' | 'iife' | 'iife-min')[] 7 | } 8 | export declare function defineBuildConfig(options: Options): _Options[] 9 | -------------------------------------------------------------------------------- /packages/components/form/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'tsup-config' 2 | 3 | export default defineBuildConfig({ 4 | format: ['cjs', 'esm', 'iife', 'iife-min'], 5 | entry: ['src/index.ts'], 6 | name: 'ultra-form', 7 | globalName: 'NaiveUltraForm', 8 | globals: { 9 | 'naive-ui': 'naive', 10 | 'vue': 'Vue', 11 | }, 12 | clean: true, 13 | }) 14 | -------------------------------------------------------------------------------- /packages/components/radio/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'tsup-config' 2 | 3 | export default defineBuildConfig({ 4 | format: ['cjs', 'esm', 'iife', 'iife-min'], 5 | entry: ['src/index.ts'], 6 | name: 'ultra-table', 7 | globalName: 'NaiveUltraTable', 8 | globals: { 9 | 'naive-ui': 'naive', 10 | 'vue': 'Vue', 11 | }, 12 | clean: true, 13 | }) 14 | -------------------------------------------------------------------------------- /packages/components/table/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'tsup-config' 2 | 3 | export default defineBuildConfig({ 4 | format: ['cjs', 'esm', 'iife', 'iife-min'], 5 | entry: ['src/index.ts'], 6 | name: 'ultra-table', 7 | globalName: 'NaiveUltraTable', 8 | globals: { 9 | 'naive-ui': 'naive', 10 | 'vue': 'Vue', 11 | }, 12 | clean: true, 13 | }) 14 | -------------------------------------------------------------------------------- /packages/components/checkbox/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'tsup-config' 2 | 3 | export default defineBuildConfig({ 4 | format: ['cjs', 'esm', 'iife', 'iife-min'], 5 | entry: ['src/index.ts'], 6 | name: 'ultra-table', 7 | globalName: 'NaiveUltraTable', 8 | globals: { 9 | 'naive-ui': 'naive', 10 | 'vue': 'Vue', 11 | }, 12 | clean: true, 13 | }) 14 | -------------------------------------------------------------------------------- /packages/components/dialog/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'tsup-config' 2 | 3 | export default defineBuildConfig({ 4 | format: ['cjs', 'esm', 'iife', 'iife-min'], 5 | entry: ['src/index.ts'], 6 | name: 'ultra-table', 7 | globalName: 'NaiveUltraTable', 8 | globals: { 9 | 'naive-ui': 'naive', 10 | 'vue': 'Vue', 11 | }, 12 | clean: true, 13 | }) 14 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import antfu from '@antfu/eslint-config' 3 | 4 | export default antfu( 5 | { 6 | type: 'app', 7 | vue: true, 8 | }, 9 | { 10 | rules: { 11 | 'vue/component-name-in-template-casing': 'off', 12 | 'ts/no-use-before-define': 'off', 13 | 'no-alert': 'off', 14 | 'prefer-promise-reject-errors': 'off', 15 | }, 16 | }, 17 | ) 18 | -------------------------------------------------------------------------------- /packages/components/main/src/resolver.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentResolver } from 'unplugin-vue-components/types' 2 | 3 | function NaiveUltraResolver(): ComponentResolver { 4 | return { 5 | type: 'component', 6 | resolve: (name: string) => { 7 | if (name.match(/^Nu.+/)) 8 | return { name, from: 'naive-ultra' } 9 | }, 10 | } 11 | } 12 | 13 | export default NaiveUltraResolver 14 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/demo/slots.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /docs/components/form/demo/slots.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /packages/components/table/src/composables/useTableMinWidth.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeRef } from '@vueuse/core' 2 | import type { TableColumns } from './useColumns' 3 | import { useArrayMap, useArrayReduce } from '@vueuse/core' 4 | 5 | export function useTableMinWidth(columns: MaybeRef) { 6 | const columnWidths = useArrayMap(columns, col => Number(col.width || col.minWidth || 100)) 7 | return useArrayReduce(columnWidths, (acc, cur) => acc + cur, 0) 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/actions/src/components/index.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue' 2 | import type { ActionsInstance } from '../types' 3 | import { defineComponent } from 'vue' 4 | 5 | export const NuActions = defineComponent({ 6 | name: 'NuActions', 7 | props: { 8 | is: { 9 | type: Object as PropType>, 10 | required: true, 11 | }, 12 | }, 13 | setup(props) { 14 | return () => props.is() 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /packages/components/main/src/imports.ts: -------------------------------------------------------------------------------- 1 | import type { ImportsMap } from 'unplugin-auto-import/types' 2 | 3 | function NaiveUltraImports(): ImportsMap { 4 | return { 5 | 'naive-ultra': [ 6 | 'defineForm', 7 | 'defineTable', 8 | 'defineActions', 9 | 'field', 10 | 'ONLY_RENDER', 11 | 'useColumnIndexes', 12 | 'useColumnLink', 13 | 'useColumns', 14 | ], 15 | } 16 | } 17 | 18 | export default NaiveUltraImports 19 | -------------------------------------------------------------------------------- /packages/utils/src/components/index.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, h } from 'vue' 2 | 3 | export const If = defineComponent({ 4 | name: 'If', 5 | props: { 6 | cond: { 7 | type: Boolean, 8 | default: true, 9 | }, 10 | tag: { 11 | type: String, 12 | }, 13 | }, 14 | setup(props, { slots }) { 15 | return () => props.cond 16 | ? (props.tag ? h(props.tag, {}, slots) : slots.default?.()) 17 | : null 18 | }, 19 | }) 20 | -------------------------------------------------------------------------------- /docs/zh-CN/components/checkbox/demo/basic.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /docs/components/checkbox/demo/basic.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /packages/utils/src/composables/index.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export function useAsyncCallback any>(fun: T) { 4 | const error = ref() 5 | const loading = ref(false) 6 | function execute(...args: any[]) { 7 | const result = fun(...args) 8 | if (result instanceof Promise) { 9 | loading.value = true 10 | result.finally(() => loading.value = false).catch() 11 | } 12 | return result 13 | } 14 | 15 | return [execute as unknown as T, loading, error] as const 16 | } 17 | -------------------------------------------------------------------------------- /packages/components/table/src/composables/useColumnIndexes.ts: -------------------------------------------------------------------------------- 1 | import type { UnwrapNestedRefs } from 'vue' 2 | import type { OffsetPagination } from './useOffsetPagination' 3 | import { nanoid } from 'nanoid' 4 | 5 | export function useColumnIndexes(pagination: UnwrapNestedRefs) { 6 | return { 7 | key: nanoid(5), 8 | title: '#', 9 | render: (_: any, rowIndex: number) => { 10 | return rowIndex + 1 + ((pagination.page - 1) * pagination.pageSize) 11 | }, 12 | fixed: 'left' as const, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/components/main/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'tsup-config' 2 | 3 | export default [ 4 | ...defineBuildConfig({ 5 | format: ['cjs', 'esm', 'iife', 'iife-min'], 6 | entry: ['src/index.ts'], 7 | name: 'naive-ultra', 8 | globalName: 'NaiveUltra', 9 | globals: { 10 | 'naive-ui': 'naive', 11 | 'vue': 'Vue', 12 | }, 13 | clean: true, 14 | 15 | }), 16 | { 17 | format: ['cjs', 'esm'], 18 | entry: ['src/resolver.ts', 'src/imports.ts'], 19 | name: 'naive-ultra', 20 | dts: true, 21 | }, 22 | ] 23 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/demo/array.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 25 | -------------------------------------------------------------------------------- /docs/components/radio/demo/basic.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /docs/zh-CN/components/radio/demo/basic.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/demo/props.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | -------------------------------------------------------------------------------- /docs/components/form/demo/array.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 27 | -------------------------------------------------------------------------------- /docs/components/actions/demo/basic.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/demo/grid.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 31 | -------------------------------------------------------------------------------- /docs/components/form/demo/grid.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 31 | -------------------------------------------------------------------------------- /docs/zh-CN/components/actions/demo/basic.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /docs/components/form/demo/props.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | -------------------------------------------------------------------------------- /docs/components/form/demo/basic.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 27 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/demo/basic.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 28 | -------------------------------------------------------------------------------- /docs/zh-CN/components/checkbox/demo/events.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /docs/auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | export {} 7 | declare global { 8 | const defineActions: typeof import('naive-ultra')['defineActions'] 9 | const defineForm: typeof import('naive-ultra')['defineForm'] 10 | const defineTable: typeof import('naive-ultra')['defineTable'] 11 | const field: typeof import('naive-ultra')['field'] 12 | const useColumnIndexes: typeof import('naive-ultra')['useColumnIndexes'] 13 | const useColumnLink: typeof import('naive-ultra')['useColumnLink'] 14 | const useColumns: typeof import('naive-ultra')['useColumns'] 15 | } 16 | -------------------------------------------------------------------------------- /docs/components/checkbox/demo/events.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/demo/field.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 34 | -------------------------------------------------------------------------------- /docs/zh-CN/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Naive Ultra" 7 | text: "Naive UI Components" 8 | tagline: 让中后台开发简单快捷 9 | image: /logo.svg 10 | actions: 11 | - theme: brand 12 | text: Get Started 13 | link: /zh-CN/guide/intro 14 | - theme: alt 15 | text: View on GitHub 16 | link: https://github.com/hairyf/naive-ultra 17 | 18 | features: 19 | - title: 简单易用 20 | details: 提供统一的 API 设计理念和示例文档,快速上手,极大提升中后台系统的开发效率。 21 | - title: Naive UI 22 | details: 使用 Naive UI 的组件作为基准,在此基础上进行扩展,满足复杂的业务场景。 23 | - title: TypeScript 24 | details: 使用 TypeScript 编写,具备完善的类型推导和类型提示,配套详细的 TS 文档,保障开发体验和代码质量。 25 | --- 26 | -------------------------------------------------------------------------------- /docs/zh-CN/components/actions/demo/custom.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 32 | -------------------------------------------------------------------------------- /docs/components/actions/demo/custom.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 32 | -------------------------------------------------------------------------------- /docs/components/form/demo/data-transform.vue: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 27 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/demo/data-transform.vue: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 27 | -------------------------------------------------------------------------------- /packages/components/provider/src/components/GlobalLoadingBar.tsx: -------------------------------------------------------------------------------- 1 | import { loadingBarProviderProps, NLoadingBarProvider, useLoadingBar } from 'naive-ui' 2 | import { defineComponent } from 'vue' 3 | 4 | export const NuInstallLoadingBar = defineComponent((_, { slots }) => { 5 | window.$loadingBar = useLoadingBar() 6 | return slots.default as any 7 | }) 8 | 9 | export const NuGlobalLoadingBar = defineComponent({ 10 | name: 'NuGlobalLoadingBar', 11 | props: loadingBarProviderProps, 12 | setup(props, { slots }) { 13 | return () => ( 14 | 15 | 16 | {slots} 17 | 18 | 19 | ) 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /docs/components/form/demo/toolbars.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 32 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/demo/toolbars.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 32 | -------------------------------------------------------------------------------- /packages/components/provider/src/components/GlobalNotification.tsx: -------------------------------------------------------------------------------- 1 | import { NNotificationProvider, notificationProviderProps, useNotification } from 'naive-ui' 2 | import { defineComponent } from 'vue' 3 | 4 | export const NuInstallNotification = defineComponent((_, { slots }) => { 5 | window.$notification = useNotification() 6 | return slots.default as any 7 | }) 8 | 9 | export const NuGlobalNotification = defineComponent({ 10 | name: 'NuGlobalNotification', 11 | props: notificationProviderProps, 12 | setup(props, { slots }) { 13 | return () => ( 14 | 15 | 16 | {slots} 17 | 18 | 19 | ) 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@naive-ultra/utils", 3 | "type": "module", 4 | "version": "0.5.0", 5 | "main": "./src/index.ts", 6 | "publishConfig": { 7 | "main": "./dist/index.cjs", 8 | "types": "./dist/index.d.ts", 9 | "module": "./dist/index.js", 10 | "unpkg": "./dist/index.iife.min.js", 11 | "jsdelivr": "./dist/index.iife.min.js", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs" 16 | } 17 | } 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "build": "tsup", 24 | "prepublish": "npm run build" 25 | }, 26 | "dependencies": { 27 | "defu": "^6.1.4", 28 | "vue": "catalog:share" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/components/table/src/composables/useColumnLink.ts: -------------------------------------------------------------------------------- 1 | import type { TableBaseColumn, TableColumn } from './useColumns' 2 | import { NButton } from 'naive-ui' 3 | import { h } from 'vue' 4 | 5 | export function useColumnLink(linkKey: string, column: Partial>): TableColumn { 6 | return { 7 | key: linkKey, 8 | ...column, 9 | render: (row: any, index) => { 10 | const content = column.render?.(row, index) || row[column.key || linkKey] 11 | if (content === '-' || !content) 12 | return '-' 13 | if (!row[linkKey]) 14 | return content 15 | return h(NButton, { tag: 'a', href: row[linkKey], target: '_blank', text: true, type: 'primary' }, { default: () => content }) 16 | }, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/components/form/demo/field.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 34 | -------------------------------------------------------------------------------- /packages/components/provider/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export function packer(target: T, keys: K[], pack: (source: T[K], key: K) => void) { 2 | for (const key of keys) { 3 | const source = target[key] 4 | pack(source, key) 5 | } 6 | } 7 | 8 | export type Deferred = Promise & { resolve: (value: T) => void, reject: () => void } 9 | 10 | export function createDeferred(): Deferred { 11 | let resolve: any, reject: any 12 | 13 | const promise = new Promise((_resolve, _reject) => { 14 | resolve = _resolve 15 | reject = _reject 16 | }) as unknown as any 17 | 18 | promise.resolve = (v: any) => { 19 | resolve(v) 20 | return promise 21 | } 22 | promise.reject = reject 23 | 24 | return promise 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "jsx": "preserve", 5 | "jsxImportSource": "vue", 6 | "lib": [ 7 | "esnext", 8 | "dom" 9 | ], 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "strict": true, 14 | "declaration": true, 15 | "downlevelIteration": true, 16 | "sourceMap": true, 17 | "esModuleInterop": true, 18 | "skipDefaultLibCheck": true, 19 | "skipLibCheck": true 20 | }, 21 | "include": [ 22 | "./**/*.ts", 23 | "./**/*.d.ts", 24 | "./**/*.tsx", 25 | "./**/*.vue", 26 | "types/**.d.ts", 27 | "types/**/**.d.ts" 28 | ], 29 | "exclude": [ 30 | "**/dist/*.ts", 31 | "**/dist/*.d.ts" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "jsx": "preserve", 5 | "jsxImportSource": "vue", 6 | "lib": [ 7 | "esnext", 8 | "dom" 9 | ], 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "strict": true, 14 | "declaration": true, 15 | "downlevelIteration": true, 16 | "sourceMap": true, 17 | "esModuleInterop": true, 18 | "skipDefaultLibCheck": true, 19 | "skipLibCheck": true 20 | }, 21 | "include": [ 22 | "./**/*.ts", 23 | "./**/*.d.ts", 24 | "./**/*.tsx", 25 | "./**/*.vue", 26 | "types/**.d.ts", 27 | "types/**/**.d.ts" 28 | ], 29 | "exclude": [ 30 | "**/dist/*.ts", 31 | "**/dist/*.d.ts" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/demo/field-render.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 29 | -------------------------------------------------------------------------------- /packages/components/form/src/config/index.ts: -------------------------------------------------------------------------------- 1 | import { NuCheckboxGroup } from '@naive-ultra/checkbox' 2 | import { NuRadioGroup } from '@naive-ultra/radio' 3 | import { NAutoComplete, NButton, NCascader, NDatePicker, NInput, NInputNumber, NMention, NRate, NSelect, NSlider, NSwitch, NTimePicker } from 'naive-ui' 4 | 5 | export const FieldComponents: Record = { 6 | 'date-picker': NDatePicker, 7 | 'select': NSelect, 8 | 'auto-complete': NAutoComplete, 9 | 'cascader': NCascader, 10 | 'input': NInput, 11 | 'input-number': NInputNumber, 12 | 'button': NButton, 13 | 'switch': NSwitch, 14 | 'checkbox-group': NuCheckboxGroup, 15 | 'radio-group': NuRadioGroup, 16 | 'rate': NRate, 17 | 'slider': NSlider, 18 | 'time-picker': NTimePicker, 19 | 'mention': NMention, 20 | } 21 | -------------------------------------------------------------------------------- /packages/components/form/src/types/instance.ts: -------------------------------------------------------------------------------- 1 | import type { FormInst, FormItemInst, FormRules } from 'naive-ui' 2 | import type { ShouldRuleBeApplied } from 'naive-ui/es/form/src/interface' 3 | import type { DecodeValues, RecordFormItemConfigExport } from './config' 4 | import type { Data, TransformData } from './data' 5 | 6 | export interface UltraFormInstance { 7 | values: DecodeValues 8 | data: Data 9 | dataTrans: TransformData 10 | validate: (filters?: string[] | ShouldRuleBeApplied) => Promise 11 | resetValidate: (fields?: string[]) => void 12 | resetFields: (fields?: string[]) => void 13 | _formInstRef: FormInst | undefined 14 | _formItemInstRefs: FormItemInst[] 15 | _rules: FormRules 16 | } 17 | -------------------------------------------------------------------------------- /docs/zh-CN/components/table/demo/basic.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 32 | -------------------------------------------------------------------------------- /docs/components/table/demo/basic.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 32 | -------------------------------------------------------------------------------- /packages/components/provider/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@naive-ultra/provider", 3 | "type": "module", 4 | "version": "0.5.0", 5 | "main": "./src/index.ts", 6 | "publishConfig": { 7 | "main": "./dist/index.cjs", 8 | "types": "./dist/index.d.ts", 9 | "module": "./dist/index.js", 10 | "unpkg": "./dist/index.iife.min.js", 11 | "jsdelivr": "./dist/index.iife.min.js", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs" 16 | } 17 | } 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "build": "tsup", 24 | "prepublish": "npm run build" 25 | }, 26 | "dependencies": { 27 | "defu": "catalog:utils", 28 | "naive-ui": "catalog:share", 29 | "vue": "catalog:share" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/components/actions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@naive-ultra/actions", 3 | "type": "module", 4 | "version": "0.5.0", 5 | "main": "./src/index.ts", 6 | "publishConfig": { 7 | "main": "./dist/index.cjs", 8 | "types": "./dist/index.d.ts", 9 | "module": "./dist/index.js", 10 | "unpkg": "./dist/index.iife.min.js", 11 | "jsdelivr": "./dist/index.iife.min.js", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs" 16 | } 17 | } 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "build": "tsup", 24 | "prepublish": "npm run build" 25 | }, 26 | "dependencies": { 27 | "@naive-ultra/utils": "workspace:^", 28 | "naive-ui": "catalog:share", 29 | "vue": "catalog:share" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/demo/clone.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /docs/components/form/demo/field-render.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 29 | -------------------------------------------------------------------------------- /docs/components/table/demo/pagination.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 32 | -------------------------------------------------------------------------------- /docs/zh-CN/components/checkbox/demo/focus-blur.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 31 | -------------------------------------------------------------------------------- /docs/components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // @ts-nocheck 3 | // Generated by unplugin-vue-components 4 | // Read more: https://github.com/vuejs/core/pull/3399 5 | // biome-ignore lint: disable 6 | export {} 7 | 8 | /* prettier-ignore */ 9 | declare module 'vue' { 10 | export interface GlobalComponents { 11 | NButton: typeof import('naive-ui')['NButton'] 12 | NCheckbox: typeof import('naive-ui')['NCheckbox'] 13 | NSpace: typeof import('naive-ui')['NSpace'] 14 | NSwitch: typeof import('naive-ui')['NSwitch'] 15 | NuActions: typeof import('naive-ultra')['NuActions'] 16 | NuCheckboxGroup: typeof import('naive-ultra')['NuCheckboxGroup'] 17 | NuForm: typeof import('naive-ultra')['NuForm'] 18 | NuRadioGroup: typeof import('naive-ultra')['NuRadioGroup'] 19 | NuTable: typeof import('naive-ultra')['NuTable'] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/components/actions/demo/form.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 39 | -------------------------------------------------------------------------------- /docs/zh-CN/components/table/demo/pagination.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 32 | -------------------------------------------------------------------------------- /docs/components/checkbox/demo/focus-blur.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 31 | -------------------------------------------------------------------------------- /docs/zh-CN/components/actions/demo/form.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 39 | -------------------------------------------------------------------------------- /packages/components/actions/src/types/index.ts: -------------------------------------------------------------------------------- 1 | import type { NestedRefs } from '@naive-ultra/utils' 2 | import type { ButtonProps } from 'naive-ui' 3 | import type { VNodeChild } from 'vue' 4 | 5 | export interface ActionsProps extends NestedRefs> { 6 | helper?: (...args: T) => Promise | void 7 | render?: string | ((...args: T) => VNodeChild) 8 | enable?: boolean | ((...args: T) => boolean) 9 | slots?: Record VNodeChild> 10 | custom?: (...args: T) => VNodeChild 11 | } 12 | 13 | export interface ActionsInstance { 14 | (...args: T): VNodeChild 15 | } 16 | 17 | export interface ActionsParsedProps extends ButtonProps { 18 | slots?: Record any> 19 | custom?: (...args: any[]) => VNodeChild | VNodeChild[] 20 | } 21 | -------------------------------------------------------------------------------- /packages/components/radio/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@naive-ultra/radio", 3 | "type": "module", 4 | "version": "0.5.0", 5 | "main": "./src/index.ts", 6 | "publishConfig": { 7 | "main": "./dist/index.cjs", 8 | "types": "./dist/index.d.ts", 9 | "module": "./dist/index.js", 10 | "unpkg": "./dist/index.iife.min.js", 11 | "jsdelivr": "./dist/index.iife.min.js", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs" 16 | } 17 | } 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "build": "tsup", 24 | "prepublish": "npm run build" 25 | }, 26 | "dependencies": { 27 | "@naive-ultra/utils": "workspace:^", 28 | "@vueuse/core": "catalog:share", 29 | "naive-ui": "catalog:share", 30 | "vue": "catalog:share" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/components/checkbox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@naive-ultra/checkbox", 3 | "type": "module", 4 | "version": "0.5.0", 5 | "main": "./src/index.ts", 6 | "publishConfig": { 7 | "main": "./dist/index.cjs", 8 | "types": "./dist/index.d.ts", 9 | "module": "./dist/index.js", 10 | "unpkg": "./dist/index.iife.min.js", 11 | "jsdelivr": "./dist/index.iife.min.js", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs" 16 | } 17 | } 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "build": "tsup", 24 | "prepublish": "npm run build" 25 | }, 26 | "dependencies": { 27 | "@naive-ultra/utils": "workspace:^", 28 | "@vueuse/core": "catalog:share", 29 | "naive-ui": "catalog:share", 30 | "vue": "catalog:share" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/components/form/demo/clone.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /docs/components/form/demo/form.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /docs/zh-CN/components/checkbox/index.md: -------------------------------------------------------------------------------- 1 | # Ultra Checkbox 2 | 3 | 以配置项创建复选框组。 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ## Props 12 | 13 | | 名称 | 类型 | 默认值 | 说明 | 14 | | --- | --- | --- | --- | 15 | | options | `CheckboxMixedOption[]` | `-` | 复选框配置 | 16 | | space | `SpaceProps` | `-` | 间隔配置 | 17 | 18 | > 更多参数参考 [checkbox-group](https://www.naiveui.com/zh-CN/os-theme/components/checkbox#CheckboxGroup-Props)。 19 | 20 | ### CheckboxOption Properties 21 | 22 | | 名称 | 类型 | 说明 | 23 | | --- | --- | --- | 24 | | ref | `CheckboxInst \| Ref` | 用于绑定某个 checkbox 实例 | 25 | | slots | `Record JSX.Element \| undefined>` | Checkbox Slots | 26 | 27 | > 更多参数参考 [checkbox](https://www.naiveui.com/zh-CN/os-theme/components/checkbox#Checkbox-Props)。 28 | -------------------------------------------------------------------------------- /packages/components/table/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@naive-ultra/table", 3 | "type": "module", 4 | "version": "0.5.0", 5 | "main": "./src/index.ts", 6 | "publishConfig": { 7 | "main": "./dist/index.cjs", 8 | "types": "./dist/index.d.ts", 9 | "module": "./dist/index.js", 10 | "unpkg": "./dist/index.iife.min.js", 11 | "jsdelivr": "./dist/index.iife.min.js", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs" 16 | } 17 | } 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "build": "tsup", 24 | "prepublish": "npm run build" 25 | }, 26 | "dependencies": { 27 | "@naive-ultra/utils": "workspace:^", 28 | "@vueuse/core": "catalog:share", 29 | "naive-ui": "catalog:share", 30 | "nanoid": "catalog:utils", 31 | "vue": "catalog:share" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/demo/form.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | id-token: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | tags: 10 | - 'v*' 11 | 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | - uses: pnpm/action-setup@v4 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: lts/* 23 | registry-url: https://registry.npmjs.org/ 24 | 25 | - run: pnpm dlx changelogithub 26 | env: 27 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 28 | 29 | # # Uncomment the following lines to publish to npm on CI 30 | # 31 | # - run: pnpm install 32 | # - run: pnpm publish -r --access public 33 | # env: 34 | # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 35 | # NPM_CONFIG_PROVENANCE: true 36 | -------------------------------------------------------------------------------- /docs/zh-CN/components/radio/index.md: -------------------------------------------------------------------------------- 1 | # Ultra Radio 2 | 3 | 以配置项创建单选框组。 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ## Props 12 | 13 | | 名称 | 类型 | 默认值 | 说明 | 14 | | --- | --- | --- | --- | 15 | | options | `CheckboxMixedOption[]` | `-` | 复选框配置 | 16 | | space | `SpaceProps` | `-` | 间隔配置 | 17 | | type | `'default' \| 'button'` | `-` | 单选组类型 | 18 | 19 | > 更多参数参考 [radio-group](https://www.naiveui.com/zh-CN/os-theme/components/radio#RadioGroup-Props)。 20 | 21 | ### RadioOption Properties 22 | 23 | | 名称 | 类型 | 说明 | 24 | | --- | --- | --- | 25 | | slots | `Record JSX.Element \| undefined>` | Radio Slots | 26 | 27 | > 更多参数参考 [radio, radio-button](https://www.naiveui.com/zh-CN/os-theme/components/radio#Radio-Props,-RadioButton-Props)。 28 | -------------------------------------------------------------------------------- /packages/components/dialog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@naive-ultra/dialog", 3 | "type": "module", 4 | "version": "0.5.0", 5 | "private": true, 6 | "main": "./src/index.ts", 7 | "publishConfig": { 8 | "main": "./dist/index.cjs", 9 | "types": "./dist/index.d.ts", 10 | "module": "./dist/index.js", 11 | "unpkg": "./dist/index.iife.min.js", 12 | "jsdelivr": "./dist/index.iife.min.js", 13 | "exports": { 14 | ".": { 15 | "import": "./dist/index.js", 16 | "require": "./dist/index.cjs" 17 | } 18 | } 19 | }, 20 | "files": [ 21 | "dist" 22 | ], 23 | "scripts": { 24 | "build": "tsup", 25 | "prepublish": "npm run build" 26 | }, 27 | "dependencies": { 28 | "@hairy/utils": "catalog:utils", 29 | "@naive-ultra/utils": "workspace:^", 30 | "@vueuse/core": "catalog:share", 31 | "naive-ui": "catalog:share", 32 | "vue": "catalog:share" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "private": true, 4 | "scripts": { 5 | "docs:dev": "vitepress dev", 6 | "docs:build": "vitepress build", 7 | "docs:preview": "vitepress preview" 8 | }, 9 | "dependencies": { 10 | "@vue/theme": "^2.3.0", 11 | "naive-ui": "catalog:share", 12 | "naive-ultra": "workspace:^" 13 | }, 14 | "devDependencies": { 15 | "@iconify-json/svg-spinners": "catalog:docs", 16 | "@shikijs/vitepress-twoslash": "catalog:docs", 17 | "@vitejs/plugin-vue-jsx": "catalog:docs", 18 | "floating-vue": "catalog:docs", 19 | "unplugin-auto-import": "catalog:share", 20 | "unplugin-vue-components": "catalog:share", 21 | "vite": "catalog:cli", 22 | "vite-tsconfig-paths": "catalog:docs", 23 | "vitepress": "catalog:docs", 24 | "vitepress-plugin-demo": "catalog:docs", 25 | "vitepress-plugin-group-icons": "catalog:docs", 26 | "vue": "catalog:share" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /docs/zh-CN/components/radio/demo/buttons.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 30 | -------------------------------------------------------------------------------- /docs/components/radio/demo/buttons.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 30 | -------------------------------------------------------------------------------- /packages/components/form/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@naive-ultra/form", 3 | "type": "module", 4 | "version": "0.5.0", 5 | "main": "./src/index.ts", 6 | "publishConfig": { 7 | "main": "./dist/index.cjs", 8 | "types": "./dist/index.d.ts", 9 | "module": "./dist/index.js", 10 | "unpkg": "./dist/index.iife.min.js", 11 | "jsdelivr": "./dist/index.iife.min.js", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs" 16 | } 17 | } 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "build": "tsup", 24 | "prepublish": "npm run build" 25 | }, 26 | "dependencies": { 27 | "@naive-ultra/checkbox": "workspace:*", 28 | "@naive-ultra/radio": "workspace:*", 29 | "@naive-ultra/utils": "workspace:^", 30 | "@vueuse/core": "catalog:share", 31 | "naive-ui": "catalog:share", 32 | "vue": "catalog:share" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/field-context.md: -------------------------------------------------------------------------------- 1 | # Field Context 2 | 3 | Field Context(字段上下文)是 `naive-ultra` 表单体系中的高级特性,用于在表单项之间传递和共享上下文信息,便于实现表单项间的依赖、联动、动态校验等复杂场景。 4 | 5 | ::: demo src="./demo/field-context.vue" title="字段上下文" 6 | `field|config` 传入函数可接收当前使用的 form 实例,用于组合校验。 7 | ::: 8 | 9 | ## 获取和使用 Field Context 10 | 11 | 在表单项的配置函数、`renderItem` 自定义渲染函数中,可以通过参数获取 context。例如: 12 | 13 | ```ts 14 | import { defineForm } from 'naive-ultra' 15 | 16 | const form = defineForm({ 17 | username: { 18 | label: '用户名', 19 | value: '', 20 | rules: [{ required: true, message: '请输入用户名' }], 21 | }, 22 | password: field(context => ({ 23 | label: '密码', 24 | value: '', 25 | rules: [ 26 | { 27 | validator: (rule, value) => { 28 | if (context.data.username === 'admin' && !value) 29 | return Promise.reject('管理员必须设置密码') 30 | return Promise.resolve() 31 | }, 32 | }, 33 | ], 34 | })), 35 | }) 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/zh-CN/guide/intro.md: -------------------------------------------------------------------------------- 1 | # The concept of ProComponents 2 | 3 | 类似于 Ant Design Pro Components,Naive Ultra 是一个基于 Naive UI 的高级组件库,为后台系统提供更高级的抽象和封装。 4 | 5 | 我们对 Naive Ultra 的设计进行了一些调整,以更好地与 Naive UI 的设计风格保持一致,并提供快速高效地构建高质量后端应用程序的能力。 6 | 7 | ## Design Ideas 8 | 9 | > 以下内容来自 Ant Pro Components 10 | 11 | 对于几乎任何业务来说,我们实际上是基于状态定义了一系列行为。以表格为例,首先我们需要一个状态 `data` 用于存储从服务器请求的数据,为了优化体验,我们还需要一个 `loading`。所以我们有了一系列行为,我们需要首先设置 `loading=true`,然后发起网络请求,网络请求完成后设置 `data` 为请求的数据,`loading=false`,一个网络请求完成了,虽然非常简单,但一个业务系统有相当数量的表格,每个表格只定义一次,工作量非常大。 12 | 13 | 如果要重新请求网络,我们需要将上述行为封装成一个方法,点击重新加载数据,如果有分页,那么还需要一个新的变量 page,我们需要根据需要在重新请求之前确定是否将页码重置为第一页,这引入了另一个变量。如果您的表单还需要控制每页显示的数量,那么将会更加繁琐。这种重复的工作会浪费我们大量的时间。 14 | 15 | ## A component !≈ a page 16 | 17 | 我们并不希望您将组件视为页面,而是将其视为功能的超集。这样可以使您的代码保持灵活性,这也是它与 Ant Design Pro Components 的区别之处。 18 | 19 | 列表页面可以使用 `pro-form` + `pro-table` 组合,而编辑页面可以利用 `pro-form` + `button` 或其他各种组件。这使我们能够专注于实现核心业务逻辑和页面效果。 20 | 21 | 我们对开发新的组件会尽可能的谨慎,我们会尽可能的将组件的功能拆分到更小的组件中,以便您可以更好的组合它们。 22 | -------------------------------------------------------------------------------- /packages/components/form/src/composables/useRules.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeRef } from '@vueuse/core' 2 | import type { FormItemRule } from 'naive-ui' 3 | import type { FormItemConfig, FormItemConfigWithKey } from '../types' 4 | 5 | import { computed, unref } from 'vue' 6 | 7 | export function useRules(values: MaybeRef>) { 8 | return computed(() => { 9 | const entries = Object.keys(unref(values)) 10 | .map((key) => { 11 | const value = unref(values)[key] as FormItemConfigWithKey 12 | key = value.key || key 13 | return [value.key || key, parseRules(key, unref(value.rules), value.validate)] 14 | }) 15 | return Object.fromEntries(entries) 16 | }) 17 | } 18 | 19 | function parseRules(key: string, rules: FormItemRule | FormItemRule[] = [], validate?: MaybeRef) { 20 | if (unref(validate) === false) 21 | return [] 22 | if (!Array.isArray(rules)) 23 | rules = [rules] 24 | return rules.map(rule => ({ trigger: 'blur', key, ...rule })) 25 | } 26 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Naive Ultra" 7 | text: "Naive UI Components" 8 | tagline: Make middle and back-end development simple and efficient 9 | image: /logo.svg 10 | actions: 11 | - theme: brand 12 | text: Get Started 13 | link: /guide/intro 14 | - theme: alt 15 | text: View on GitHub 16 | link: https://github.com/hairyf/naive-ultra 17 | 18 | features: 19 | - title: Simple & Easy to Use 20 | details: Provides unified API design concepts and example documentation, allowing you to get started quickly and greatly improve the development efficiency of middle and back-end systems. 21 | - title: Naive UI 22 | details: Uses Naive UI components as the standard, and extends on this basis to meet complex business scenarios. 23 | - title: TypeScript 24 | details: Written in TypeScript, with complete type inference and type hints, and detailed TS documentation to ensure development experience and code quality. 25 | --- 26 | -------------------------------------------------------------------------------- /docs/zh-CN/components/radio/demo/sizes.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 31 | -------------------------------------------------------------------------------- /docs/components/radio/demo/sizes.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 31 | -------------------------------------------------------------------------------- /docs/zh-CN/components/actions/index.md: -------------------------------------------------------------------------------- 1 | # Ultra Actions 2 | 3 | Ultra Actions 通常用于在 Table Columns 与 Form Toolbars 中,它也可以使用 `` 独立渲染。 4 | 5 | 它返回一个 Function Component,该 Function Component 会根据传入的参数渲染组件。 6 | 7 | 8 | 9 | ::: demo src="./demo/form.vue" title="表单工具栏" 10 | 11 | 与 Form Toolbars 配合使用,可以快速制作表单工具栏。 12 | 13 | ::: 14 | 15 | 16 | 17 | ::: demo src="./demo/custom.vue" title="自定义渲染" 18 | 19 | 默认情况下,Actions 仅渲染 button 控件,如果需要使用其他组件,可以使用 `custom` 属性。 20 | 21 | ::: 22 | 23 | ## Item Props 24 | 25 | | 名称 | 类型 | 默认值 | 说明 | 26 | | --- | --- | --- | --- | 27 | | render | `string \| (...args) => string \| VNodeChild` | `-` | 渲染按钮控件项目内容 | 28 | | helper | `(...args) => void \| Promise` | `-` | 处理点击事件,如果返回的是 `promise` 将自动开启 `loading` | 29 | | enable | `(...args) => void \| Promise` | `-` | 是否启用控件,返回 `false` 将不渲染该控件 | 30 | | custom | `(...args) => VNodeChild` | `-` | 自定义渲染 | 31 | 32 | > 更多参数请参考 [n-button](https://www.naiveui.com/zh-CN/light/components/button)。 33 | -------------------------------------------------------------------------------- /packages/components/provider/src/components/InstallProvider.tsx: -------------------------------------------------------------------------------- 1 | import type { Component, PropType, VNode } from 'vue' 2 | import { defineComponent, h } from 'vue' 3 | 4 | export interface InstallComponentOptions { component: Component, props: any } 5 | export type InstallComponentItem = Component | InstallComponentOptions 6 | 7 | export const NuInstallProvider = defineComponent({ 8 | props: { 9 | install: { 10 | type: Array as PropType, 11 | default: () => [], 12 | }, 13 | }, 14 | setup({ install }, { slots }) { 15 | function render(content: VNode, { component, props }: any) { 16 | return h(component, props, () => content) 17 | } 18 | return () => resolve(install).reduceRight(render, slots.default?.() as any) 19 | }, 20 | }) 21 | 22 | function resolve(components: InstallComponentItem[]): InstallComponentOptions[] { 23 | return components.map((item: any) => { 24 | if (!item.component) 25 | return { component: item as Component, props: {} } 26 | else 27 | return item 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /docs/components/table/demo/form.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 47 | -------------------------------------------------------------------------------- /docs/components/actions/demo/table.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 45 | -------------------------------------------------------------------------------- /docs/zh-CN/components/table/demo/form.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 47 | -------------------------------------------------------------------------------- /docs/zh-CN/components/actions/demo/table.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 45 | -------------------------------------------------------------------------------- /packages/components/table/src/types/index.ts: -------------------------------------------------------------------------------- 1 | import type { PromisifyFn } from '@vueuse/core' 2 | import type { DataTableInst } from 'naive-ui' 3 | import type { UnwrapNestedRefs } from 'vue' 4 | import type { OffsetPagination, ServerPaginationResolve } from '../composables' 5 | 6 | export interface ScrollTo { 7 | (x: number, y: number): void 8 | (options: { 9 | left?: number 10 | top?: number 11 | behavior?: ScrollBehavior 12 | }): void 13 | } 14 | 15 | export interface DataResolved { 16 | data: T 17 | total: number 18 | } 19 | 20 | export interface UltraTableRequest { 21 | (pagination: ServerPaginationResolve): DataResolved | Promise> 22 | } 23 | 24 | export interface UltraTableInstance { 25 | data: T 26 | loading: boolean 27 | pagination: UnwrapNestedRefs 28 | next: () => void 29 | prev: () => void 30 | request: UltraTableRequest 31 | requestAll: () => Promise 32 | search: PromisifyFn<(pagination?: ServerPaginationResolve) => void> 33 | reset: PromisifyFn<() => void> 34 | _tableInstRef: DataTableInst | undefined 35 | } 36 | -------------------------------------------------------------------------------- /docs/zh-CN/components/table/hooks.md: -------------------------------------------------------------------------------- 1 | # Table Hooks 2 | 3 | ::: demo src="./demo/hooks.vue" title="常用 hooks 用法" 4 | Table Hooks 提供了一组用于简化表格列配置、索引列、链接列等常用场景的工具函数,让 UltraTable 的列配置更灵活、可复用。 5 | ::: 6 | 7 | ## useColumns 8 | 9 | 用于批量处理和生成表格列配置,支持动态过滤、默认最小宽度等。 10 | 11 | ```ts 12 | import { useColumns } from 'naive-ultra' 13 | 14 | const columns = useColumns([ 15 | { key: 'name', title: '姓名' }, 16 | { key: 'age', title: '年龄' }, 17 | ]) 18 | ``` 19 | 20 | ## useColumnIndexes 21 | 22 | 快速生成静态索引列(自动序号),常用于表格首列。 23 | 24 | ```ts 25 | import { useColumnIndexes } from 'naive-ultra' 26 | 27 | const columns = [ 28 | useColumnIndexes(table.pagination), 29 | { key: 'name', title: '姓名' }, 30 | ] 31 | ``` 32 | 33 | ## useColumnLink 34 | 35 | 快速生成链接列,支持自定义渲染内容和跳转地址。 36 | 37 | ```ts 38 | import { useColumnLink } from 'naive-ultra' 39 | 40 | const columns = [ 41 | useColumnLink('url', { title: '官网', render: row => row.name }), 42 | ] 43 | ``` 44 | 45 | ## 其他常用 hooks 46 | 47 | - `useTableMinWidth(columns)`:自动计算表格最小宽度,适配响应式布局。 48 | - `useOffsetPagination(options)`:分页状态管理,配合 UltraTable 使用。 49 | 50 | > 你可以将这些 hooks 灵活组合,快速实现复杂表格场景。 51 | -------------------------------------------------------------------------------- /packages/components/form/src/define/index.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeRef } from '@vueuse/core' 2 | import type { FormExtendsConfig, RecordFormItemConfigExport, UltraFormInstance } from '../types' 3 | 4 | import { reactive } from 'vue' 5 | import { useData, useMetadata, useRules, useValues } from '../composables' 6 | 7 | export type DefineFormReturn = T extends RecordFormItemConfigExport ? UltraFormInstance : UltraFormInstance 8 | 9 | export function defineForm(initialValues: MaybeRef): DefineFormReturn { 10 | const metadata = useMetadata() 11 | const values = useValues(metadata, initialValues) 12 | const formData = useData(metadata) 13 | const rules = useRules(values) 14 | 15 | const instance = reactive({ 16 | values, 17 | data: formData.data, 18 | dataTrans: formData.dataTrans, 19 | resetFields: formData.resetFields, 20 | validate: metadata.validate, 21 | resetValidate: metadata.resetValidate, 22 | _formInstRef: metadata.formInstRef, 23 | _formItemInstRefs: metadata.formItemInstRefs, 24 | _rules: rules, 25 | }) 26 | 27 | return instance 28 | } 29 | -------------------------------------------------------------------------------- /docs/components/table/demo/watcher.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 49 | -------------------------------------------------------------------------------- /docs/zh-CN/components/table/demo/watcher.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 49 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025-PRESENT Hairyf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Disable the default formatter, use eslint instead 3 | "prettier.enable": false, 4 | "editor.formatOnSave": false, 5 | 6 | // Auto fix 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": "explicit", 9 | "source.organizeImports": "never" 10 | }, 11 | 12 | // Silent the stylistic rules in you IDE, but still auto fix them 13 | "eslint.rules.customizations": [ 14 | { "rule": "style/*", "severity": "off" }, 15 | { "rule": "*-indent", "severity": "off" }, 16 | { "rule": "*-spacing", "severity": "off" }, 17 | { "rule": "*-spaces", "severity": "off" }, 18 | { "rule": "*-order", "severity": "off" }, 19 | { "rule": "*-dangle", "severity": "off" }, 20 | { "rule": "*-newline", "severity": "off" }, 21 | { "rule": "*quotes", "severity": "off" }, 22 | { "rule": "*semi", "severity": "off" } 23 | ], 24 | 25 | // Enable eslint for all supported languages 26 | "eslint.validate": [ 27 | "javascript", 28 | "javascriptreact", 29 | "typescript", 30 | "typescriptreact", 31 | "vue", 32 | "html", 33 | "markdown", 34 | "json", 35 | "jsonc", 36 | "yaml" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /docs/zh-CN/components/table/demo/hooks.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.codeeditor.ts: -------------------------------------------------------------------------------- 1 | import type { NaiveUIContainerOptions } from 'vitepress-plugin-demo/client/naive-ui' 2 | import { indexHtml, srcMainTs, viteConfigJs } from './index.codeeditor.files' 3 | 4 | export const codeeditor: NaiveUIContainerOptions['codeeditor'] = { 5 | editors: ['stackblitz', 'codesandbox'], 6 | globals: { 7 | package: { 8 | scripts: { start: 'vite' }, 9 | dependencies: { 10 | 'naive-ultra': 'latest', 11 | 'vue': 'latest', 12 | }, 13 | devDependencies: { 14 | 'unplugin-vue-components': 'latest', 15 | 'unplugin-auto-import': 'latest', 16 | '@vitejs/plugin-vue': 'latest', 17 | 'typescript': 'latest', 18 | 'vite': 'latest', 19 | }, 20 | }, 21 | files: { 22 | 'vite.config.js': { content: viteConfigJs }, 23 | 'src/main.ts': { content: srcMainTs }, 24 | 'index.html': { content: indexHtml }, 25 | }, 26 | }, 27 | resolve(props) { 28 | const content = decodeURIComponent(props.tsCode || props.jsCode) 29 | return { 30 | opens: ['src/App.vue'], 31 | files: { 32 | 'src/App.vue': { content }, 33 | }, 34 | } 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/data-trans.md: -------------------------------------------------------------------------------- 1 | # Form Data Transform 2 | 3 | 数据转换通常用于将表单数据进行预处理,并转换为后端可识别的格式,而 Ultra Form 内置 transform 实现这一点,并通过访问 form 的实例中的 `dataTrans` 获得已转换的数据,并提供一流的 TypeScript 支持。 4 | 5 | 6 | 7 | 需要注意的是,你需要通过 field 函数定义字段,以便 Typescript 能够正确推断类型。 8 | 9 | ```ts 10 | import { field } from 'naive-ultra' 11 | 12 | const form = defineForm({ 13 | time: field({ 14 | type: 'date-picker', 15 | props: { type: 'datetimerange', clearable: true }, 16 | value: null as null | string[], 17 | transform(value) { 18 | return { 19 | startAt: value?.[0] ?? null, 20 | endAt: value?.[1] ?? null, 21 | } 22 | }, 23 | }), 24 | time2: { 25 | type: 'date-picker', 26 | props: { type: 'datetimerange', clearable: true }, 27 | value: null as null | string[], 28 | transform(value) { 29 | return { 30 | startAt2: value?.[0] ?? null, 31 | endAt2: value?.[1] ?? null, 32 | } 33 | }, 34 | } 35 | }) 36 | form.dataTrans.endAt // string | null 37 | form.dataTrans.startAt // string | null 38 | form.dataTrans.endAt2 // any 39 | form.dataTrans.startAt2 // any 40 | ``` 41 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - playground 3 | - docs 4 | - packages/**/* 5 | - examples/**/* 6 | catalogs: 7 | bundler: 8 | esbuild-plugin-globals: ^0.2.0 9 | tsup: 6.6.0 10 | unbuild: ^3.5.0 11 | unplugin-vue-jsx: ^0.6.1 12 | cli: 13 | '@antfu/eslint-config': ^4.16.2 14 | '@antfu/ni': ^23.3.1 15 | bumpp: ^10.2.0 16 | eslint: ^9.30.1 17 | lint-staged: ^15.5.2 18 | pnpm: ^10.12.4 19 | simple-git-hooks: ^2.13.0 20 | tsx: ^4.20.3 21 | typescript: ^5.8.3 22 | vite: ^7.0.3 23 | docs: 24 | '@iconify-json/svg-spinners': ^1.2.2 25 | '@shikijs/vitepress-twoslash': ^3.7.0 26 | '@vitejs/plugin-vue-jsx': ^5.0.1 27 | floating-vue: ^5.2.2 28 | vite-tsconfig-paths: ^5.1.4 29 | vitepress: ^2.0.0-alpha.8 30 | vitepress-plugin-demo: ^0.8.0 31 | vitepress-plugin-group-icons: ^1.6.1 32 | share: 33 | '@vueuse/core': ^13.5.0 34 | naive-ui: ^2.42.0 35 | unplugin-auto-import: ^0.16.7 36 | unplugin-vue-components: ^28.8.0 37 | vue: ^3.5.17 38 | tesing: 39 | vitest: ^3.2.4 40 | types: 41 | '@types/node': ^24.0.11 42 | utils: 43 | '@hairy/utils': ^1.33.0 44 | defu: ^6.1.4 45 | nanoid: ^5.1.5 46 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: pnpm/action-setup@v4 18 | with: 19 | run_install: false 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: lts/* 23 | cache: pnpm 24 | 25 | - run: pnpm i -g @antfu/ni 26 | - run: nci 27 | - run: nr lint 28 | - run: nr typecheck 29 | 30 | test: 31 | runs-on: ${{ matrix.os }} 32 | 33 | strategy: 34 | matrix: 35 | node: [lts/*] 36 | os: [ubuntu-latest, windows-latest, macos-latest] 37 | fail-fast: false 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: pnpm/action-setup@v4 42 | with: 43 | run_install: false 44 | - name: Set node ${{ matrix.node }} 45 | uses: actions/setup-node@v4 46 | with: 47 | node-version: ${{ matrix.node }} 48 | cache: pnpm 49 | 50 | - run: pnpm i -g @antfu/ni 51 | - run: nci 52 | - run: nr test 53 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/Outline/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable ts/ban-ts-comment */ 2 | import { resolveHeaders } from './outline.js' 3 | 4 | export function getHeaders(range) { 5 | const headers = [...document.querySelectorAll('.VPDoc h2,h3,h4,h5,h6,.n-card')] 6 | .filter(el => el.id && el.hasChildNodes()) 7 | .map((el) => { 8 | const level = Number(el.tagName[1]) 9 | return { 10 | // @ts-expect-error 11 | title: serializeHeader(el), 12 | link: `#${el.id}`, 13 | level: level || 2, 14 | } 15 | }) 16 | return resolveHeaders(headers, range) 17 | } 18 | 19 | export function serializeHeader(h: HTMLDivElement) { 20 | let ret = '' 21 | for (const node of h.childNodes) { 22 | if (h?.classList?.contains('n-card')) 23 | return serializeHeader(h.querySelector('.n-card-header__main')!) 24 | 25 | if (node.nodeType === 1) { 26 | // @ts-expect-error 27 | if (node?.classList?.contains('VPBadge') || node?.classList?.contains('header-anchor')) { 28 | continue 29 | } 30 | 31 | ret += node.textContent 32 | } 33 | else if (node.nodeType === 3) { 34 | ret += node.textContent 35 | } 36 | } 37 | return ret.trim() 38 | } 39 | -------------------------------------------------------------------------------- /docs/components/checkbox/index.md: -------------------------------------------------------------------------------- 1 | # Ultra Checkbox 2 | 3 | Create a checkbox group from configuration options. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ## Props 12 | 13 | | Name | Type | Default | Description | 14 | | ------- | --------------------- | ------- | ------------------- | 15 | | options | `CheckboxMixedOption[]` | `-` | Checkbox options | 16 | | space | `SpaceProps` | `-` | Spacing config | 17 | 18 | > For more parameters, see [checkbox-group](https://www.naiveui.com/en-US/os-theme/components/checkbox#CheckboxGroup-Props). 19 | 20 | ### CheckboxOption Properties 21 | 22 | | Name | Type | Description | 23 | | ----- | ------------------------------------ | -------------------------- | 24 | | ref | `CheckboxInst \| Ref` | Bind a specific checkbox instance | 25 | | slots | `Record JSX.Element \| undefined>` | Checkbox Slots | 26 | 27 | > For more parameters, see [checkbox](https://www.naiveui.com/en-US/os-theme/components/checkbox#Checkbox-Props). 28 | -------------------------------------------------------------------------------- /docs/components/table/demo/hooks.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /packages/components/form/src/composables/useValues.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeRef } from '@vueuse/core' 2 | import type { FieldConfigExtends, FormItemConfig } from '../types' 3 | import type { Metadata } from './useMetadata' 4 | import { isFunction, isUndefined, noop } from '@naive-ultra/utils' 5 | 6 | import { syncRef } from '@vueuse/core' 7 | import { computed, reactive, unref } from 'vue' 8 | 9 | export function useValues(metadata: Metadata, initialValues: MaybeRef) { 10 | function parse(): Record { 11 | const values = unref(initialValues) 12 | const entries = Object.keys(values).map((key) => { 13 | const target = Reflect.get(values, key) 14 | const option = isFunction(target) 15 | ? target(metadata) 16 | : target 17 | // 修复 naive-ui 无法处理 undefined 的情况 18 | if (isUndefined(option.value)) 19 | option.value = null 20 | return [option.key ?? key, option] 21 | }) 22 | return reactive(Object.fromEntries(entries)) 23 | } 24 | 25 | const values = computed({ 26 | get: parse, 27 | set: noop, 28 | }) 29 | 30 | syncRef(metadata.values, values.value, { direction: 'rtl' } as any) 31 | 32 | return values 33 | } 34 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.codeeditor.files.ts: -------------------------------------------------------------------------------- 1 | export const viteConfigJs = `\ 2 | import AutoImport from 'unplugin-auto-import/vite' 3 | import Components from 'unplugin-vue-components/vite' 4 | // import NaiveUltraResolver from 'naive-ultra/resolver' 5 | // import NaiveUltraImports from 'naive-ultra/imports' 6 | import { defineConfig } from 'vite' 7 | import Vue from '@vitejs/plugin-vue' 8 | 9 | export default defineConfig({ 10 | plugins: [ 11 | // AutoImport({ 12 | // imports: [NaiveUltraImports()], 13 | // }), 14 | // Components({ 15 | // resolvers: [NaiveUltraResolver()], 16 | // }), 17 | Vue() 18 | ], 19 | }) 20 | ` 21 | export const srcMainTs = `\ 22 | import { createApp } from 'vue'; 23 | import App from './App.vue'; 24 | 25 | createApp(App).mount('#app'); 26 | ` 27 | 28 | export const indexHtml = `\ 29 | 30 | 31 | 32 | 33 | 34 | 35 | Vite + Vue + TS 36 | 37 | 38 |
39 | 40 | 41 | 42 | ` 43 | -------------------------------------------------------------------------------- /packages/components/form/src/utils/render.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable ts/ban-ts-comment */ 2 | import type { Ref } from 'vue' 3 | import type { FormItemConfig } from '../types' 4 | 5 | import { final } from '@naive-ultra/utils' 6 | import { reactive, unref } from 'vue' 7 | import { FieldComponents } from '../config' 8 | 9 | export function renderItemField(model: Ref, config: FormItemConfig, key?: string): any { 10 | const { renderItem, ..._config } = config 11 | return renderItem?.(model, _config, renderDefField) 12 | || renderDefField(model, _config, key) 13 | } 14 | 15 | export function renderDefField(model: Ref, config: FormItemConfig, _key?: string) { 16 | const placeholder = final(config.placeholder) 17 | const Component = config.type ? FieldComponents[config.type] : null 18 | 19 | if (!config.type || !Component) 20 | return null 21 | 22 | return ( 23 | 31 | {{ ...config.slots }} 32 | 33 | ) 34 | } 35 | 36 | export function ONLY_RENDER() { 37 | return Symbol('ONLY_KEY') 38 | } 39 | -------------------------------------------------------------------------------- /docs/components/form/field-context.md: -------------------------------------------------------------------------------- 1 | # Field Context 2 | 3 | Field Context is an advanced feature in the `naive-ultra` form system, used for passing and sharing context information between form items, making it easy to implement dependencies, linkage, and dynamic validation between form items. 4 | 5 | ::: demo src="./demo/field-context.vue" title="Field Context" 6 | The function passed to `field|config` can receive the current form instance for composing validation. 7 | ::: 8 | 9 | ## Getting and Using Field Context 10 | 11 | In the config function of a form item or the custom render function `renderItem`, you can get the context via parameters. For example: 12 | 13 | ```ts 14 | import { defineForm } from 'naive-ultra' 15 | 16 | const form = defineForm({ 17 | username: { 18 | label: 'Username', 19 | value: '', 20 | rules: [{ required: true, message: 'Please enter username' }], 21 | }, 22 | password: field(context => ({ 23 | type: 'input', 24 | label: 'Password', 25 | value: '', 26 | rules: [ 27 | { 28 | validator: (rule, value) => { 29 | if (context.data.username === 'admin' && !value) 30 | return Promise.reject('Admin must set a password') 31 | return Promise.resolve() 32 | }, 33 | }, 34 | ], 35 | })), 36 | }) 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/components/radio/index.md: -------------------------------------------------------------------------------- 1 | # Ultra Radio 2 | 3 | Create a radio group from configuration options. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ## Props 12 | 13 | | Name | Type | Default | Description | 14 | | ------- | --------------------- | ------- | ------------------- | 15 | | options | `CheckboxMixedOption[]` | `-` | Radio options | 16 | | space | `SpaceProps` | `-` | Spacing config | 17 | | type | `'default' | 'button'` | `-` | Radio group type | 18 | 19 | > For more parameters, see [radio-group](https://www.naiveui.com/en-US/os-theme/components/radio#RadioGroup-Props). 20 | 21 | ### RadioOption Properties 22 | 23 | | Name | Type | Description | 24 | | ----- | ------------------------------------ | -------------------------- | 25 | | slots | `Record JSX.Element \| undefined>` | Radio Slots | 26 | 27 | > For more parameters, see [radio, radio-button](https://www.naiveui.com/en-US/os-theme/components/radio#Radio-Props,-RadioButton-Props). 28 | -------------------------------------------------------------------------------- /packages/components/main/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import { NuActions } from '@naive-ultra/actions' 3 | import { NuCheckboxGroup } from '@naive-ultra/checkbox' 4 | import { NuForm } from '@naive-ultra/form' 5 | import { NuGlobalDialog, NuGlobalMessage, NuGlobalNotification, NuGlobalProvider, NuInstallProvider } from '@naive-ultra/provider' 6 | import { NuRadioGroup } from '@naive-ultra/radio' 7 | import { NuTable } from '@naive-ultra/table' 8 | 9 | export * from '@naive-ultra/actions' 10 | export * from '@naive-ultra/checkbox' 11 | export * from '@naive-ultra/form' 12 | export * from '@naive-ultra/provider' 13 | export * from '@naive-ultra/radio' 14 | export * from '@naive-ultra/table' 15 | 16 | function install(app: App): void { 17 | app.component('NuForm', NuForm) 18 | app.component('NuTable', NuTable) 19 | app.component('NuActions', NuActions) 20 | app.component('NuRadioGroup', NuRadioGroup) 21 | app.component('NuCheckboxGroup', NuCheckboxGroup) 22 | app.component('NuGlobalProvider', NuGlobalProvider) 23 | app.component('NuGlobalDialog', NuGlobalDialog) 24 | app.component('NuGlobalMessage', NuGlobalMessage) 25 | app.component('NuGlobalNotification', NuGlobalNotification) 26 | app.component('NuInstallProvider', NuInstallProvider) 27 | } 28 | 29 | export { install } 30 | 31 | export default install 32 | -------------------------------------------------------------------------------- /packages/components/actions/src/composables/index.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue' 2 | import type { ActionsParsedProps, ActionsProps } from '../types' 3 | import { useAsyncCallback } from '@naive-ultra/utils' 4 | import { computed, reactive, unref } from 'vue' 5 | 6 | export function useActionsButtons(args: Ref, actions: Ref[]>) { 7 | const buttons = computed(() => { 8 | return unref(actions) 9 | .filter((action) => { 10 | const enable = typeof action.enable === 'function' 11 | ? action.enable(...unref(args)) 12 | : (action.enable || true) 13 | return enable 14 | }) 15 | .map((action) => { 16 | const { disabled: _, helper, render, ...props } = action 17 | const [onClick, loading] = useAsyncCallback(() => helper?.(...unref(args))) 18 | const defaultRender = () => typeof render === 'function' 19 | ? () => render(...unref(args)) 20 | : () => render 21 | const slots: any = { default: defaultRender, ...props.slots } 22 | for (const key in slots) 23 | slots[key] = slots[key]?.(...unref(args)) 24 | return reactive({ 25 | ...props, 26 | loading, 27 | slots, 28 | onClick, 29 | }) as ActionsParsedProps 30 | }) 31 | }) 32 | 33 | return buttons 34 | } 35 | -------------------------------------------------------------------------------- /docs/.vitepress/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url' 2 | import VueJsx from '@vitejs/plugin-vue-jsx' 3 | import AutoImport from 'unplugin-auto-import/vite' 4 | import Components from 'unplugin-vue-components/vite' 5 | import { defineConfig } from 'vite' 6 | import Tsconfig from 'vite-tsconfig-paths' 7 | import { groupIconVitePlugin as GroupIconVitePlugin } from 'vitepress-plugin-group-icons' 8 | 9 | export default defineConfig({ 10 | plugins: [ 11 | Tsconfig({ projects: [fileURLToPath(new URL('../../tsconfig.json', import.meta.url))] }), 12 | GroupIconVitePlugin(), 13 | VueJsx(), 14 | AutoImport({ 15 | imports: [{ 16 | 'naive-ultra': [ 17 | 'defineForm', 18 | 'defineTable', 19 | 'defineActions', 20 | 'field', 21 | 'useColumnIndexes', 22 | 'useColumnLink', 23 | 'useColumns', 24 | ], 25 | }], 26 | }), 27 | Components({ 28 | resolvers: [{ 29 | type: 'component', 30 | resolve: (name: string) => { 31 | if (name.match(/^Nu.+/)) 32 | return { name, from: 'naive-ultra' } 33 | if (name.match(/^N.+/)) 34 | return { name, from: 'naive-ui' } 35 | }, 36 | }], 37 | }), 38 | ], 39 | ssr: { 40 | noExternal: ['naive-ui', 'vueuc', 'date-fns'], 41 | }, 42 | }) 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "version": "0.5.0", 4 | "private": true, 5 | "packageManager": "pnpm@10.12.4", 6 | "scripts": { 7 | "lint": "eslint --cache .", 8 | "build": "pnpm -r build", 9 | "docs": "pnpm -C docs run docs:dev", 10 | "docs:build": "pnpm -C docs run docs:build", 11 | "release": "bumpp -r && pnpm -r publish --access public", 12 | "test": "vitest", 13 | "typecheck": "tsc --noEmit", 14 | "prepare": "simple-git-hooks" 15 | }, 16 | "devDependencies": { 17 | "@antfu/eslint-config": "catalog:cli", 18 | "@antfu/ni": "catalog:cli", 19 | "@types/node": "catalog:types", 20 | "bumpp": "catalog:cli", 21 | "eslint": "catalog:cli", 22 | "lint-staged": "catalog:cli", 23 | "pnpm": "catalog:cli", 24 | "simple-git-hooks": "catalog:cli", 25 | "tsup": "catalog:bundler", 26 | "tsup-config": "workspace:*", 27 | "tsx": "catalog:cli", 28 | "typescript": "catalog:cli", 29 | "unbuild": "catalog:bundler", 30 | "vitest": "catalog:tesing" 31 | }, 32 | "pnpm": { 33 | "peerDependencyRules": { 34 | "ignoreMissing": [ 35 | "@algolia/client-search", 36 | "search-insights" 37 | ] 38 | } 39 | }, 40 | "simple-git-hooks": { 41 | "pre-commit": "pnpm lint-staged" 42 | }, 43 | "lint-staged": { 44 | "*": "eslint --fix" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/components/table/src/composables/useMetadata.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable ts/ban-ts-comment */ 2 | import type { DataTableInst } from 'naive-ui' 3 | import type { ColumnKey, FilterState } from 'naive-ui/es/data-table/src/interface' 4 | import type { DefineTableOptions } from '../define' 5 | import { ref } from 'vue' 6 | import { useOffsetPagination } from './useOffsetPagination' 7 | 8 | export function useMetadata(options: DefineTableOptions) { 9 | const tableInstRef = ref() 10 | const firstPage = ref(options.pagination?.page || 1) 11 | const pagination = useOffsetPagination({ ...options.pagination }) 12 | 13 | function clearFilters() { 14 | tableInstRef.value?.clearFilters() 15 | } 16 | function clearSorter() { 17 | tableInstRef.value?.clearSorter() 18 | } 19 | function filters(filters: FilterState | null) { 20 | tableInstRef.value?.filters(filters) 21 | } 22 | function scrollTo(...args: any[]) { 23 | // @ts-expect-error 24 | tableInstRef.value?.scrollTo(...args) 25 | } 26 | function sort(columnKey: ColumnKey, order: 'ascend' | 'descend' | false) { 27 | tableInstRef.value?.sort(columnKey, order) 28 | } 29 | 30 | return { 31 | tableInstRef, 32 | pagination, 33 | clearFilters, 34 | clearSorter, 35 | filters, 36 | firstPage, 37 | scrollTo, 38 | sort, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/components/provider/src/components/GlobalMessage.tsx: -------------------------------------------------------------------------------- 1 | import type { XMessageApiInjection } from 'naive-ui' 2 | import { messageProviderProps, NMessageProvider, useMessage } from 'naive-ui' 3 | 4 | import { defineComponent } from 'vue' 5 | import { createDeferred, packer } from '../utils' 6 | 7 | export const NuInstallMessage = defineComponent((props, { slots }) => { 8 | window.$message = useMessage() as XMessageApiInjection 9 | packer( 10 | window.$message, 11 | ['create', 'success', 'warning', 'error', 'info'], 12 | (source, key) => { 13 | window.$message[key] = function (content, options) { 14 | const deferred = createDeferred() 15 | const ins = source(content, { 16 | ...options, 17 | onAfterLeave() { 18 | options?.onAfterLeave?.() 19 | deferred.resolve() 20 | }, 21 | }) 22 | return Object.assign(deferred, ins) 23 | } 24 | }, 25 | ) 26 | return slots.default as any as any 27 | }) 28 | 29 | export const NuGlobalMessage = defineComponent({ 30 | name: 'NuGlobalMessage', 31 | props: messageProviderProps, 32 | setup(props, { slots }) { 33 | return () => ( 34 | 35 | 36 | {slots} 37 | 38 | 39 | ) 40 | }, 41 | }) 42 | -------------------------------------------------------------------------------- /docs/zh-CN/components/provider/demo/basic.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 54 | -------------------------------------------------------------------------------- /docs/components/provider/demo/basic.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 51 | -------------------------------------------------------------------------------- /packages/config/index.js: -------------------------------------------------------------------------------- 1 | const Globals = require('esbuild-plugin-globals') 2 | 3 | exports.defineBuildConfig = (options) => { 4 | const formats = options?.format || ['cjs', 'esm'] 5 | const iife = formats.findIndex(v => v === 'iife') 6 | formats.splice(iife, 1) 7 | const iifeMin = formats.findIndex(v => v === 'iifeMin') 8 | formats.splice(iifeMin, 1) 9 | 10 | const configs = [] 11 | 12 | options = { 13 | ...options, 14 | dts: { resolve: [/@naive-ultra/, /naive-ui/, /@vueuse/] }, 15 | external: ['vue', 'naive-ui', '@vueuse/core', 'vue', /@naive-ultra/], 16 | esbuildPlugins: [require('unplugin-vue-jsx/esbuild')()], 17 | } 18 | 19 | configs.push(options) 20 | 21 | if (iife) { 22 | configs.push({ 23 | ...options, 24 | format: 'iife', 25 | esbuildPlugins: [Globals(options?.globals), require('unplugin-vue-jsx/esbuild')()], 26 | outExtension({ format }) { 27 | return { js: `.${format}.js` } 28 | }, 29 | dts: false, 30 | }) 31 | } 32 | if (iifeMin) { 33 | configs.push({ 34 | ...options, 35 | format: 'iife', 36 | minify: true, 37 | esbuildPlugins: [Globals(options?.globals), require('unplugin-vue-jsx/esbuild')()], 38 | outExtension({ format }) { 39 | return { js: `.${format}.min.js` } 40 | }, 41 | dts: false, 42 | }) 43 | } 44 | 45 | return configs 46 | } 47 | -------------------------------------------------------------------------------- /packages/components/provider/src/components/GlobalProvider.tsx: -------------------------------------------------------------------------------- 1 | import type { DialogProviderProps, LoadingBarProviderProps, MessageProviderProps, NotificationProviderProps } from 'naive-ui' 2 | import type { PropType } from 'vue' 3 | import { defineComponent } from 'vue' 4 | import { NuGlobalDialog } from './GlobalDialog' 5 | import { NuGlobalLoadingBar } from './GlobalLoadingBar' 6 | import { NuGlobalMessage } from './GlobalMessage' 7 | import { NuGlobalNotification } from './GlobalNotification' 8 | import { NuInstallProvider } from './InstallProvider' 9 | 10 | export const NuGlobalProvider = defineComponent({ 11 | name: 'NuGlobalProvider', 12 | props: { 13 | message: Object as PropType, 14 | loaderBar: Object as PropType, 15 | dialog: Object as PropType, 16 | notification: Object as PropType, 17 | }, 18 | setup(p, { slots }) { 19 | return () => ( 20 | 28 | {slots.default?.()} 29 | 30 | ) 31 | }, 32 | }) 33 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/demo/field-context.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 54 | -------------------------------------------------------------------------------- /packages/components/form/src/types/data.ts: -------------------------------------------------------------------------------- 1 | import type { UnwrapRef } from 'vue' 2 | 3 | interface Field { value: T } 4 | 5 | export type Data> = { 6 | [K in keyof T as K]: UnwrapRef>; 7 | } & Record 8 | 9 | export type TransformData = Record> = OmitTransformData & Transform> 10 | 11 | type Transform> = Flatten<{ [K in keyof T]: ReturnType> }> 12 | 13 | type OmitTransformData = { 14 | [K in keyof T as FilterOmitTransformKeys]: UnwrapRef>; 15 | } 16 | 17 | type PickTransform = { 18 | [K in keyof T as FilterPickTransformKeys]: T[K]; 19 | } 20 | 21 | type FieldValue = T extends Field ? V : string 22 | type FilterOmitTransformKeys = T[K] extends FieldTransform ? never : K 23 | type FilterPickTransformKeys = T[K] extends FieldTransform ? K : never 24 | 25 | interface FieldTransform { 26 | transform?: (value: V, key: string) => R 27 | } 28 | 29 | type UnionToIntersection 30 | = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) 31 | ? I 32 | : never 33 | 34 | type Flatten = { 35 | [K in keyof UnionToIntersection]: 36 | UnionToIntersection[K] 37 | } 38 | -------------------------------------------------------------------------------- /docs/components/form/data-trans.md: -------------------------------------------------------------------------------- 1 | # Form Data Transform 2 | 3 | Data transformation is often used to preprocess form data and convert it into a format recognizable by the backend. Ultra Form has a built-in transform implementation for this, and you can get the transformed data via the `dataTrans` property of the form instance, with full TypeScript support. 4 | 5 | 6 | 7 | Note: You need to define fields using the field function for TypeScript to correctly infer types. 8 | 9 | ```ts 10 | import { field } from 'naive-ultra' 11 | 12 | const form = defineForm({ 13 | time: field({ 14 | type: 'date-picker', 15 | props: { type: 'datetimerange', clearable: true }, 16 | value: null as null | string[], 17 | transform(value) { 18 | return { 19 | startAt: value?.[0] ?? null, 20 | endAt: value?.[1] ?? null, 21 | } 22 | }, 23 | }), 24 | time2: { 25 | type: 'date-picker', 26 | props: { type: 'datetimerange', clearable: true }, 27 | value: null as null | string[], 28 | transform(value) { 29 | return { 30 | startAt2: value?.[0] ?? null, 31 | endAt2: value?.[1] ?? null, 32 | } 33 | }, 34 | } 35 | }) 36 | form.dataTrans.endAt // string | null 37 | form.dataTrans.startAt // string | null 38 | form.dataTrans.endAt2 // any 39 | form.dataTrans.startAt2 // any 40 | ``` 41 | -------------------------------------------------------------------------------- /packages/components/table/src/composables/useColumns.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeRef } from '@vueuse/core' 2 | import type { TableBaseColumn as _TableBaseColumn, TableColumnGroup as _TableColumnGroup, InternalRowData, TableExpandColumn, TableSelectionColumn } from 'naive-ui/es/data-table/src/interface' 3 | import type { Ref } from 'vue' 4 | import { final } from '@naive-ultra/utils' 5 | import { computed, unref } from 'vue' 6 | 7 | export interface TableBaseColumn extends _TableBaseColumn { 8 | disabled?: boolean | Ref | ((row: T, index: number) => boolean) 9 | text?: string | ((row: T, index: number) => string) 10 | } 11 | 12 | export type TableColumnGroup = _TableColumnGroup & { 13 | disabled?: TableBaseColumn['disabled'] 14 | text?: TableBaseColumn['text'] 15 | } 16 | 17 | export type TableColumn = TableColumnGroup | TableBaseColumn | TableSelectionColumn | TableExpandColumn 18 | export type TableColumns = Array> 19 | 20 | export function useColumns(columns: MaybeRef>) { 21 | const value = computed(() => { 22 | return unref(columns).filter(col => !final(col.disabled)).map(col => ({ minWidth: 120, ...col })) 23 | }) 24 | return value 25 | } 26 | 27 | export function useColumn(columns: MaybeRef>, key: string) { 28 | return unref(columns).find((v: any) => v.key === key)! 29 | } 30 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/Outline/components/VPDocOutlineItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 25 | 26 | 57 | -------------------------------------------------------------------------------- /docs/zh-CN/components/table/demo/request.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 59 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | // https://vitepress.dev/guide/custom-theme 2 | import type { EnhanceAppContext } from 'vitepress' 3 | import TwoslashFloating from '@shikijs/vitepress-twoslash/client' 4 | import { NuGlobalProvider } from 'naive-ultra' 5 | import NaiveUIContainer from 'vitepress-plugin-demo/client/naive-ui' 6 | import Theme from 'vitepress/theme' 7 | import { h } from 'vue' 8 | import Outline from './components/Outline/index.vue' 9 | 10 | import { codeeditor } from './index.codeeditor' 11 | import '@shikijs/vitepress-twoslash/style.css' 12 | import 'virtual:group-icons.css' 13 | import './style.css' 14 | import './naive-ui.css' 15 | // import 'uno.css' 16 | 17 | // @unocss-include 18 | export default { 19 | ...Theme, 20 | Layout: () => { 21 | return h(Theme.Layout, null, { 22 | // https://vitepress.dev/guide/extending-default-theme#layout-slots 23 | 'aside-top': () => h(Outline), 24 | }) 25 | }, 26 | async enhanceApp({ app }: EnhanceAppContext) { 27 | if (!import.meta.env.SSR) { 28 | const { default: NaiveUI } = await import('naive-ui') 29 | const { default: NaiveUltra } = await import('naive-ultra') 30 | app.use(NaiveUI) 31 | app.use(NaiveUltra) 32 | app.use(NaiveUIContainer, { 33 | github: 'https://github.com/hairyf/naive-ultra/tree/main', 34 | codeeditor, 35 | install: [ 36 | NuGlobalProvider, 37 | ], 38 | }) 39 | } 40 | 41 | app.use(TwoslashFloating) 42 | }, 43 | } 44 | -------------------------------------------------------------------------------- /docs/components/form/demo/field-context.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 54 | -------------------------------------------------------------------------------- /docs/components/table/demo/request.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 59 | -------------------------------------------------------------------------------- /packages/components/table/src/composables/useOffsetPagination.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | UseOffsetPaginationReturn as _OffsetPagination, 3 | UseOffsetPaginationOptions as _OffsetPaginationOptions, 4 | } from '@vueuse/core' 5 | import type { Ref, UnwrapNestedRefs } from 'vue' 6 | 7 | import { 8 | useOffsetPagination as _useOffsetPagination, 9 | } from '@vueuse/core' 10 | import { ref } from 'vue' 11 | 12 | export interface OffsetPaginationOptions extends Omit<_OffsetPaginationOptions, 'onPageChange' | 'onPageSizeChange' | 'onPageCountChange'> { 13 | } 14 | 15 | export interface OffsetPagination extends Omit<_OffsetPagination, 'currentPage' | 'currentPageSize'> { 16 | page: Ref 17 | pageSize: Ref 18 | total: Ref 19 | } 20 | export interface ServerPaginationResolve extends Partial> { 21 | page: number 22 | pageSize: number 23 | } 24 | 25 | export function useOffsetPagination(options: _OffsetPaginationOptions = {}): OffsetPagination { 26 | const total = ref(options.total || 0) 27 | const { 28 | currentPage, 29 | currentPageSize, 30 | pageCount, 31 | isFirstPage, 32 | isLastPage, 33 | prev, 34 | next, 35 | } = _useOffsetPagination({ 36 | total, 37 | ...options, 38 | }) as _OffsetPagination 39 | 40 | const returnValue = { 41 | page: currentPage, 42 | pageSize: currentPageSize, 43 | pageCount, 44 | isFirstPage, 45 | isLastPage, 46 | prev, 47 | next, 48 | total, 49 | } 50 | 51 | return returnValue as any 52 | } 53 | -------------------------------------------------------------------------------- /packages/components/dialog/src/components/index.tsx: -------------------------------------------------------------------------------- 1 | import type { ExtractPropTypes, PropType } from 'vue' 2 | import { debounce } from '@hairy/utils' 3 | import { useAsyncCallback } from '@naive-ultra/utils' 4 | import { useVModel } from '@vueuse/core' 5 | import { dialogProps, NDialog, NModal } from 'naive-ui' 6 | import { defineComponent } from 'vue' 7 | 8 | export const ultraDialogProps = { 9 | ...dialogProps, 10 | show: Boolean, 11 | width: String, 12 | onPositiveClick: Function as PropType<(e: MouseEvent) => void | Promise>, 13 | onNegativeClick: Function as PropType<(e: MouseEvent) => void | Promise>, 14 | disabled: Boolean, 15 | } 16 | 17 | export type UltraDialogProps = ExtractPropTypes 18 | 19 | export const NuDialog = defineComponent({ 20 | name: 'NuDialog', 21 | props: ultraDialogProps, 22 | setup(props, { slots }) { 23 | const visible = useVModel(props, 'show') 24 | 25 | const [onPositiveClick, loading] = useAsyncCallback(async (e: MouseEvent) => { 26 | await props.onPositiveClick?.(e) 27 | }) 28 | 29 | const onDebouncedPositiveClick = debounce(onPositiveClick, 1000, { 30 | leading: true, 31 | trailing: false, 32 | }) 33 | 34 | return ( 35 | 36 | 41 | {slots.default?.()} 42 | 43 | 44 | ) 45 | }, 46 | }) 47 | -------------------------------------------------------------------------------- /docs/components/actions/index.md: -------------------------------------------------------------------------------- 1 | # Ultra Actions 2 | 3 | Ultra Actions are commonly used in Table Columns and Form Toolbars, and can also be rendered independently using ``. 4 | 5 | It returns a Function Component, which renders components based on the passed parameters. 6 | 7 | 8 | 9 | ::: demo src="./demo/form.vue" title="Form Toolbars" 10 | 11 | Use with Form Toolbars to quickly create form toolbars. 12 | 13 | ::: 14 | 15 | 16 | 17 | ::: demo src="./demo/custom.vue" title="Custom Rendering" 18 | 19 | By default, Actions only render button controls. If you need to use other components, you can use the `custom` property. 20 | 21 | ::: 22 | 23 | ## Item Props 24 | 25 | | Name | Type | Default | Description | 26 | | ------ | -------------------------------------- | ------- | -------------------------------------------- | 27 | | render | `string | (...args) => string \| VNodeChild` | `-` | Render the content inside the button control | 28 | | helper | `(...args) => void \| Promise` | `-` | Handles click events, if returns a `promise` will automatically enable `loading` | 29 | | enable | `(...args) => void \| Promise` | `-` | Whether to enable the control, returns `false` will not render the control | 30 | | custom | `(...args) => VNodeChild` | `-` | Custom rendering | 31 | 32 | > For more parameters, please refer to [n-button](https://www.naiveui.com/en-US/light/components/button). 33 | -------------------------------------------------------------------------------- /docs/zh-CN/components/table/actions.md: -------------------------------------------------------------------------------- 1 | # Ultra Actions(表格集成) 2 | 3 | Ultra Actions 可与 UltraTable 搭配使用,便于在表格列中渲染批量操作按钮,简化表格操作列实现。 4 | 5 | ::: demo src="../actions/demo/table.vue" title="表格控件" 6 | 在表格列配置中直接使用 `defineActions` 返回的组件函数,即可实现批量操作按钮。 7 | ::: 8 | 9 | ## 基本用法 10 | 11 | ```ts 12 | import { defineActions, defineTable, useColumns } from 'naive-ultra' 13 | 14 | const actions = defineActions([ 15 | { 16 | text: true, 17 | render: () => 'Deposit', 18 | helper: row => alert(`Deposit ${row.id}`), 19 | }, 20 | { 21 | text: true, 22 | render: () => 'Withdraw', 23 | helper: row => alert(`Withdraw ${row.id}`), 24 | }, 25 | ]) 26 | 27 | const columns = useColumns([ 28 | { key: 'id', title: 'ID' }, 29 | { key: 'name', title: 'Name' }, 30 | { key: 'action', title: 'Action', render: actions }, 31 | ]) 32 | ``` 33 | 34 | ## Action 属性 35 | 36 | | 名称 | 类型 | 说明 | 37 | | ------ | -------------------------------------------------- | ---------------------------- | 38 | | render | `string \| (...args) => string \| VNodeChild` | 渲染按钮控件项目内容 | 39 | | helper | `(...args) => void \| Promise` | 处理点击事件,支持异步 loading | 40 | | enable | `boolean \| (...args) => boolean` | 是否启用控件,返回 false 不渲染 | 41 | | custom | `(...args) => VNodeChild` | 自定义渲染 | 42 | 43 | > 更多参数请参考 [n-button](https://www.naiveui.com/zh-CN/light/components/button)。 44 | 45 | ## 泛型与类型 46 | 47 | defineActions 支持泛型参数,自动推导每个操作项的参数类型。例如 `defineActions<[Row, number]>()` 可获得当前行和索引。 48 | 49 | Ultra Actions 可用于表格、工具栏等场景。 50 | -------------------------------------------------------------------------------- /docs/components/table/hooks.md: -------------------------------------------------------------------------------- 1 | # Table Hooks 2 | 3 | ::: demo src="./demo/hooks.vue" title="Common hooks usage" 4 | Table Hooks provide a set of utility functions for simplifying table column configuration, index columns, link columns, and other common scenarios, making UltraTable's column configuration more flexible and reusable. 5 | ::: 6 | 7 | ## useColumns 8 | 9 | Used for batch processing and generating table column configurations, supports dynamic filtering, default min width, etc. 10 | 11 | ```ts 12 | import { useColumns } from 'naive-ultra' 13 | 14 | const columns = useColumns([ 15 | { key: 'name', title: 'Name' }, 16 | { key: 'age', title: 'Age' }, 17 | ]) 18 | ``` 19 | 20 | ## useColumnIndexes 21 | 22 | Quickly generate static index columns (auto serial number), commonly used for the first column of tables. 23 | 24 | ```ts 25 | import { useColumnIndexes } from 'naive-ultra' 26 | 27 | const columns = [ 28 | useColumnIndexes(table.pagination), 29 | { key: 'name', title: 'Name' }, 30 | ] 31 | ``` 32 | 33 | ## useColumnLink 34 | 35 | Quickly generate link columns, supports custom render content and jump addresses. 36 | 37 | ```ts 38 | import { useColumnLink } from 'naive-ultra' 39 | 40 | const columns = [ 41 | useColumnLink('url', { title: 'Official Site', render: row => row.name }), 42 | ] 43 | ``` 44 | 45 | ## Other Common Hooks 46 | 47 | - `useTableMinWidth(columns)`: Automatically calculates the minimum table width, suitable for responsive layouts. 48 | - `useOffsetPagination(options)`: Pagination state management, used with UltraTable. 49 | 50 | > You can flexibly combine these hooks to quickly implement complex table scenarios. 51 | -------------------------------------------------------------------------------- /docs/zh-CN/components/table/index.md: -------------------------------------------------------------------------------- 1 | # Ultra Table 2 | 3 | nu-table 用于解决项目中需要写很多 table 的样板代码的问题,所以在其中做了封装了很多常用的逻辑。这些封装可以简单的分类为预设行为与预设逻辑。 4 | 5 | 当你的表格需要与服务端进行交互或者需要多种单元格样式时,UltraTable 是不二选择,如果你只是想渲染一个表格,更建议你使用 [data-table](https://www.naiveui.com/zh-CN/os-theme/components/data-table) 或者 [table](https://www.naiveui.com/zh-CN/os-theme/components/table)。 6 | 7 | 8 | 9 | ::: demo src="./demo/request.vue" title="外部控制" 10 | 11 | 使用 `defineTable` 在外部控制请求。 12 | 13 | ::: 14 | 15 | ::: demo src="./demo/form.vue" title="表单组合" 16 | 17 | 你还可以和 `defineForm` 配合使用组成一个完整的搜索页: 18 | 19 | ::: 20 | 21 | ::: demo src="./demo/pagination.vue" title="表格分页" 22 | 23 | 分页默认开启,如果你不需要分页,可以通过 `pagination` 参数设置为 `false` 关闭,你也可以通过设置 `pagination` 覆盖 `pagination` 参数。 24 | 25 | ::: 26 | 27 | ::: demo src="./demo/watcher.vue" title="监听数据" 28 | 29 | 当表格数据变化时,你可能需要做一些额外的操作,这时你可以通过 `watch` 参数来监听数据变化,它将会自动重置分页并请求。 30 | 31 | ::: 32 | 33 | ## Props 34 | 35 | | 名称 | 类型 | 默认值 | 说明 | 36 | | --- | --- | --- | --- | 37 | | is | `UltraTableInstance` | `-` | 组件的实例 | 38 | | pagination | `boolean \| PaginationProps` | `true` | 分页配置 | 39 | 40 | > 更多参数参考 [data-table](https://www.naiveui.com/zh-CN/os-theme/components/data-table)。 41 | 42 | ## Methods(table) 43 | 44 | | 名称 | 类型 | 说明 | 45 | | --- | --- | --- | 46 | | pagination | `OffsetPagination` | 表单分页配置 | 47 | | next | `() => void` | 跳转下一页,并请求相关页面 | 48 | | prev | `() => void` | 跳转上一页,并请求相关页面 | 49 | | search | `(pagination?: ServerPaginationResolve) => Promise` | 根据分页信息重新请求内容 | 50 | | reset | `() => Promise` | 重置页码并重新请求 | 51 | | request | `(pagination: ServerPaginationResolve) => DataResolved \| Promise>` | 源请求函数 | 52 | | requestAll | `() => Promise` | 根据页码相关信息请求所有数据,一般可用于组合 `.csv` 文件 | 53 | -------------------------------------------------------------------------------- /docs/zh-CN/guide/faq.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 3 | ## defineForm 传递对象而不是数组 4 | 5 | 你可以会感到疑惑,defineForm 通过对象的形式声明组件的顺序,而不是数组,在我们实际使用中,发现数组存在一些问题,且经过我们长期的实践,我们认为采用对象是完全可行的。 6 | 7 | **类型推断在数组上无法进行:** 8 | 9 | ```ts 10 | const form = defineForm([ 11 | { 12 | type: 'input', 13 | key: 'name' as const, 14 | }, 15 | { 16 | type: 'input', 17 | key: 'age' as const, 18 | }, 19 | ]) 20 | 21 | // 无法正确推断出 form 的类型 22 | form.data // { name: string } | { age: string } 23 | ``` 24 | 25 | 而通过对象的形式却能轻松的推断出类型: 26 | 27 | ```ts 28 | const form = defineForm({ 29 | name: { 30 | type: 'input', 31 | }, 32 | age: { 33 | type: 'input', 34 | }, 35 | }) 36 | 37 | form.data // { name: string; age: string } 38 | ``` 39 | 40 | **组合表单的复用形式不够灵活:** 41 | 42 | ```ts 43 | const nameField = { 44 | type: 'input', 45 | key: 'name' as const, 46 | // ...其他配置 47 | } 48 | 49 | const form = defineForm([ 50 | nameField, 51 | { ...nameField, key: 'name2' as const } 52 | ]) 53 | ``` 54 | 55 | 对象的复用形式: 56 | 57 | ```ts 58 | const nameField = { 59 | type: 'input', 60 | // ...其他配置 61 | } 62 | const form = defineForm({ 63 | name: nameField, 64 | name2: nameField, 65 | }) 66 | ``` 67 | 68 | 尽管通过数组可以很轻松的操作索引控制表单顺序和显示,而对象也可以通过组合的方式实现动态的表单显示: 69 | 70 | ```ts 71 | const formFields = {/* ... */} 72 | const formFields2 = {/* ... */} 73 | 74 | const form = defineForm(() => { 75 | return { ...formFields, ...formFields2 } 76 | }) 77 | ``` 78 | 79 | ## defineTable 分页的配置与控制分离 80 | 81 | 在 `nu-table` 中,组件的 `pagination` 仅用于配置分页的显示参数,而 `page` 与 `pageSize` 由 `defineTable` 全程接管,你可以通过 `defineTable` 参数配置这两个参数的初始值。 82 | 83 | ```ts 84 | const table = defineTable({ 85 | // ...其他配置 86 | page: 2, 87 | pageSize: 15, 88 | }) 89 | 90 | // 你可以通过 table.pagination.page、table.pagination.pageSize 获取当前页码和当前页大小 91 | ``` 92 | -------------------------------------------------------------------------------- /packages/components/radio/src/components/index.tsx: -------------------------------------------------------------------------------- 1 | import type { RadioProps, SpaceProps } from 'naive-ui' 2 | import type { ExtractPropTypes, PropType, VNodeChild } from 'vue' 3 | import type { JSX } from 'vue/jsx-runtime' 4 | import { reactiveOmit } from '@vueuse/core' 5 | import { NRadio, NRadioButton, NRadioGroup, NSpace, radioGroupProps } from 'naive-ui' 6 | import { computed, defineComponent } from 'vue' 7 | 8 | export const ultraRadioGroupProps = { 9 | ...radioGroupProps, 10 | type: { 11 | type: String as PropType<'button' | 'default'>, 12 | default: 'default', 13 | }, 14 | options: Array as PropType, 15 | space: Object as PropType, 16 | } 17 | 18 | export type UltraRadioGroupProps = ExtractPropTypes 19 | 20 | export interface RadioMixedOption extends RadioProps { 21 | slots?: Record JSX.Element | undefined> 22 | } 23 | 24 | export const NuRadioGroup = defineComponent({ 25 | name: 'NuRadioGroup', 26 | props: ultraRadioGroupProps, 27 | setup(props) { 28 | const options = computed(() => props.options || []) 29 | const radioGroupProps = reactiveOmit(props, ['space', 'options', 'type']) 30 | function renderRadio({ slots, ...rest }: RadioMixedOption, i: number) { 31 | return props.type === 'default' 32 | ? {slots} 33 | : {slots} 34 | } 35 | function renderSpace(content: VNodeChild) { 36 | return props.type === 'default' 37 | ? {content} 38 | : content 39 | } 40 | return () => ( 41 | 42 | {renderSpace(options.value.map(renderRadio))} 43 | 44 | ) 45 | }, 46 | }) 47 | 48 | export default NuRadioGroup 49 | -------------------------------------------------------------------------------- /packages/components/provider/src/global.naive.ts: -------------------------------------------------------------------------------- 1 | import type { DialogOptions, MessageOptions, MessageType } from 'naive-ui' 2 | import type { VNodeChild } from 'vue' 3 | 4 | declare module 'naive-ui' { 5 | type XDialogReactive = { 6 | readonly key: string 7 | readonly destroy: () => void 8 | } & DialogOptions & Promise 9 | 10 | export interface XDialogApiInjection { 11 | destroyAll: () => void 12 | create: (options: DialogOptions) => XDialogReactive 13 | success: (options: DialogOptions) => XDialogReactive 14 | warning: (options: DialogOptions) => XDialogReactive 15 | error: (options: DialogOptions) => XDialogReactive 16 | info: (options: DialogOptions) => XDialogReactive 17 | } 18 | 19 | export type XDialogProviderInst = XDialogApiInjection 20 | 21 | export type ContentType = string | (() => VNodeChild) 22 | 23 | export interface XMessageApiInjection { 24 | create: (content: ContentType, options?: MessageOptions) => XMessageReactive 25 | info: (content: ContentType, options?: MessageOptions) => XMessageReactive 26 | success: (content: ContentType, options?: MessageOptions) => XMessageReactive 27 | warning: (content: ContentType, options?: MessageOptions) => XMessageReactive 28 | error: (content: ContentType, options?: MessageOptions) => XMessageReactive 29 | loading: (content: ContentType, options?: MessageOptions) => XMessageReactive 30 | destroyAll: () => void 31 | } 32 | 33 | export type XMessageReactive = { 34 | content?: ContentType 35 | duration?: number 36 | closable?: boolean 37 | keepAliveOnHover?: boolean 38 | type: MessageType 39 | icon?: () => VNodeChild 40 | showIcon?: boolean 41 | onClose?: () => void 42 | destroy: () => void 43 | } & Promise 44 | 45 | export type XMessageProviderInst = XMessageApiInjection 46 | 47 | } 48 | 49 | export { } 50 | -------------------------------------------------------------------------------- /packages/components/actions/src/define/index.tsx: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue' 2 | import type { ActionsInstance, ActionsParsedProps, ActionsProps } from '../types' 3 | import { c, NButton } from 'naive-ui' 4 | import { defineComponent, h, toRefs } from 'vue' 5 | import { useActionsButtons } from '../composables' 6 | 7 | export function defineActions(actions: ActionsProps[]): ActionsInstance { 8 | return (...args: T) => h(Component, { args, actions }) 9 | } 10 | 11 | const Component = defineComponent({ 12 | props: { 13 | args: { 14 | type: Array as PropType, 15 | default: () => [], 16 | }, 17 | actions: { 18 | type: Array as PropType, 19 | default: () => [], 20 | }, 21 | }, 22 | setup(props) { 23 | const { args, actions } = toRefs(props) 24 | const buttons = useActionsButtons(args, actions) 25 | 26 | style.mount() 27 | 28 | function renderCustom(options: ActionsParsedProps) { 29 | return
{options.custom?.(...props.args)}
30 | } 31 | function renderButton(options: ActionsParsedProps) { 32 | const { slots, ...props } = options 33 | return ( 34 | 39 | {{ ...slots }} 40 | 41 | ) 42 | } 43 | 44 | return () => ( 45 |
46 | {buttons.value.map((options) => { 47 | return options.custom 48 | ? renderCustom(options) 49 | : renderButton(options) 50 | })} 51 |
52 | ) 53 | }, 54 | }) 55 | 56 | const style = c('.n-actions', { 57 | display: 'flex', 58 | alignItems: 'center', 59 | }, [ 60 | c('.n-actions__item:not(:first-child)', { 61 | marginLeft: '12px', 62 | }), 63 | ]) 64 | -------------------------------------------------------------------------------- /docs/zh-CN/components/provider/index.md: -------------------------------------------------------------------------------- 1 | # Ultra Provider 2 | 3 | `naive-ui` 所提供的所有全局组件,并不能在全局中使用,因为他们需要挂载到某个组件上,我们提供了 `globals` 组件,用于在全局中使用这些反馈组件,它将自动挂载 Typescript 类型。 4 | 5 | ```html 6 | 7 | 8 | 9 | 10 | ``` 11 | 12 | 现在你可以在全局中通过使用 `$message`、`$dialog`、`$loadingBar`、`$notification` 来使用这些方法。 13 | 14 | 15 | 16 | ```ts 17 | function onClick() { 18 | $message.info('人生处处是美梦') 19 | } 20 | ``` 21 | 22 | 另外,我们对 `$message`、`$dialog` 的返回结果进行了优化,现在你可以直接使用 `await` 来获取用户的选择结果: 23 | 24 | ```ts 25 | const ins = $dialog.warning({ 26 | content: glbI18n.t('merchant.store.deleteText'), 27 | // 如果用户点击了确认,则会执行 postApiStoreDelete 方法,此时 button 处于 loading 状态 28 | onPositiveClick: () => postApiStoreDelete({ storeId: id }), 29 | }) 30 | 31 | // 销毁方法 32 | ins.destroy 33 | 34 | // promise 方法 35 | ins.then 36 | ins.catch 37 | 38 | // 使用 await 等待结果 39 | await ins 40 | // 用户点击了确认,并等待请求结束 41 | ``` 42 | 43 | `$message` 则是动画结束后,promise 变为成功,这在一些需要等待动画结束后再执行的场景中非常有用: 44 | 45 | ```ts 46 | await $message.error('错误') 47 | // 动画执行结束,组件被销毁 48 | ``` 49 | 50 | ## 按需使用 51 | 52 | 如果你只需要使用 `$message`,你可以通过挂载单独的组件来实现: 53 | 54 | ```html 55 | 56 | 59 | 60 | 65 | ``` 66 | 67 | 你可以使用 `nu-installs-provider` 简化挂载操作: 68 | 69 | ```html 70 | 71 | 74 | 84 | ``` 85 | -------------------------------------------------------------------------------- /packages/components/checkbox/src/components/index.tsx: -------------------------------------------------------------------------------- 1 | import type { CheckboxInst, CheckboxProps, SpaceProps } from 'naive-ui' 2 | import type { ExtractPropTypes, PropType, Ref } from 'vue' 3 | 4 | import type { JSX } from 'vue/jsx-runtime' 5 | import { reactiveOmit } from '@vueuse/core' 6 | import { checkboxGroupProps, NCheckbox, NCheckboxGroup, NSpace } from 'naive-ui' 7 | import { computed, defineComponent, isRef } from 'vue' 8 | 9 | export const ultraCheckboxGroupProps = { 10 | ...checkboxGroupProps, 11 | options: Array as PropType, 12 | space: Object as PropType, 13 | } 14 | 15 | export type UltraCheckboxGroupProps = ExtractPropTypes 16 | 17 | export interface CheckboxMixedOption extends CheckboxProps { 18 | slots?: Record JSX.Element | undefined> 19 | ref?: CheckboxInst | undefined | Ref 20 | } 21 | 22 | export const NuCheckboxGroup = defineComponent({ 23 | name: 'NuCheckboxGroup', 24 | props: ultraCheckboxGroupProps, 25 | setup(props) { 26 | const options = computed(() => props.options || []) 27 | const groupProps = reactiveOmit(props, ['space', 'options']) 28 | function renderCheckbox(opts: CheckboxMixedOption, i: number) { 29 | return ( 30 | isRef(opts.ref) 35 | ? opts.ref.value = inst 36 | : opts.ref = inst 37 | } 38 | > 39 | {opts.label || opts.slots?.default?.()} 40 | 41 | ) 42 | } 43 | return () => ( 44 | 45 | 46 | {options.value.map(renderCheckbox)} 47 | 48 | 49 | ) 50 | }, 51 | }) 52 | 53 | export default NuCheckboxGroup 54 | -------------------------------------------------------------------------------- /packages/components/main/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "naive-ultra", 3 | "type": "module", 4 | "version": "0.5.0", 5 | "exports": { 6 | ".": "./src/index.ts", 7 | "./resolver": "./src/resolver.ts", 8 | "./imports": "./src/imports.ts" 9 | }, 10 | "main": "./src/index.ts", 11 | "publishConfig": { 12 | "exports": { 13 | ".": { 14 | "types": "./dist/index.d.ts", 15 | "require": "./dist/index.cjs", 16 | "import": "./dist/index.js" 17 | }, 18 | "./resolver": { 19 | "types": "./dist/resolver.d.ts", 20 | "require": "./dist/resolver.cjs", 21 | "import": "./dist/resolver.js" 22 | }, 23 | "./imports": { 24 | "types": "./dist/imports.d.ts", 25 | "require": "./dist/imports.cjs", 26 | "import": "./dist/imports.js" 27 | }, 28 | "./*": "./*" 29 | }, 30 | "main": "./dist/index.cjs", 31 | "module": "./dist/index.js", 32 | "unpkg": "./dist/index.iife.min.js", 33 | "jsdelivr": "./dist/index.iife.min.js", 34 | "types": "./dist/index.d.ts" 35 | }, 36 | "typesVersions": { 37 | "*": { 38 | "*": [ 39 | "./dist/*", 40 | "./*" 41 | ] 42 | } 43 | }, 44 | "files": [ 45 | "dist" 46 | ], 47 | "engines": { 48 | "node": ">=14" 49 | }, 50 | "scripts": { 51 | "build": "tsup", 52 | "prepublish": "npm run build" 53 | }, 54 | "dependencies": { 55 | "@naive-ultra/actions": "workspace:*", 56 | "@naive-ultra/checkbox": "workspace:*", 57 | "@naive-ultra/form": "workspace:*", 58 | "@naive-ultra/provider": "workspace:*", 59 | "@naive-ultra/radio": "workspace:*", 60 | "@naive-ultra/table": "workspace:*", 61 | "naive-ui": "catalog:share", 62 | "vue": "catalog:share" 63 | }, 64 | "devDependencies": { 65 | "unplugin-auto-import": "catalog:share", 66 | "unplugin-vue-components": "catalog:share" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/components/radio/README.md: -------------------------------------------------------------------------------- 1 | # Naive Ultra 2 | 3 | See our website [naive-ultra](/https://naiveui-naive-ultra.vercel.app/) for more information. 4 | 5 | ## Install 6 | 7 | Using pnpm: 8 | 9 | ```bash 10 | pnpm install --save naive-ultra 11 | ``` 12 | 13 | or using yarn: 14 | 15 | ```bash 16 | yarn add naive-ultra 17 | ``` 18 | 19 | ## Usage 20 | 21 | ### Globals 22 | 23 | ```js 24 | import NaiveUltra from 'naive-ultra' 25 | import { createApp } from 'vue' 26 | import App from './App.vue' 27 | 28 | const app = createApp(App) 29 | 30 | app.use(NaiveUltra) 31 | ``` 32 | 33 | ## On-Demand Import (Recommended) 34 | 35 | Install the `unplugin-vue-components` and `unplugin-auto-import` plugins, which will automatically import all components and APIs from `naive-ultra`. 36 | 37 | ```sh 38 | npm install -D unplugin-vue-components unplugin-auto-import 39 | ``` 40 | 41 | ### Vite 42 | 43 | ```ts 44 | import NaiveUltraImports from 'naive-ultra/imports' 45 | import NaiveUltraResolver from 'naive-ultra/resolver' 46 | import AutoImport from 'unplugin-auto-import/vite' 47 | import Components from 'unplugin-vue-components/vite' 48 | // vite.config.ts 49 | import { defineConfig } from 'vite' 50 | 51 | export default defineConfig({ 52 | // ... 53 | plugins: [ 54 | // ... 55 | AutoImport({ 56 | imports: [NaiveUltraImports()], 57 | }), 58 | Components({ 59 | resolvers: [NaiveUltraResolver()], 60 | }), 61 | ], 62 | }) 63 | ``` 64 | 65 | ### Webpack 66 | 67 | ```js 68 | const NaiveUltraImports = require('naive-ultra/imports') 69 | const NaiveUltraResolver = require('naive-ultra/resolver') 70 | // webpack.config.js 71 | const AutoImport = require('unplugin-auto-import/webpack') 72 | const Components = require('unplugin-vue-components/webpack') 73 | 74 | module.exports = { 75 | // ... 76 | plugins: [ 77 | // ... 78 | AutoImport({ 79 | imports: [NaiveUltraImports()], 80 | }), 81 | Components({ 82 | resolvers: [NaiveUltraResolver()], 83 | }), 84 | ], 85 | } 86 | ``` 87 | -------------------------------------------------------------------------------- /packages/components/table/README.md: -------------------------------------------------------------------------------- 1 | # Naive Ultra 2 | 3 | See our website [naive-ultra](/https://naiveui-naive-ultra.vercel.app/) for more information. 4 | 5 | ## Install 6 | 7 | Using pnpm: 8 | 9 | ```bash 10 | pnpm install --save naive-ultra 11 | ``` 12 | 13 | or using yarn: 14 | 15 | ```bash 16 | yarn add naive-ultra 17 | ``` 18 | 19 | ## Usage 20 | 21 | ### Globals 22 | 23 | ```js 24 | import NaiveUltra from 'naive-ultra' 25 | import { createApp } from 'vue' 26 | import App from './App.vue' 27 | 28 | const app = createApp(App) 29 | 30 | app.use(NaiveUltra) 31 | ``` 32 | 33 | ## On-Demand Import (Recommended) 34 | 35 | Install the `unplugin-vue-components` and `unplugin-auto-import` plugins, which will automatically import all components and APIs from `naive-ultra`. 36 | 37 | ```sh 38 | npm install -D unplugin-vue-components unplugin-auto-import 39 | ``` 40 | 41 | ### Vite 42 | 43 | ```ts 44 | import NaiveUltraImports from 'naive-ultra/imports' 45 | import NaiveUltraResolver from 'naive-ultra/resolver' 46 | import AutoImport from 'unplugin-auto-import/vite' 47 | import Components from 'unplugin-vue-components/vite' 48 | // vite.config.ts 49 | import { defineConfig } from 'vite' 50 | 51 | export default defineConfig({ 52 | // ... 53 | plugins: [ 54 | // ... 55 | AutoImport({ 56 | imports: [NaiveUltraImports()], 57 | }), 58 | Components({ 59 | resolvers: [NaiveUltraResolver()], 60 | }), 61 | ], 62 | }) 63 | ``` 64 | 65 | ### Webpack 66 | 67 | ```js 68 | const NaiveUltraImports = require('naive-ultra/imports') 69 | const NaiveUltraResolver = require('naive-ultra/resolver') 70 | // webpack.config.js 71 | const AutoImport = require('unplugin-auto-import/webpack') 72 | const Components = require('unplugin-vue-components/webpack') 73 | 74 | module.exports = { 75 | // ... 76 | plugins: [ 77 | // ... 78 | AutoImport({ 79 | imports: [NaiveUltraImports()], 80 | }), 81 | Components({ 82 | resolvers: [NaiveUltraResolver()], 83 | }), 84 | ], 85 | } 86 | ``` 87 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/field-item.md: -------------------------------------------------------------------------------- 1 | # Form Field Item 2 | 3 | Form Field 支持 Naive UI 大部分表单组件,包括输入框、选择器、日期选择器等,你可以通过设置 `type: ` 来指定使用的组件类型。 4 | 5 | ::: demo src="./demo/form.vue" title="基础用法" 6 | ::: 7 | 8 | ::: demo src="./demo/form-item-props.vue" title="Form Item Props" 9 | `formItemProps` 用于设置当前表单项的属性,支持 [Naive UI n-form-item](https://www.naiveui.com/zh-CN/os-theme/components/form#FormItem-Props) 和 [n-grid-item](https://www.naiveui.com/zh-CN/os-theme/components/grid#GridItem-Props) 的所有属性。你可以通过它自定义 label、校验、布局、样式等。 10 | 11 | - UltraForm 默认启用 grid 布局,所有表单项均包裹在 `NFormItemGi`(Grid Item)中,`formItemProps` 同时支持 n-form-item 和 n-grid-item 的属性。 12 | - 你可以通过 `span` 属性设置当前表单项的栅格宽度,或通过 `formItemProps` 进一步定制。 13 | ::: 14 | 15 | ::: demo src="./demo/props.vue" title="Component Props" 16 | `props` 用于给底层 Naive UI 组件的属性。例如: 17 | ::: 18 | 19 | ::: demo src="./demo/slots.vue" title="自定义插槽" 20 | `slots` 用于自定义底层组件的插槽内容,格式为对象。例如: 21 | ::: 22 | 23 | ## Placeholder 24 | 25 | `placeholder` 用于设置表单项的占位符文本,支持字符串和函数两种形式。 26 | 27 | ```ts 28 | { 29 | placeholder: () => yourI18n.t('...') 30 | } 31 | ``` 32 | 33 | ## Only Render 34 | 35 | 定义 Form 时,通过引用 ONLY_RENDER 对该字段标识为仅渲染,不进行收集数据。 36 | 37 | ```ts 38 | import { defineForm, field, ONLY_RENDER } from 'naive-ultra' 39 | 40 | const form = defineForm({ 41 | [ONLY_RENDER()]: field({ 42 | type: 'button', 43 | props: { 44 | onClick() { 45 | form.validate() 46 | } 47 | }, 48 | }) 49 | }) 50 | ``` 51 | 52 | ## 其他常用属性 53 | 54 | - `label`:表单项标签 55 | - `span`:当前表单项的栅格宽度(仅 grid 布局下生效) 56 | - `rules`:校验规则,支持 Naive UI 的校验格式 57 | - `placeholder`:占位符 58 | - `renderItem`:自定义渲染函数,适合复杂场景 59 | 60 | ## 参考 61 | 62 | - [Naive UI n-form Props](https://www.naiveui.com/zh-CN/os-theme/components/form#Form-Props) 63 | - [Naive UI n-form-item Props](https://www.naiveui.com/zh-CN/os-theme/components/form#FormItem-Props) 64 | - [Naive UI n-grid Props](https://www.naiveui.com/zh-CN/os-theme/components/grid#Grid-Props) 65 | - [Naive UI n-grid-item Props](https://www.naiveui.com/zh-CN/os-theme/components/grid#GridItem-Props) 66 | -------------------------------------------------------------------------------- /packages/components/provider/src/components/GlobalDialog.tsx: -------------------------------------------------------------------------------- 1 | import type { XDialogProviderInst } from 'naive-ui' 2 | import { dialogProviderProps, NDialogProvider, useDialog } from 'naive-ui' 3 | import { defineComponent } from 'vue' 4 | import { createDeferred, packer } from '../utils' 5 | 6 | export const NuInstallDialog = defineComponent((_, { slots }) => { 7 | window.$dialog = useDialog() as XDialogProviderInst 8 | packer( 9 | window.$dialog, 10 | ['create', 'success', 'warning', 'error', 'info'], 11 | (source, key) => { 12 | window.$dialog[key] = function (options) { 13 | const deferred = createDeferred() 14 | const inst = source({ 15 | ...options, 16 | onPositiveClick(e) { 17 | const result = options.onPositiveClick?.(e) 18 | if (result instanceof Promise) { 19 | inst.loading = true 20 | result 21 | .finally(() => inst.loading = false) 22 | .then(deferred.resolve) 23 | .catch(deferred.reject) 24 | } 25 | else { 26 | deferred.resolve(result as any) 27 | } 28 | return result 29 | }, 30 | onClose() { 31 | deferred.reject() 32 | options.onClose?.() 33 | }, 34 | onNegativeClick(e) { 35 | deferred.reject() 36 | options.onNegativeClick?.(e) 37 | }, 38 | onMaskClick(e) { 39 | deferred.reject() 40 | options.onMaskClick?.(e) 41 | }, 42 | }) 43 | return Object.assign(deferred, inst) 44 | } 45 | }, 46 | ) 47 | return slots.default as any 48 | }) 49 | 50 | export const NuGlobalDialog = defineComponent({ 51 | name: 'NuGlobalDialog', 52 | props: dialogProviderProps, 53 | setup(props, { slots }) { 54 | return () => ( 55 | 56 | 57 | {slots} 58 | 59 | 60 | ) 61 | }, 62 | }) 63 | -------------------------------------------------------------------------------- /docs/public/logo.svg: -------------------------------------------------------------------------------- 1 | Naive UI - LOGO -------------------------------------------------------------------------------- /docs/components/table/actions.md: -------------------------------------------------------------------------------- 1 | # Ultra Actions (Table Integration) 2 | 3 | Ultra Actions can be used with UltraTable to render batch operation buttons in table columns, simplifying the implementation of table operation columns. 4 | 5 | ::: demo src="../actions/demo/table.vue" title="Table Controls" 6 | In the table column configuration, you can directly use the component function returned by `defineActions` to implement batch operation buttons. 7 | ::: 8 | 9 | ## Basic Usage 10 | 11 | ```ts 12 | import { defineActions, defineTable, useColumns } from 'naive-ultra' 13 | 14 | const actions = defineActions([ 15 | { 16 | text: true, 17 | render: () => 'Deposit', 18 | helper: row => alert(`Deposit ${row.id}`), 19 | }, 20 | { 21 | text: true, 22 | render: () => 'Withdraw', 23 | helper: row => alert(`Withdraw ${row.id}`), 24 | }, 25 | ]) 26 | 27 | const columns = useColumns([ 28 | { key: 'id', title: 'ID' }, 29 | { key: 'name', title: 'Name' }, 30 | { key: 'action', title: 'Action', render: actions }, 31 | ]) 32 | ``` 33 | 34 | ## Action Properties 35 | 36 | | Name | Type | Description | 37 | | ------ | -------------------------------------- | -------------------------------------------- | 38 | | render | `string | (...args) => string \| VNodeChild` | Render the content inside the button control | 39 | | helper | `(...args) => void \| Promise` | Handles click events, supports async loading | 40 | | enable | `boolean \| (...args) => boolean` | Whether to enable the control, returns false will not render | 41 | | custom | `(...args) => VNodeChild` | Custom rendering | 42 | 43 | > For more parameters, please refer to [n-button](https://www.naiveui.com/en-US/light/components/button). 44 | 45 | ## Generics and Types 46 | 47 | defineActions supports generic parameters, automatically inferring the parameter types for each action. For example, `defineActions<[Row, number]>()` can get the current row and index. 48 | 49 | Ultra Actions can be used in tables, toolbars, and other scenarios. 50 | -------------------------------------------------------------------------------- /packages/components/form/src/composables/useMetadata.ts: -------------------------------------------------------------------------------- 1 | import type { FormInst, FormItemInst } from 'naive-ui' 2 | import type { ShouldRuleBeApplied } from 'naive-ui/es/form/src/interface' 3 | import type { Ref } from 'vue' 4 | import type { FormItemConfig } from '../types' 5 | import { noop } from '@naive-ultra/utils' 6 | import { reactiveComputed } from '@vueuse/core' 7 | import { ref } from 'vue' 8 | 9 | export interface Metadata { 10 | data: Record 11 | dataTrans: Record 12 | dataRef: Readonly> 13 | dataTransRef: Readonly> 14 | values: Ref> 15 | validate: (filters?: string[] | ShouldRuleBeApplied) => Promise 16 | resetValidate: (fields?: string[]) => void 17 | resetFields: (fields?: string[]) => void 18 | formInstRef: Ref 19 | formItemInstRefs: Ref 20 | } 21 | 22 | export function useMetadata(): Metadata { 23 | const dataRef = ref({}) 24 | const dataTransRef = ref({}) 25 | const values = ref({}) 26 | const formInstRef = ref() 27 | const formItemInstRefs = ref([]) 28 | 29 | function validate(filters?: string[] | ShouldRuleBeApplied) { 30 | if (!filters) 31 | return formInstRef.value?.validate() as any 32 | if (typeof filters === 'function') 33 | return formInstRef.value?.validate(undefined, filters) 34 | 35 | return formInstRef.value?.validate(undefined, rule => filters.includes(rule.key || '')) 36 | } 37 | 38 | function resetValidate(paths?: string[]) { 39 | if (!paths?.length) 40 | return formInstRef.value?.restoreValidation() 41 | for (const item of formItemInstRefs.value) { 42 | if (paths.includes(item.path || '')) 43 | item.restoreValidation() 44 | } 45 | } 46 | 47 | return { 48 | formInstRef, 49 | formItemInstRefs, 50 | data: reactiveComputed(() => dataRef.value), 51 | dataTrans: reactiveComputed(() => dataTransRef.value), 52 | dataRef, 53 | dataTransRef, 54 | validate, 55 | values, 56 | resetValidate, 57 | resetFields: noop, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/Outline/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 31 | 32 | 80 | -------------------------------------------------------------------------------- /packages/components/form/src/composables/useData.ts: -------------------------------------------------------------------------------- 1 | import type { DeepReadonly } from 'vue' 2 | import type { Data, TransformData } from '../types' 3 | import type { Metadata } from './useMetadata' 4 | import { noop } from '@naive-ultra/utils' 5 | import { syncRef } from '@vueuse/core' 6 | import { computed, reactive } from 'vue' 7 | 8 | export function useData(metadata: Metadata) { 9 | const { values, data, dataTrans } = metadata 10 | 11 | const dataRef = computed({ 12 | get: () => parse(values), 13 | set: noop, 14 | }) 15 | 16 | const dataTransRef = computed(() => parseTrans(values).value) 17 | 18 | const dataFirst = JSON.stringify(dataRef.value) 19 | 20 | function resetFields(paths?: string[]) { 21 | const cloneFirst = JSON.parse(dataFirst) 22 | paths ??= Object.keys(dataRef.value) 23 | for (const key of paths) 24 | dataRef.value[key] = cloneFirst[key] ?? null 25 | } 26 | 27 | syncRef(metadata.dataRef, dataRef, { direction: 'rtl' }) 28 | syncRef(metadata.dataTransRef, dataTransRef, { direction: 'rtl' }) 29 | metadata.resetFields = resetFields 30 | 31 | return { 32 | resetFields, 33 | data, 34 | dataTrans, 35 | dataRef, 36 | dataTransRef, 37 | } 38 | } 39 | 40 | function parse(values: Metadata['values']): Data { 41 | const entries = Object.entries(values.value) 42 | .filter(([key]) => typeof key !== 'symbol') 43 | .map(([key]) => key) 44 | .map((key) => { 45 | return [ 46 | key, 47 | computed({ 48 | get: () => values.value[key].value, 49 | set: v => (values.value[key].value = v), 50 | }), 51 | ] 52 | }) 53 | 54 | return reactive(Object.fromEntries(entries)) 55 | } 56 | 57 | function parseTrans(values: Metadata['values']) { 58 | return computed(() => { 59 | const target: Record = {} 60 | for (const key in values.value) { 61 | const { value, transform } = values.value[key] 62 | if (typeof key === 'symbol') 63 | continue 64 | if (transform) 65 | Object.assign(target, transform(value, key)) 66 | else 67 | target[key] = value 68 | } 69 | return target as DeepReadonly> 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /packages/components/form/src/utils/fields.tsx: -------------------------------------------------------------------------------- 1 | import type { Field, FieldConfig, FieldConfigFn } from '../types' 2 | import { clone as _clone, cloneDeep as _cloneDeep, defu, final, isFunction, toArray } from '@naive-ultra/utils' 3 | import { unref } from 'vue' 4 | 5 | export function field(item: FieldConfig) { 6 | const target = encase(item) 7 | 8 | function config(config: FieldConfig) { 9 | if (isFunction(config)) 10 | return field(ctx => defu((config as any)(ctx), { ...final(item, ctx) })) 11 | if (isFunction(item)) 12 | return field((c => defu(config, { ...final(item, c) })) as FieldConfig) 13 | return field(defu(config, { ...item }) as FieldConfig) 14 | } 15 | 16 | target.config = config as unknown as FieldConfigFn 17 | 18 | return target 19 | } 20 | 21 | function encase(config: FieldConfig) { 22 | const target = config as Field 23 | 24 | function clone() { 25 | return _clone(target) 26 | } 27 | function cloneDeep() { 28 | return _cloneDeep(target) 29 | } 30 | function noop() { 31 | return target 32 | } 33 | function preventDefault() { 34 | return target.config({ rules: [], label: '' }) 35 | } 36 | function preventRequired() { 37 | return target.config((config) => { 38 | const source = final(target, config) 39 | const rules = toArray(unref(source.rules) || []) 40 | .filter(v => !v?.required) 41 | return { ...source, rules } 42 | }) 43 | } 44 | 45 | function preventAutofill() { 46 | return target.config({ 47 | renderItem(model, config, defaultRender) { 48 | return ( 49 | <> 50 | 51 | 52 | {defaultRender(model, config)} 53 | 54 | ) 55 | }, 56 | }) 57 | } 58 | target.clone = isFunction(target) ? noop : clone 59 | target.cloneDeep = isFunction(target) ? noop : cloneDeep 60 | target.preventDefault = preventDefault 61 | target.preventRequired = preventRequired 62 | target.preventAutofill = preventAutofill 63 | return target 64 | } 65 | -------------------------------------------------------------------------------- /docs/guide/intro.md: -------------------------------------------------------------------------------- 1 | # The Concept of ProComponents 2 | 3 | Similar to Ant Design Pro Components, Naive Ultra is an advanced component library based on Naive UI, providing higher-level abstraction and encapsulation for backend systems. 4 | 5 | We have made some adjustments to the design of Naive Ultra to better align with the design style of Naive UI and to provide the ability to quickly and efficiently build high-quality backend applications. 6 | 7 | ## Design Ideas 8 | 9 | > The following content is adapted from Ant Pro Components 10 | 11 | For almost any business, we are essentially defining a series of behaviors based on state. Take tables as an example: first, we need a state `data` to store the data requested from the server. To optimize the experience, we also need a `loading` state. So we have a series of behaviors: we need to set `loading=true` first, then initiate a network request, and after the request completes, set `data` to the requested data and `loading=false`. One network request is done. Although this is very simple, a business system has a considerable number of tables, and each table is only defined once, but the workload is still very large. 12 | 13 | If you need to re-request the network, you need to encapsulate the above behavior into a method. When clicking to reload data, if there is pagination, you also need a new variable `page`. You need to determine whether to reset the page number to the first page before re-requesting, which introduces another variable. If your form also needs to control the number of items displayed per page, it becomes even more cumbersome. This kind of repetitive work wastes a lot of our time. 14 | 15 | ## A component !≈ a page 16 | 17 | We do not want you to treat components as pages, but rather as supersets of functionality. This keeps your code flexible, which is also a key difference from Ant Design Pro Components. 18 | 19 | A list page can be composed of `pro-form` + `pro-table`, while an edit page can use `pro-form` + `button` or various other components. This allows us to focus on implementing core business logic and page effects. 20 | 21 | We are very cautious when developing new components. We try to split component functionality into smaller components as much as possible, so you can better compose them as needed. 22 | -------------------------------------------------------------------------------- /packages/utils/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import type { Component, FunctionalComponent, VNode } from 'vue' 2 | import { createDefu } from 'defu' 3 | import { h } from 'vue' 4 | 5 | export function noop() { } 6 | 7 | // eslint-disable-next-line ts/no-unsafe-function-type 8 | export function isFunction(val: any): val is Function { 9 | return typeof val === 'function' 10 | } 11 | 12 | export function isUndefined(val: any): val is undefined { 13 | return typeof val === 'undefined' 14 | } 15 | 16 | export function toArray(val: T | T[]): T[] { 17 | return Array.isArray(val) ? val : [val].filter(Boolean) 18 | } 19 | 20 | export const defu = createDefu((obj, key, value) => { 21 | if (Array.isArray(obj[key]) && Array.isArray(value)) { 22 | obj[key] = value 23 | return true 24 | } 25 | }) 26 | 27 | export function final(value: ((...args: any[]) => T) | T, ...params: any[]) { 28 | return isFunction(value) ? value(...params) : value 29 | } 30 | 31 | export function isObject(val: any): val is Record { 32 | return val !== null && typeof val === 'object' && !Array.isArray(val) 33 | } 34 | 35 | export function isArray(val: any): val is T[] { 36 | return Array.isArray(val) 37 | } 38 | 39 | export function clone(value: T): T { 40 | if (isFunction(value)) 41 | return Object.assign((...args: any) => value(...args), value) 42 | if (Array.isArray(value)) 43 | return [...value] as any 44 | if (typeof value !== 'object' || value === null) 45 | return value 46 | return { ...value } 47 | } 48 | 49 | export function cloneDeep(value: T): T { 50 | if (isFunction(value)) 51 | return Object.assign((...args: any) => value(...args), cloneDeep({ ...value })) 52 | if (isArray(value)) 53 | return value.map(cloneDeep) as any 54 | if (!isObject(value) || value === null) 55 | return value 56 | const keys = Object.entries(value) 57 | const entries = keys.map(([key, value]) => [key, cloneDeep(value)]) 58 | return Object.fromEntries(entries) 59 | } 60 | 61 | export function render(is?: Component | FunctionalComponent | string | number | boolean | VNode | null) { 62 | if (typeof is === 'boolean') 63 | return null 64 | if (typeof is === 'string' || typeof is === 'number') 65 | return is 66 | return is ? h(is) : null 67 | } 68 | -------------------------------------------------------------------------------- /docs/zh-CN/guide/install.md: -------------------------------------------------------------------------------- 1 | # Naive Ultra 2 | 3 | Pro Components 是基于 Naive UI 开发的一套高级组件。它提供了更高层次的抽象和封装,具备开箱即用的可用性,并显著提高了创建 CRUD 页面的效率,专注于页面开发。 4 | 5 | ## Features 6 | 7 | - [Ultra Form](/zh-CN/components/form/) 基于对象的函数式编程的表单模板组件,预设常见布局和行为 8 | - [Ultra Table](/zh-CN/components/table/) 抽象网络请求和表格格式化 9 | - [Ultra Actions](/zh-CN/components/actions/) 多场景的表单、表格控件 10 | - [Ultra Checkbox](/zh-CN/components/checkbox/) 组合式复选框 11 | - [Ultra Radio](/zh-CN/components/radio/) 组合式单选框 12 | - [Ultra Provider](/zh-CN/components/provider/) 全局反馈组件挂载与优化 13 | - 开发中... 14 | 15 | ## 安装 16 | 17 | naive-ultra 中的每个组件都是一个独立的包。您也可以安装 `naive-ultra` 来使用所有组件。 18 | 19 | ```sh 20 | pnpm add @naive-ultra/form 21 | pnpm add @naive-ultra/table 22 | 23 | # or 24 | 25 | pnpm add naive-ultra 26 | ``` 27 | 28 | ## 完整引入 29 | 30 | ```ts 31 | import NaiveUltra from 'naive-ultra' 32 | import { createApp } from 'vue' 33 | import App from './App.vue' 34 | 35 | const app = createApp(App) 36 | 37 | app.use(NaiveUltra) 38 | ``` 39 | 40 | ## 按需导入(推荐) 41 | 42 | 安装 unplugin-vue-components 和 unplugin-auto-import 这两款插件,它们将自动导入 naive-ultra 的所有组件与 API。 43 | 44 | ```sh 45 | npm install -D unplugin-vue-components unplugin-auto-import 46 | ``` 47 | 48 | ### Vite 49 | 50 | ```ts 51 | import NaiveUltraImports from 'naive-ultra/imports' 52 | import NaiveUltraResolver from 'naive-ultra/resolver' 53 | import AutoImport from 'unplugin-auto-import/vite' 54 | import Components from 'unplugin-vue-components/vite' 55 | // vite.config.ts 56 | import { defineConfig } from 'vite' 57 | 58 | export default defineConfig({ 59 | // ... 60 | plugins: [ 61 | // ... 62 | AutoImport({ 63 | imports: [NaiveUltraImports()], 64 | }), 65 | Components({ 66 | resolvers: [NaiveUltraResolver()], 67 | }), 68 | ], 69 | }) 70 | ``` 71 | 72 | ### Webpack 73 | 74 | ```js 75 | const NaiveUltraImports = require('naive-ultra/imports') 76 | const NaiveUltraResolver = require('naive-ultra/resolver') 77 | // webpack.config.js 78 | const AutoImport = require('unplugin-auto-import/webpack') 79 | const Components = require('unplugin-vue-components/webpack') 80 | 81 | module.exports = { 82 | // ... 83 | plugins: [ 84 | // ... 85 | AutoImport({ 86 | imports: [NaiveUltraImports()], 87 | }), 88 | Components({ 89 | resolvers: [NaiveUltraResolver()], 90 | }), 91 | ], 92 | } 93 | ``` 94 | -------------------------------------------------------------------------------- /docs/guide/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## Why does defineForm accept an object instead of an array? 4 | 5 | You may wonder why defineForm declares components in the form of an object rather than an array. In our practical use, we found some issues with arrays, and after long-term practice, we believe using objects is completely feasible. 6 | 7 | **Type inference does not work well with arrays:** 8 | 9 | ```ts 10 | const form = defineForm([ 11 | { 12 | type: 'input', 13 | key: 'name' as const, 14 | }, 15 | { 16 | type: 'input', 17 | key: 'age' as const, 18 | }, 19 | ]) 20 | 21 | // Cannot correctly infer the type of form 22 | form.data // { name: string } | { age: string } 23 | ``` 24 | 25 | But with objects, type inference works easily: 26 | 27 | ```ts 28 | const form = defineForm({ 29 | name: { 30 | type: 'input', 31 | }, 32 | age: { 33 | type: 'input', 34 | }, 35 | }) 36 | 37 | form.data // { name: string; age: string } 38 | ``` 39 | 40 | **Array-based form reuse is not flexible enough:** 41 | 42 | ```ts 43 | const nameField = { 44 | type: 'input', 45 | key: 'name' as const, 46 | // ...other config 47 | } 48 | 49 | const form = defineForm([ 50 | nameField, 51 | { ...nameField, key: 'name2' as const } 52 | ]) 53 | ``` 54 | 55 | Object-based reuse: 56 | 57 | ```ts 58 | const nameField = { 59 | type: 'input', 60 | // ...other config 61 | } 62 | const form = defineForm({ 63 | name: nameField, 64 | name2: nameField, 65 | }) 66 | ``` 67 | 68 | Although arrays make it easy to control the order and display of form fields, objects can also achieve dynamic form display through composition: 69 | 70 | ```ts 71 | const formFields = {/* ... */} 72 | const formFields2 = {/* ... */} 73 | 74 | const form = defineForm(() => { 75 | return { ...formFields, ...formFields2 } 76 | }) 77 | ``` 78 | 79 | ## Pagination config and control separation in defineTable 80 | 81 | In `nu-table`, the `pagination` prop is only for configuring the display parameters of pagination, while `page` and `pageSize` are managed entirely by `defineTable`. You can configure the initial values of these two parameters via the defineTable options. 82 | 83 | ```ts 84 | const table = defineTable({ 85 | // ...other config 86 | page: 2, 87 | pageSize: 15, 88 | }) 89 | 90 | // You can get the current page and page size via table.pagination.page and table.pagination.pageSize 91 | ``` 92 | -------------------------------------------------------------------------------- /docs/components/provider/index.md: -------------------------------------------------------------------------------- 1 | # Ultra Provider 2 | 3 | All global components provided by `naive-ui` cannot be used globally by default, because they need to be mounted on a certain component. We provide the `globals` component, which allows you to use these feedback components globally, and it will automatically mount TypeScript types. 4 | 5 | ```html 6 | 7 | 8 | 9 | 10 | ``` 11 | 12 | Now you can use `$message`, `$dialog`, `$loadingBar`, `$notification` globally. 13 | 14 | 15 | 16 | ```ts 17 | function onClick() { 18 | $message.info('Life is a dream everywhere') 19 | } 20 | ``` 21 | 22 | In addition, we have optimized the return results of `$message` and `$dialog`, so you can directly use `await` to get the user's selection result: 23 | 24 | ```ts 25 | const ins = $dialog.warning({ 26 | content: glbI18n.t('merchant.store.deleteText'), 27 | // If the user clicks confirm, postApiStoreDelete will be executed, and the button will be in loading state 28 | onPositiveClick: () => postApiStoreDelete({ storeId: id }), 29 | }) 30 | 31 | // destroy method 32 | ins.destroy 33 | 34 | // promise methods 35 | ins.then 36 | ins.catch 37 | 38 | // use await to wait for the result 39 | await ins 40 | // The user clicked confirm and waited for the request to finish 41 | ``` 42 | 43 | For `$message`, the promise resolves after the animation ends, which is useful in scenarios where you need to wait for the animation to finish before executing: 44 | 45 | ```ts 46 | await $message.error('Error') 47 | // Animation ends, component is destroyed 48 | ``` 49 | 50 | ## On-demand Usage 51 | 52 | If you only need to use `$message`, you can mount a single component: 53 | 54 | ```html 55 | 56 | 59 | 60 | 65 | ``` 66 | 67 | You can use `nu-installs-provider` for simplified mounting: 68 | 69 | ```html 70 | 71 | 74 | 84 | ``` 85 | -------------------------------------------------------------------------------- /packages/components/table/src/define/index.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue' 2 | import type { OffsetPaginationOptions, ServerPaginationResolve } from '../composables' 3 | 4 | import type { UltraTableInstance, UltraTableRequest } from '../types' 5 | import { useAsyncCallback } from '@naive-ultra/utils' 6 | import { useDebounceFn } from '@vueuse/core' 7 | import { reactive, ref, watch } from 'vue' 8 | import { useMetadata } from '../composables' 9 | 10 | export interface DefineTableOptions { 11 | request: UltraTableRequest 12 | watch?: any[] 13 | immediate?: boolean 14 | pagination?: OffsetPaginationOptions 15 | } 16 | 17 | export function defineTable(options: DefineTableOptions): UltraTableInstance { 18 | const { firstPage, pagination, tableInstRef } = useMetadata(options) 19 | const data = ref([]) as Ref 20 | const { immediate = true } = options 21 | 22 | const [request, loading] = useAsyncCallback( 23 | async (_pagination?: ServerPaginationResolve) => { 24 | if (typeof _pagination?.page === 'undefined') 25 | pagination.page.value = firstPage.value 26 | const result = await options.request(reactive(pagination)) 27 | pagination.total.value = result.total 28 | data.value = result.data 29 | }, 30 | ) 31 | 32 | const search = useDebounceFn(request, 50) 33 | const reset = useDebounceFn(() => request(), 50) 34 | const requestAll = () => requestAllData(options.request) 35 | 36 | watch(pagination.page, (newValue, oldValue) => { 37 | if (newValue !== oldValue) 38 | search(reactive(pagination)) 39 | }) 40 | 41 | watch(pagination.pageSize, () => { 42 | search(reactive(pagination)) 43 | }) 44 | 45 | if (options.watch) 46 | watch(options.watch, () => reset()) 47 | 48 | if (immediate) 49 | reset() 50 | 51 | return reactive({ 52 | _tableInstRef: tableInstRef, 53 | data, 54 | loading, 55 | search, 56 | request: options.request, 57 | reset, 58 | requestAll, 59 | pagination: reactive(pagination), 60 | prev: pagination.prev, 61 | next: pagination.next, 62 | }) 63 | } 64 | 65 | async function requestAllData(request: DefineTableOptions['request']) { 66 | const pagination = { page: 1, pageSize: 1000 } 67 | let length = Infinity 68 | const data: any[] = [] 69 | while (length > 1000) { 70 | const result = await request(pagination as any) 71 | const array = result.data as unknown as any[] || [] 72 | data.push(...array) 73 | length = array.length 74 | pagination.page++ 75 | } 76 | return data as unknown as T 77 | } 78 | -------------------------------------------------------------------------------- /docs/zh-CN/components/form/field-api.md: -------------------------------------------------------------------------------- 1 | # Field API 2 | 3 | Field API 是 `naive-ultra` 表单中的核心功能,提供了丰富的字段操作方法。通过 `field` 函数创建的字段实例具有链式调用、克隆、配置修改等能力,让表单字段的配置更加灵活和可复用。 4 | 5 | ::: demo src="./demo/field.vue" title="Field 基础用法" 6 | `field` 是一个描述对象,field 可以与 form 分离,且通过 `field` 函数创建具有额外的能力。 7 | ::: 8 | 9 | ::: demo src="./demo/clone.vue" title="字段克隆" 10 | 通过 `field` 方法返回的字段,使用 `clone` 方法可以克隆一个新的字段,避免字段数据引用是同一个 value。 11 | ::: 12 | 13 | 而字段配置化,则意味着可分离化和集中处理,你可以通过在某个文件(例如 fields.ts),集中存放具有相同属性的字段内容,这将减少类似组件表单的样板代码。 14 | 15 | ```ts 16 | // fields.ts 17 | export const statusField = field({/* ... */}) 18 | export const methodField = field({/* ... */}) 19 | export const creatorField = field({/* ... */}) 20 | export const staffNoField = field({/* ... */}) 21 | export const staffNameField = field({/* ... */}) 22 | export const phoneField = field({/* ... */}) 23 | export const deviceField = field({/* ... */}) 24 | ``` 25 | 26 | ## config 27 | 28 | 携带并返回新的配置,支持链式调用。 29 | 30 | ```ts 31 | const field = field({ 32 | type: 'input', 33 | label: '用户名' 34 | }) 35 | 36 | const newField = field.config({ 37 | label: '新用户名', 38 | placeholder: '请输入用户名' 39 | }) 40 | ``` 41 | 42 | ## clone 43 | 44 | 浅克隆当前配置,返回一个新的 field 实例。 45 | 46 | ```ts 47 | const field = field({ 48 | type: 'input', 49 | label: '用户名' 50 | }) 51 | 52 | const clonedField = field.clone() 53 | ``` 54 | 55 | ## cloneDeep 56 | 57 | 深克隆当前配置,返回一个新的 field 实例。 58 | 59 | ```ts 60 | const field = field({ 61 | type: 'input', 62 | label: '用户名', 63 | props: { clearable: true } 64 | }) 65 | 66 | const deepClonedField = field.cloneDeep() 67 | ``` 68 | 69 | ## preventDefault 70 | 71 | 将 label、rules 留空,保留其他属性。 72 | 73 | ```ts 74 | const field = field({ 75 | type: 'button', 76 | label: '按钮', 77 | rules: [{ required: true }] 78 | }) 79 | 80 | const noLabelField = field.preventDefault() 81 | ``` 82 | 83 | ## preventRequired 84 | 85 | 去除 rules 中的 required 规则,保留其他校验规则。 86 | 87 | ```ts 88 | const field = field({ 89 | type: 'input', 90 | label: '用户名', 91 | rules: [ 92 | { required: true, message: '请输入用户名' }, 93 | { min: 3, message: '用户名至少3个字符' } 94 | ] 95 | }) 96 | 97 | const noRequiredField = field.preventRequired() 98 | // 结果:只保留 min 规则,去除 required 规则 99 | ``` 100 | 101 | ## preventAutofill 102 | 103 | 阻止浏览器自动填充,通过添加隐藏的 input 元素实现。 104 | 105 | ```ts 106 | const field = field({ 107 | type: 'input', 108 | label: '密码', 109 | props: { type: 'password' } 110 | }) 111 | 112 | const noAutofillField = field.preventAutofill() 113 | ``` 114 | 115 | ## 参考 116 | 117 | - [Field Context](./field-context.md) 118 | - [Field Item](./field-item.md) 119 | -------------------------------------------------------------------------------- /docs/components/form/field-item.md: -------------------------------------------------------------------------------- 1 | # Form Field Item 2 | 3 | Form Field supports most Naive UI form components, including input, select, date picker, etc. You can specify the component type by setting `type: `. 4 | 5 | ::: demo src="./demo/form.vue" title="Basic Usage" 6 | ::: 7 | 8 | ::: demo src="./demo/form-item-props.vue" title="Form Item Props" 9 | `formItemProps` is used to set the properties of the current form item, supporting all properties of [Naive UI n-form-item](https://www.naiveui.com/en-US/os-theme/components/form#FormItem-Props) and [n-grid-item](https://www.naiveui.com/en-US/os-theme/components/grid#GridItem-Props). You can customize label, validation, layout, style, etc. 10 | 11 | - UltraForm enables grid layout by default, all form items are wrapped in `NFormItemGi` (Grid Item), and `formItemProps` supports both n-form-item and n-grid-item properties. 12 | - You can set the column width of the current form item via the `span` property, or further customize it via `formItemProps`. 13 | ::: 14 | 15 | ::: demo src="./demo/props.vue" title="Component Props" 16 | `props` is used to set the properties of the underlying Naive UI component. For example: 17 | ::: 18 | 19 | ::: demo src="./demo/slots.vue" title="Custom Slots" 20 | `slots` is used to customize the slot content of the underlying component, formatted as an object. For example: 21 | ::: 22 | 23 | ## Placeholder 24 | 25 | `placeholder` is used to set the placeholder text for the form item, supporting both string and function formats. 26 | 27 | ```ts 28 | { 29 | placeholder: () => yourI18n.t('...') 30 | } 31 | ``` 32 | 33 | ## Only Render 34 | 35 | When defining a Form, you can use ONLY_RENDER to mark a field as render-only, and it will not be collected as form data. 36 | 37 | ```ts 38 | import { defineForm, field, ONLY_RENDER } from 'naive-ultra' 39 | 40 | const form = defineForm({ 41 | [ONLY_RENDER()]: field({ 42 | type: 'button', 43 | props: { 44 | onClick() { 45 | form.validate() 46 | } 47 | }, 48 | }) 49 | }) 50 | ``` 51 | 52 | ## Other Common Properties 53 | 54 | - `label`: Form item label 55 | - `span`: Column width of the current form item (only effective in grid layout) 56 | - `rules`: Validation rules, supports Naive UI validation format 57 | - `placeholder`: Placeholder 58 | - `renderItem`: Custom render function, suitable for complex scenarios 59 | 60 | ## Reference 61 | 62 | - [Naive UI n-form Props](https://www.naiveui.com/en-US/os-theme/components/form#Form-Props) 63 | - [Naive UI n-form-item Props](https://www.naiveui.com/en-US/os-theme/components/form#FormItem-Props) 64 | - [Naive UI n-grid Props](https://www.naiveui.com/en-US/os-theme/components/grid#Grid-Props) 65 | - [Naive UI n-grid-item Props](https://www.naiveui.com/en-US/os-theme/components/grid#GridItem-Props) 66 | -------------------------------------------------------------------------------- /docs/guide/install.md: -------------------------------------------------------------------------------- 1 | # Naive Ultra 2 | 3 | Pro Components is a set of advanced components developed based on Naive UI. It provides higher-level abstraction and encapsulation, with out-of-the-box usability, and significantly improves the efficiency of building CRUD pages, focusing on page development. 4 | 5 | ## Features 6 | 7 | - [Ultra Form](/components/form/) Object-based functional programming form template component, with preset common layouts and behaviors 8 | - [Ultra Table](/components/table/) Abstracts network requests and table formatting 9 | - [Ultra Actions](/components/actions/) Multi-scenario form and table controls 10 | - [Ultra Checkbox](/components/checkbox/) Composite checkbox group 11 | - [Ultra Radio](/components/radio/) Composite radio group 12 | - [Ultra Provider](/components/provider/) Global feedback component mounting and optimization 13 | - More in development... 14 | 15 | ## Installation 16 | 17 | Each component in naive-ultra is a standalone package. You can also install `naive-ultra` to use all components. 18 | 19 | ```sh 20 | pnpm add @naive-ultra/form 21 | pnpm add @naive-ultra/table 22 | 23 | # or 24 | 25 | pnpm add naive-ultra 26 | ``` 27 | 28 | ## Full Import 29 | 30 | ```ts 31 | import NaiveUltra from 'naive-ultra' 32 | import { createApp } from 'vue' 33 | import App from './App.vue' 34 | 35 | const app = createApp(App) 36 | 37 | app.use(NaiveUltra) 38 | ``` 39 | 40 | ## On-demand Import (Recommended) 41 | 42 | Install the plugins unplugin-vue-components and unplugin-auto-import, which will automatically import all components and APIs from naive-ultra. 43 | 44 | ```sh 45 | npm install -D unplugin-vue-components unplugin-auto-import 46 | ``` 47 | 48 | ### Vite 49 | 50 | ```ts 51 | import NaiveUltraImports from 'naive-ultra/imports' 52 | import NaiveUltraResolver from 'naive-ultra/resolver' 53 | import AutoImport from 'unplugin-auto-import/vite' 54 | import Components from 'unplugin-vue-components/vite' 55 | // vite.config.ts 56 | import { defineConfig } from 'vite' 57 | 58 | export default defineConfig({ 59 | // ... 60 | plugins: [ 61 | // ... 62 | AutoImport({ 63 | imports: [NaiveUltraImports()], 64 | }), 65 | Components({ 66 | resolvers: [NaiveUltraResolver()], 67 | }), 68 | ], 69 | }) 70 | ``` 71 | 72 | ### Webpack 73 | 74 | ```js 75 | const NaiveUltraImports = require('naive-ultra/imports') 76 | const NaiveUltraResolver = require('naive-ultra/resolver') 77 | // webpack.config.js 78 | const AutoImport = require('unplugin-auto-import/webpack') 79 | const Components = require('unplugin-vue-components/webpack') 80 | 81 | module.exports = { 82 | // ... 83 | plugins: [ 84 | // ... 85 | AutoImport({ 86 | imports: [NaiveUltraImports()], 87 | }), 88 | Components({ 89 | resolvers: [NaiveUltraResolver()], 90 | }), 91 | ], 92 | } 93 | ``` 94 | -------------------------------------------------------------------------------- /docs/components/table/index.md: -------------------------------------------------------------------------------- 1 | # Ultra Table 2 | 3 | nu-table is designed to solve the problem of writing a lot of table template code in projects, so it encapsulates a lot of common logic. These encapsulations can be simply classified as preset behaviors and preset logic. 4 | 5 | When your table needs to interact with the server or requires multiple cell formats, UltraTable is the best choice. If you just want to render a table, it is recommended to use [data-table](https://www.naiveui.com/en-US/os-theme/components/data-table) or [table](https://www.naiveui.com/en-US/os-theme/components/table). 6 | 7 | 8 | 9 | ::: demo src="./demo/request.vue" title="External Control" 10 | 11 | Use `defineTable` for external request control. 12 | 13 | ::: 14 | 15 | ::: demo src="./demo/form.vue" title="Form Integration" 16 | 17 | You can also use it with `defineForm` to create a complete search page: 18 | 19 | ::: 20 | 21 | ::: demo src="./demo/pagination.vue" title="Table Pagination" 22 | 23 | Pagination is enabled by default. If you don't need pagination, you can set the `pagination` prop to `false` to disable it, or override the `pagination` prop to customize it. 24 | 25 | ::: 26 | 27 | ::: demo src="./demo/watcher.vue" title="Watch Data" 28 | 29 | When the table data changes, you may need to perform some extra operations. You can use the `watch` prop to listen for data changes, and it will automatically reset pagination and request data. 30 | 31 | ::: 32 | 33 | ## Props 34 | 35 | | Name | Type | Default | Description | 36 | | ---------- | ------------------------------ | ------- | ------------------- | 37 | | is | `UltraTableInstance` | `-` | Instance of the component | 38 | | pagination | `boolean \| PaginationProps` | `true` | Pagination config | 39 | 40 | > For more parameters, see [data-table](https://www.naiveui.com/en-US/os-theme/components/data-table). 41 | 42 | ## Methods (table) 43 | 44 | | Name | Type | Description | 45 | | ----------- | ------------------------------------------------- | ------------------- | 46 | | pagination | `OffsetPagination` | Table pagination config | 47 | | next | `() => void` | Go to next page and request data | 48 | | prev | `() => void` | Go to previous page and request data | 49 | | search | `(pagination?: ServerPaginationResolve) => Promise` | Request content based on pagination info | 50 | | reset | `() => Promise` | Reset page and request data | 51 | | request | `(pagination: ServerPaginationResolve) => DataResolved \| Promise>` | Source request function | 52 | | requestAll | `() => Promise` | Request all data based on pagination info, usually for exporting .csv files | 53 | -------------------------------------------------------------------------------- /packages/components/table/src/components/index.tsx: -------------------------------------------------------------------------------- 1 | import type { PaginationProps } from 'naive-ui' 2 | import type { PropType } from 'vue' 3 | 4 | import type { UltraTableInstance } from '../types' 5 | import { If, isObject } from '@naive-ultra/utils' 6 | 7 | import { reactiveOmit, reactivePick } from '@vueuse/core' 8 | import { c, dataTableProps, NDataTable, NPagination } from 'naive-ui' 9 | import { computed, defineComponent, reactive, toRefs } from 'vue' 10 | import { useTableMinWidth } from '../composables' 11 | 12 | const { pagination: _p, ...extendsProps } = dataTableProps 13 | 14 | export const ultraTableProps = { 15 | ...extendsProps, 16 | is: { 17 | type: Object as PropType, 18 | required: true as const, 19 | }, 20 | pagination: { 21 | type: [Boolean, Object] as PropType>, 22 | default: true as const, 23 | }, 24 | } 25 | 26 | export const NuTable = defineComponent({ 27 | name: 'NuTable', 28 | props: ultraTableProps, 29 | setup(_props, { slots }) { 30 | style.mount() 31 | 32 | const customs = ['is', 'pagination'] as const 33 | const props = reactivePick(_props, ...customs) 34 | const tableProps = reactiveOmit(_props, ...customs) 35 | 36 | const { 37 | _tableInstRef, 38 | data, 39 | loading, 40 | } = toRefs(props.is) 41 | 42 | const instance = computed(() => reactive(props.is)) 43 | const columns = computed(() => tableProps.columns ?? []) 44 | 45 | const showPagination = computed(() => props.pagination !== false) 46 | 47 | const pagination = computed(() => { 48 | return { 49 | showQuickJumper: instance.value.pagination.pageCount > 1, 50 | pageCount: instance.value.pagination.pageCount, 51 | showSizePicker: true, 52 | pageSizes: [10, 20, 30, 40], 53 | ...(isObject(props.pagination) ? props.pagination : {}), 54 | } as PaginationProps 55 | }) 56 | 57 | const minWidth = useTableMinWidth(columns) 58 | 59 | return () => ( 60 |
64 | 71 | {slots} 72 | 73 | 74 | 81 | 82 |
83 | ) 84 | }, 85 | }) 86 | 87 | const style = c('.nu-table', [ 88 | c('.n-data-table-table', { 89 | minWidth: 'var(--n-table-min-width) !important', 90 | }), 91 | 92 | c('.nu-table__pagination', { 93 | display: 'flex', 94 | justifyContent: 'flex-end', 95 | marginTop: '18px', 96 | userSelect: 'none', 97 | }), 98 | ]) 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Naive Ultra 2 | 3 | [![npm version][npm-version-src]][npm-version-href] 4 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 5 | [![bundle][bundle-src]][bundle-href] 6 | [![JSDocs][jsdocs-src]][jsdocs-href] 7 | [![License][license-src]][license-href] 8 | 9 | See our website [naive-ultra](https://naiveui-pro.vercel.app/) for more information. 10 | 11 | ## Install 12 | 13 | Using pnpm: 14 | 15 | ```bash 16 | pnpm install --save naive-ultra 17 | ``` 18 | 19 | or using yarn: 20 | 21 | ```bash 22 | yarn add naive-ultra 23 | ``` 24 | 25 | ## Usage 26 | 27 | ### Globals 28 | 29 | ```js 30 | import NaiveUltra from 'naive-ultra' 31 | import { createApp } from 'vue' 32 | import App from './App.vue' 33 | 34 | const app = createApp(App) 35 | 36 | app.use(NaiveUltra) 37 | ``` 38 | 39 | ## On-Demand Import (Recommended) 40 | 41 | Install the `unplugin-vue-components` and `unplugin-auto-import` plugins, which will automatically import all components and APIs from `naive-ultra`. 42 | 43 | ```sh 44 | npm install -D unplugin-vue-components unplugin-auto-import 45 | ``` 46 | 47 | ### Vite 48 | 49 | ```ts 50 | import NaiveUltraImports from 'naive-ultra/imports' 51 | import NaiveUltraResolver from 'naive-ultra/resolver' 52 | import AutoImport from 'unplugin-auto-import/vite' 53 | import Components from 'unplugin-vue-components/vite' 54 | // vite.config.ts 55 | import { defineConfig } from 'vite' 56 | 57 | export default defineConfig({ 58 | // ... 59 | plugins: [ 60 | // ... 61 | AutoImport({ 62 | imports: [NaiveUltraImports()], 63 | }), 64 | Components({ 65 | resolvers: [NaiveUltraResolver()], 66 | }), 67 | ], 68 | }) 69 | ``` 70 | 71 | ### Webpack 72 | 73 | ```js 74 | // webpack.config.js 75 | const NaiveUltraImports = require('naive-ultra/imports') 76 | const NaiveUltraResolver = require('naive-ultra/resolver') 77 | const AutoImport = require('unplugin-auto-import/webpack') 78 | const Components = require('unplugin-vue-components/webpack') 79 | 80 | module.exports = { 81 | // ... 82 | plugins: [ 83 | // ... 84 | AutoImport({ 85 | imports: [NaiveUltraImports()], 86 | }), 87 | Components({ 88 | resolvers: [NaiveUltraResolver()], 89 | }), 90 | ], 91 | } 92 | ``` 93 | 94 | ## License 95 | 96 | [MIT](./LICENSE) License © [Hairyf](https://github.com/hairyf) 97 | 98 | 99 | 100 | [npm-version-src]: https://img.shields.io/npm/v/naive-ultra?style=flat&colorA=080f12&colorB=1fa669 101 | [npm-version-href]: https://npmjs.com/package/naive-ultra 102 | [npm-downloads-src]: https://img.shields.io/npm/dm/naive-ultra?style=flat&colorA=080f12&colorB=1fa669 103 | [npm-downloads-href]: https://npmjs.com/package/naive-ultra 104 | [bundle-src]: https://img.shields.io/bundlephobia/minzip/naive-ultra?style=flat&colorA=080f12&colorB=1fa669&label=minzip 105 | [bundle-href]: https://bundlephobia.com/result?p=naive-ultra 106 | [license-src]: https://img.shields.io/github/license/hairyf/naive-ultra.svg?style=flat&colorA=080f12&colorB=1fa669 107 | [license-href]: https://github.com/hairyf/naive-ultra/blob/main/LICENSE 108 | [jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=1fa669 109 | [jsdocs-href]: https://www.jsdocs.io/package/naive-ultra 110 | -------------------------------------------------------------------------------- /docs/components/form/field-api.md: -------------------------------------------------------------------------------- 1 | # Field API 2 | 3 | The Field API is the core feature of `naive-ultra` forms, providing rich field manipulation methods. Fields created via the `field` function support chain calls, cloning, config modification, and more, making form field configuration more flexible and reusable. 4 | 5 | ::: demo src="./demo/field.vue" title="Field Basic Usage" 6 | `field` is a descriptor object, and can be separated from the form. The `field` function can create fields with additional capabilities. 7 | ::: 8 | 9 | ::: demo src="./demo/clone.vue" title="Field Clone" 10 | The `clone` method of a field returns a new field, avoiding value reference issues. 11 | ::: 12 | 13 | Field configuration means you can modularize and centralize field definitions, for example in a file like fields.ts, to reduce repetitive code for similar form templates. 14 | 15 | ```ts 16 | // fields.ts 17 | export const statusField = field({/* ... */}) 18 | export const methodField = field({/* ... */}) 19 | export const creatorField = field({/* ... */}) 20 | export const staffNoField = field({/* ... */}) 21 | export const staffNameField = field({/* ... */}) 22 | export const phoneField = field({/* ... */}) 23 | export const deviceField = field({/* ... */}) 24 | ``` 25 | 26 | ## config 27 | 28 | Attach and return a new config, supports chain calls. 29 | 30 | ```ts 31 | const field = field({ 32 | type: 'input', 33 | label: 'Username' 34 | }) 35 | 36 | const newField = field.config({ 37 | label: 'New Username', 38 | placeholder: 'Please enter username' 39 | }) 40 | ``` 41 | 42 | ## clone 43 | 44 | Shallow clone the current config, returns a new field instance. 45 | 46 | ```ts 47 | const field = field({ 48 | type: 'input', 49 | label: 'Username' 50 | }) 51 | 52 | const clonedField = field.clone() 53 | ``` 54 | 55 | ## cloneDeep 56 | 57 | Deep clone the current config, returns a new field instance. 58 | 59 | ```ts 60 | const field = field({ 61 | type: 'input', 62 | label: 'Username', 63 | props: { clearable: true } 64 | }) 65 | 66 | const deepClonedField = field.cloneDeep() 67 | ``` 68 | 69 | ## preventDefault 70 | 71 | Clears label and rules, keeps other properties. 72 | 73 | ```ts 74 | const field = field({ 75 | type: 'button', 76 | label: 'Button', 77 | rules: [{ required: true }] 78 | }) 79 | 80 | const noLabelField = field.preventDefault() 81 | ``` 82 | 83 | ## preventRequired 84 | 85 | Removes required rules from rules, keeps other validation rules. 86 | 87 | ```ts 88 | const field = field({ 89 | type: 'input', 90 | label: 'Username', 91 | rules: [ 92 | { required: true, message: 'Please enter username' }, 93 | { min: 3, message: 'Username must be at least 3 characters' } 94 | ] 95 | }) 96 | 97 | const noRequiredField = field.preventRequired() 98 | // Result: only min rule remains, required rule is removed 99 | ``` 100 | 101 | ## preventAutofill 102 | 103 | Prevents browser autofill by adding a hidden input element. 104 | 105 | ```ts 106 | const field = field({ 107 | type: 'input', 108 | label: 'Password', 109 | props: { type: 'password' } 110 | }) 111 | 112 | const noAutofillField = field.preventAutofill() 113 | ``` 114 | 115 | ## Reference 116 | 117 | - [Field Context](./field-context.md) 118 | - [Field Item](./field-item.md) 119 | --------------------------------------------------------------------------------