@soybeanjs/ui 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -69,6 +69,52 @@ provideAccordionUi(ui); // headless reads this via useAccordionUi()
69
69
  - **`ConfigProvider`** — sets global `dir`, `locale`, `nonce`, and default `tooltip` config for the entire component tree, including RTL layout switching
70
70
  - **`cn()`** — Tailwind-aware class merge (`clsx` + `tailwind-merge`), used for conflict-free class composition
71
71
 
72
+ ### Locale Support
73
+
74
+ `ConfigProvider` supports the following locale bundles:
75
+
76
+ | Code | Language |
77
+ | ------- | ------------------- |
78
+ | `zh-CN` | Simplified Chinese |
79
+ | `zh-TW` | Traditional Chinese |
80
+ | `en` | English |
81
+ | `ar` | Arabic |
82
+ | `ja` | Japanese |
83
+ | `ko` | Korean |
84
+ | `de` | German |
85
+ | `fr` | French |
86
+ | `es` | Spanish |
87
+ | `pt-BR` | Portuguese (Brazil) |
88
+ | `ru` | Russian |
89
+ | `tr` | Turkish |
90
+ | `id` | Indonesian |
91
+
92
+ Only `en` and `zh-CN` are pre-registered by default. `registerLocale` supports two registration styles:
93
+
94
+ - Pass a `LocaleRegistry` object. Built-in locale files from `@soybeanjs/headless/locale/{code}` already export this shape, including `dir` metadata.
95
+ - Pass a locale key plus `LocaleMessages` for a lightweight custom locale.
96
+
97
+ The shorthand `registerLocale(key, messages)` form uses the key as the locale name and falls back to `ltr`. Use the object form when you need explicit metadata such as `rtl`.
98
+
99
+ ```ts
100
+ import { en, registerLocale } from '@soybeanjs/headless/locale';
101
+ import type { LocaleMessages } from '@soybeanjs/headless/locale';
102
+ import ar from '@soybeanjs/headless/locale/ar';
103
+
104
+ registerLocale(ar);
105
+
106
+ const customMessages: LocaleMessages = {
107
+ ...en.messages,
108
+ pagination: {
109
+ ...en.messages.pagination,
110
+ nextPage: 'Next →',
111
+ prevPage: '← Prev'
112
+ }
113
+ };
114
+
115
+ registerLocale('custom', customMessages);
116
+ ```
117
+
72
118
  ### Package Exports
73
119
 
74
120
  **@soybeanjs/headless** ships fine-grained sub-paths:
package/README.zh-CN.md CHANGED
@@ -69,6 +69,52 @@ provideAccordionUi(ui); // headless 通过 useAccordionUi() 读取
69
69
  - **`ConfigProvider`** — 全局设置 `dir`、`locale`、`nonce` 及默认 `tooltip` 配置,应用于整个组件树,并支持 RTL 布局切换
70
70
  - **`cn()`** — Tailwind 感知的类名合并工具(`clsx` + `tailwind-merge`),解决类名冲突
71
71
 
72
+ ### 语言支持
73
+
74
+ `ConfigProvider` 当前支持以下 locale 文案包:
75
+
76
+ | 代码 | 语言 |
77
+ | ------- | ------------ |
78
+ | `zh-CN` | 简体中文 |
79
+ | `zh-TW` | 繁體中文 |
80
+ | `en` | 英语 |
81
+ | `ar` | 阿拉伯语 |
82
+ | `ja` | 日语 |
83
+ | `ko` | 韩语 |
84
+ | `de` | 德语 |
85
+ | `fr` | 法语 |
86
+ | `es` | 西班牙语 |
87
+ | `pt-BR` | 巴西葡萄牙语 |
88
+ | `ru` | 俄语 |
89
+ | `tr` | 土耳其语 |
90
+ | `id` | 印度尼西亚语 |
91
+
92
+ 默认只有 `en` 和 `zh-CN` 会被预注册。`registerLocale` 支持两种注册方式:
93
+
94
+ - 直接传入 `LocaleRegistry` 对象。`@soybeanjs/headless/locale/{code}` 导出的内置语言文件已经是这种结构,并且自带 `dir` 元数据。
95
+ - 传入 locale key 和 `LocaleMessages`,用于快速注册一个轻量自定义语言。
96
+
97
+ 简写形式 `registerLocale(key, messages)` 会将 key 作为语言名,并在未显式提供方向时回退到 `ltr`。如果你需要像 `ar` 这样的 `rtl` 元数据,请优先使用对象形式。
98
+
99
+ ```ts
100
+ import { en, registerLocale } from '@soybeanjs/headless/locale';
101
+ import type { LocaleMessages } from '@soybeanjs/headless/locale';
102
+ import ar from '@soybeanjs/headless/locale/ar';
103
+
104
+ registerLocale(ar);
105
+
106
+ const customMessages: LocaleMessages = {
107
+ ...en.messages,
108
+ pagination: {
109
+ ...en.messages.pagination,
110
+ nextPage: '下一页 →',
111
+ prevPage: '← 上一页'
112
+ }
113
+ };
114
+
115
+ registerLocale('custom', customMessages);
116
+ ```
117
+
72
118
  ### 包导出
73
119
 
74
120
  **@soybeanjs/headless** 提供精细化子路径导出:
@@ -1 +1 @@
1
- import{themeSizes as e}from"../../constants/common.js";import t from"../dialog/dialog-provider.js";import n from"../progress/progress-provider.js";import r from"../toast/toast-provider.js";import{provideConfigProviderContext as i}from"./context.js";import a from"../icon/icon.js";import{createBlock as o,createCommentVNode as s,createTextVNode as c,createVNode as l,defineComponent as u,guardReactiveProps as d,h as f,mergeProps as p,normalizeProps as m,openBlock as h,renderSlot as g,toDisplayString as _,unref as v,watch as y,watchEffect as b,withCtx as x}from"vue";import{useOmitProps as S}from"@soybeanjs/headless/composables";import{useStorage as C}from"@vueuse/core";import{ConfigProvider as w}from"@soybeanjs/headless/config-provider";import{Primitive as T}from"@soybeanjs/headless/primitive";import{isClient as E,transformPropsToContext as D}from"@soybeanjs/headless/shared";import{createShadcnTheme as O}from"@soybeanjs/shadcn-theme";const k=u({name:`SConfigProvider`,__name:`config-provider`,props:{theme:{default:()=>({})},size:{default:`md`},iconify:{default:()=>({width:`1.25em`,height:`1.25em`})},progress:{},toast:{},customToast:{type:Boolean},dir:{default:`ltr`},locale:{},nonce:{},tooltip:{},nuxt:{type:Boolean},iconRender:{},messages:{}},setup(u){let k=u,A=S(k,[`iconRender`,`theme`,`size`,`iconify`,`progress`,`toast`,`customToast`]),j=k.iconRender??(e=>f(a,{icon:e}));i({...D(k),iconRender:j});let{getCss:M}=O(k.theme),N=()=>M(k.theme,k.theme.radius),P=C(`__SoybeanUI_themeVars`,N());function F(t){if(!E)return;document.documentElement.classList.add(`size-${t}`);let n=e.filter(e=>e!==t).map(e=>`size-${e}`);document.documentElement.classList.remove(...n)}return y(()=>k.size,e=>{F(e)},{immediate:!0,flush:`sync`}),b(()=>{P.value=N()}),(e,i)=>(h(),o(v(w),p(v(A),{"icon-render":v(j)}),{default:x(()=>[l(v(T),{id:`__SoybeanUI_themeVars`,as:`style`},{default:x(()=>[c(_(v(P)),1)]),_:1}),g(e.$slots,`default`),k.customToast?s(`v-if`,!0):(h(),o(r,m(p({key:0},k.toast)),null,16)),l(t),l(n,m(d(k.progress)),null,16)]),_:3},16,[`icon-render`]))}});export{k as default};
1
+ import{themeSizes as e}from"../../constants/common.js";import t from"../dialog/dialog-provider.js";import n from"../progress/progress-provider.js";import r from"../toast/toast-provider.js";import{provideConfigProviderContext as i}from"./context.js";import a from"../icon/icon.js";import{createBlock as o,createCommentVNode as s,createTextVNode as c,createVNode as l,defineComponent as u,guardReactiveProps as d,h as f,mergeProps as p,normalizeProps as m,openBlock as h,renderSlot as g,toDisplayString as _,unref as v,watch as y,watchEffect as b,withCtx as x}from"vue";import{useOmitProps as S}from"@soybeanjs/headless/composables";import{useStorage as C}from"@vueuse/core";import{ConfigProvider as w}from"@soybeanjs/headless/config-provider";import{Primitive as T}from"@soybeanjs/headless/primitive";import{isClient as E,transformPropsToContext as D}from"@soybeanjs/headless/shared";import{createShadcnTheme as O}from"@soybeanjs/shadcn-theme";const k=u({name:`SConfigProvider`,__name:`config-provider`,props:{theme:{default:()=>({})},size:{default:`md`},iconify:{default:()=>({width:`1.25em`,height:`1.25em`})},progress:{},toast:{},customToast:{type:Boolean},dir:{},locale:{},nonce:{},tooltip:{},nuxt:{type:Boolean},iconRender:{},messages:{}},setup(u){let k=u,A=S(k,[`iconRender`,`theme`,`size`,`iconify`,`progress`,`toast`,`customToast`]),j=k.iconRender??(e=>f(a,{icon:e}));i({...D(k),iconRender:j});let{getCss:M}=O(k.theme),N=()=>M(k.theme,k.theme.radius),P=C(`__SoybeanUI_themeVars`,N());function F(t){if(!E)return;document.documentElement.classList.add(`size-${t}`);let n=e.filter(e=>e!==t).map(e=>`size-${e}`);document.documentElement.classList.remove(...n)}return y(()=>k.size,e=>{F(e)},{immediate:!0,flush:`sync`}),b(()=>{P.value=N()}),(e,i)=>(h(),o(v(w),p(v(A),{"icon-render":v(j)}),{default:x(()=>[l(v(T),{id:`__SoybeanUI_themeVars`,as:`style`},{default:x(()=>[c(_(v(P)),1)]),_:1}),g(e.$slots,`default`),k.customToast?s(`v-if`,!0):(h(),o(r,m(p({key:0},k.toast)),null,16)),l(t),l(n,m(d(k.progress)),null,16)]),_:3},16,[`icon-render`]))}});export{k as default};
@@ -2,7 +2,6 @@ import { ThemeSize } from "../../theme/types.js";
2
2
  import { ConfigProviderProps } from "./types.js";
3
3
  import * as _$vue from "vue";
4
4
  import * as _$_soybeanjs_shadcn_theme0 from "@soybeanjs/shadcn-theme";
5
- import * as _$_soybeanjs_headless0 from "@soybeanjs/headless";
6
5
 
7
6
  //#region src/components/config-provider/config-provider.vue.d.ts
8
7
  declare var __VLS_14: {};
@@ -11,7 +10,6 @@ type __VLS_Slots = {} & {
11
10
  };
12
11
  declare const __VLS_base: _$vue.DefineComponent<ConfigProviderProps, {}, {}, {}, {}, _$vue.ComponentOptionsMixin, _$vue.ComponentOptionsMixin, {}, string, _$vue.PublicProps, Readonly<ConfigProviderProps> & Readonly<{}>, {
13
12
  size: ThemeSize;
14
- dir: _$_soybeanjs_headless0.Direction;
15
13
  theme: _$_soybeanjs_shadcn_theme0.ThemeOptions;
16
14
  iconify: IconifyOptions;
17
15
  }, {}, {}, {}, string, _$vue.ComponentProvideOptions, false, {}, any>;
@@ -1 +1 @@
1
- import{mergeVariants as e}from"../../theme/shared.js";import"../../theme/index.js";import t from"../calendar/calendar.js";import{dateFieldVariants as n}from"../date-field/variants.js";import{datePickerVariants as r}from"./variants.js";import{computed as i,createBlock as a,createVNode as o,defineComponent as s,mergeProps as c,openBlock as l,toHandlers as u,unref as d,withCtx as f}from"vue";import{useForwardListeners as p,useOmitProps as m}from"@soybeanjs/headless/composables";import{DatePickerCompact as h,provideDatePickerUi as g}from"@soybeanjs/headless/date-picker";const _=s({name:`SDatePicker`,__name:`date-picker`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{},ui:{},calendarUi:{},dateFieldProps:{},placement:{},showArrow:{type:Boolean},triggerProps:{},portalProps:{},positionerProps:{},popupProps:{},arrowProps:{},closeProps:{},disabled:{type:Boolean},open:{type:Boolean,default:void 0},defaultOpen:{type:Boolean},modal:{type:Boolean},headerProps:{},headingProps:{},prevProps:{},nextProps:{},gridProps:{},gridHeadProps:{},gridBodyProps:{},gridRowProps:{},headCellProps:{},cellProps:{},cellTriggerProps:{},dir:{},locale:{},modelValue:{},defaultValue:{},multiple:{},placeholder:{},defaultPlaceholder:{},readonly:{type:Boolean},pagedNavigation:{type:Boolean},preventDeselect:{type:Boolean},weekStartsOn:{},weekdayFormat:{},calendarLabel:{},fixedWeeks:{type:Boolean},maxValue:{},minValue:{},numberOfMonths:{},initialFocus:{type:Boolean},isDateDisabled:{},isDateUnavailable:{},nextPage:{},prevPage:{},disableDaysOutsideCurrentView:{type:Boolean},asChild:{type:Boolean},as:{}},emits:[`update:open`,`escapeKeyDown`,`pointerDownOutside`,`focusOutside`,`interactOutside`,`openAutoFocus`,`closeAutoFocus`,`update:modelValue`,`update:placeholder`],setup(s,{emit:_}){let v=s,y=_,b=m(v,[`class`,`size`,`ui`]),x=p(y);return g(i(()=>{let t=r({size:v.size}),i=n({size:v.size});return e(Object.assign(t,i),v.ui,{root:v.class})})),(e,n)=>(l(),a(d(h),c(d(b),u(d(x))),{default:f(({calendarProps:e,close:n,onUpdateModelValue:r,onUpdatePlaceholder:i})=>[o(t,c(e,{size:s.size,ui:s.calendarUi,"onUpdate:modelValue":e=>{r(e),n()},"onUpdate:placeholder":i}),null,16,[`size`,`ui`,`onUpdate:modelValue`,`onUpdate:placeholder`])]),_:1},16))}});export{_ as default};
1
+ import{mergeVariants as e}from"../../theme/shared.js";import"../../theme/index.js";import t from"../calendar/calendar.js";import{dateFieldVariants as n}from"../date-field/variants.js";import{datePickerVariants as r}from"./variants.js";import{computed as i,createBlock as a,createVNode as o,defineComponent as s,mergeProps as c,openBlock as l,toHandlers as u,unref as d,withCtx as f}from"vue";import{useForwardListeners as p,useOmitProps as m}from"@soybeanjs/headless/composables";import{DatePickerCompact as h,provideDatePickerUi as g}from"@soybeanjs/headless/date-picker";const _=s({name:`SDatePicker`,__name:`date-picker`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{},ui:{},calendarUi:{},dateFieldProps:{},placement:{},showArrow:{type:Boolean},triggerProps:{},portalProps:{},positionerProps:{},popupProps:{},arrowProps:{},closeProps:{},disabled:{type:Boolean},open:{type:Boolean,default:void 0},defaultOpen:{type:Boolean},modal:{type:Boolean},headerProps:{},headingProps:{},prevProps:{},nextProps:{},gridProps:{},gridHeadProps:{},gridBodyProps:{},gridRowProps:{},headCellProps:{},cellProps:{},cellTriggerProps:{},dir:{},locale:{},modelValue:{},defaultValue:{},multiple:{type:Boolean},placeholder:{},defaultPlaceholder:{},readonly:{type:Boolean},pagedNavigation:{type:Boolean},preventDeselect:{type:Boolean},weekStartsOn:{},weekdayFormat:{},calendarLabel:{},fixedWeeks:{type:Boolean},maxValue:{},minValue:{},numberOfMonths:{},initialFocus:{type:Boolean},isDateDisabled:{},isDateUnavailable:{},nextPage:{},prevPage:{},disableDaysOutsideCurrentView:{type:Boolean},asChild:{type:Boolean},as:{}},emits:[`update:open`,`escapeKeyDown`,`pointerDownOutside`,`focusOutside`,`interactOutside`,`openAutoFocus`,`closeAutoFocus`,`update:modelValue`,`update:placeholder`],setup(s,{emit:_}){let v=s,y=_,b=m(v,[`class`,`size`,`ui`]),x=p(y);return g(i(()=>{let t=r({size:v.size}),i=n({size:v.size});return e(Object.assign(t,i),v.ui,{root:v.class})})),(e,n)=>(l(),a(d(h),c(d(b),u(d(x))),{default:f(({calendarProps:e,close:n,onUpdateModelValue:r,onUpdatePlaceholder:i})=>[o(t,c(e,{size:s.size,ui:s.calendarUi,"onUpdate:modelValue":e=>{r(e),n()},"onUpdate:placeholder":i}),null,16,[`size`,`ui`,`onUpdate:modelValue`,`onUpdate:placeholder`])]),_:1},16))}});export{_ as default};
@@ -6,8 +6,8 @@ import { PageTabsOptionData } from "@soybeanjs/headless/page-tabs";
6
6
  //#region src/components/page-tabs/page-tabs.vue.d.ts
7
7
  declare const __VLS_export: <T extends PageTabsOptionData = PageTabsOptionData>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
8
8
  props: _$vue.PublicProps & __VLS_PrettifyLocal<PageTabsProps<T> & {
9
- onClick?: ((tab: T) => any) | undefined;
10
9
  onContextmenu?: ((tab: T) => any) | undefined;
10
+ onClick?: ((tab: T) => any) | undefined;
11
11
  onClose?: ((tab: T) => any) | undefined;
12
12
  "onUpdate:modelValue"?: ((value: string) => any) | undefined;
13
13
  "onUpdate:items"?: ((items: T[]) => any) | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soybeanjs/ui",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "SoybeanUI is built on top of SoybeanHeadless, providing a collection of styled components for Vue 3.",
5
5
  "homepage": "https://github.com/soybeanjs/soybean-ui",
6
6
  "bugs": {
@@ -57,25 +57,25 @@
57
57
  "tailwind-variants": "^3.2.2",
58
58
  "valibot": "^1.4.0",
59
59
  "zod": "^4.4.3",
60
- "@soybeanjs/headless": "^0.20.0"
60
+ "@soybeanjs/headless": "^0.21.0"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@soybeanjs/cli": "^1.7.2",
64
64
  "@soybeanjs/eslint-config-vue": "^0.0.2",
65
65
  "@soybeanjs/unocss-preset": "^0.2.0",
66
66
  "@soybeanjs/unocss-shadcn": "^0.4.0",
67
- "@types/node": "^25.6.2",
67
+ "@types/node": "^25.7.0",
68
68
  "@unocss/cli": "^66.6.8",
69
69
  "@vitejs/plugin-vue": "^6.0.6",
70
70
  "@vitejs/plugin-vue-jsx": "^5.1.5",
71
- "@vitest/coverage-v8": "^4.1.5",
71
+ "@vitest/coverage-v8": "^4.1.6",
72
72
  "@vue/test-utils": "^2.4.10",
73
73
  "axe-core": "^4.11.4",
74
74
  "eslint": "^10.3.0",
75
75
  "happy-dom": "^20.9.0",
76
76
  "lint-staged": "^17.0.4",
77
- "oxfmt": "^0.48.0",
78
- "oxlint": "^1.63.0",
77
+ "oxfmt": "^0.49.0",
78
+ "oxlint": "^1.64.0",
79
79
  "simple-git-hooks": "^2.13.1",
80
80
  "tsdown": "0.22.0",
81
81
  "tsx": "^4.21.0",
@@ -90,7 +90,7 @@
90
90
  "vite": "^8.0.12",
91
91
  "vite-plugin-vue-devtools": "^8.1.2",
92
92
  "vite-plugin-vue-meta-layouts": "^0.6.1",
93
- "vitest": "^4.1.5",
93
+ "vitest": "^4.1.6",
94
94
  "vue": "^3.5.34",
95
95
  "vue-router": "^5.0.6",
96
96
  "vue-tsc": "^3.2.8"
@@ -120,13 +120,13 @@
120
120
  "gen:changelog": "tsx scripts/changelog.ts && tsx scripts/changelog-i18n.ts && oxfmt docs/src/generated/changelog/ docs/src/generated/changelog-locales/",
121
121
  "translate:api:i18n": "tsx scripts/api-i18n-translate.ts",
122
122
  "translate:changelog:i18n": "tsx scripts/changelog-i18n-translate.ts",
123
+ "translate:locale": "tsx scripts/locale-translate.ts",
123
124
  "gen:headless": "tsx scripts/headless.ts && oxfmt headless/src/constants/components.ts headless/src/namespaced/index.ts",
124
125
  "gen:ui": "tsx scripts/ui.ts && oxfmt src/constants/components.ts",
125
126
  "preview": "vite preview",
126
127
  "preview:docs": "pnpm --filter @soybeanjs/ui-docs preview",
127
128
  "publish-pkg": "pnpm publish -r --access public",
128
129
  "release": "soy release",
129
- "stub": "tsx scripts/stub.ts",
130
130
  "test": "vitest run",
131
131
  "test:coverage": "vitest run --coverage",
132
132
  "test:ui": "vitest --ui",