cabloy 5.1.247 → 5.1.249

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.
Files changed (50) hide show
  1. package/.cabloy-version +1 -1
  2. package/.vscode/settings.json +1 -2
  3. package/CHANGELOG.md +27 -0
  4. package/package.json +1 -7
  5. package/repo-docs/frontend/column-configuration-guide.md +3 -1
  6. package/repo-docs/frontend/table-guide.md +27 -2
  7. package/repo-e2e/specs/a-commerce.spec.ts +32 -0
  8. package/repo-e2e/specs/cabloy-basic.spec.ts +1236 -0
  9. package/vona/package.original.json +1 -7
  10. package/vona/pnpm-lock.yaml +139 -1011
  11. package/zova/package.original.json +0 -6
  12. package/zova/packages-cli/cli/package.json +2 -2
  13. package/zova/packages-cli/cli-set-front/package.json +2 -2
  14. package/zova/packages-utils/zova-vite/package.json +1 -1
  15. package/zova/packages-utils/zova-vite/src/teleports.ts +7 -0
  16. package/zova/packages-utils/zova-vite/test/teleports.test.ts +114 -0
  17. package/zova/packages-zova/zova/package.json +2 -2
  18. package/zova/pnpm-lock.yaml +6 -958
  19. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx +267 -0
  20. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.header.tsx +42 -10
  21. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.locale.tsx +10 -5
  22. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx +97 -11
  23. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.theme.tsx +19 -10
  24. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.user.tsx +11 -0
  25. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/style.ts +128 -0
  26. package/zova/src/suite/a-home/modules/home-layoutadmin/src/config/locale/en-us.ts +6 -0
  27. package/zova/src/suite/a-home/modules/home-layoutadmin/src/config/locale/zh-cn.ts +6 -0
  28. package/zova/src/suite/a-home/modules/home-layoutweb/src/.metadata/component/layoutWeb.ts +10 -8
  29. package/zova/src/suite/a-home/modules/home-layoutweb/src/.metadata/index.ts +25 -0
  30. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx +83 -0
  31. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.header.tsx +37 -8
  32. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.locale.tsx +25 -8
  33. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.tabs.tsx +3 -1
  34. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.theme.tsx +10 -5
  35. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.user.tsx +64 -21
  36. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/style.ts +42 -0
  37. package/zova/src/suite/a-home/modules/home-layoutweb/src/config/locale/en-us.ts +4 -0
  38. package/zova/src/suite/a-home/modules/home-layoutweb/src/config/locale/zh-cn.ts +4 -0
  39. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/rowOperations/controller.tsx +2 -1
  40. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/controller.tsx +6 -10
  41. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +9 -6
  42. package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
  43. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +17 -1
  44. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssrProfile.ts +15 -0
  45. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/types/ssr.ts +1 -0
  46. package/zova/src/suite-vendor/a-zova/modules/a-ssr/test/lib/ssrProfile.test.ts +23 -1
  47. package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/package.json +1 -1
  48. package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/src/service/ssrHandler.ts +11 -11
  49. package/zova/src/suite-vendor/a-zova/package.json +3 -3
  50. package/vona/.textlintrc.json +0 -19
@@ -4,8 +4,136 @@ import { Style } from 'zova-module-a-bean';
4
4
  @Style()
5
5
  export class StyleLayoutAdmin extends BeanStyleBase {
6
6
  cTab: string;
7
+ cHeaderActions: string;
8
+ cHeader: string;
9
+ cTabItems: string;
10
+ cTabItemsMenu: string;
7
11
 
8
12
  protected async __init__() {
13
+ this.cHeader = this.$style({
14
+ $nest: {
15
+ '& .header-title': { minWidth: 0 },
16
+ '&:has(.tab-items) .header-title': {
17
+ flex: '0 1 auto',
18
+ maxWidth: '10rem',
19
+ overflow: 'hidden',
20
+ textOverflow: 'ellipsis',
21
+ whiteSpace: 'nowrap',
22
+ },
23
+ '&:has(.tab-items) .header-navigation': { marginInline: 0, paddingInline: 0 },
24
+ '@media (width < 600px)': {
25
+ $nest: { '&:has(.tab-items) .header-title': { display: 'none' } },
26
+ },
27
+ },
28
+ });
29
+ this.cTabItems = this.$style({
30
+ display: 'flex',
31
+ alignItems: 'center',
32
+ minWidth: 0,
33
+ height: '48px',
34
+ $nest: {
35
+ '& .tab-items-viewport': { position: 'relative', minWidth: 0, flex: '1 1 0' },
36
+ '& .tab-items-scroll': { overflowX: 'auto', scrollbarWidth: 'none' },
37
+ '& .tab-items-scroll::-webkit-scrollbar': { display: 'none' },
38
+ '& .tab-items-content': {
39
+ display: 'flex',
40
+ flexWrap: 'nowrap',
41
+ width: 'max-content',
42
+ minWidth: '100%',
43
+ },
44
+ '& .tab': { flexShrink: 0, height: '48px', gap: '4px' },
45
+ '& .tab > svg': { flexShrink: 0 },
46
+ '& .tab > div': { minWidth: 0 },
47
+ '& .tab .tab-close': { top: '2px', right: '2px' },
48
+ '& .tab-items-more': { flex: '0 0 40px', width: '40px', height: '40px', fontSize: '20px' },
49
+ '&.is-compact .tab-items-content': { width: '100%' },
50
+ '&.is-compact .tab': { minWidth: 0, maxWidth: '100%', paddingInline: '12px' },
51
+ '&.is-compact .tab-close': { display: 'none' },
52
+ '& .tab-items-viewport::before, & .tab-items-viewport::after': {
53
+ content: '""',
54
+ position: 'absolute',
55
+ top: 0,
56
+ bottom: 0,
57
+ width: '16px',
58
+ zIndex: 1,
59
+ pointerEvents: 'none',
60
+ opacity: 0,
61
+ },
62
+ '& .tab-items-viewport::before': {
63
+ left: 0,
64
+ background: 'linear-gradient(to right, var(--color-base-300), transparent)',
65
+ },
66
+ '& .tab-items-viewport::after': {
67
+ right: 0,
68
+ background: 'linear-gradient(to left, var(--color-base-300), transparent)',
69
+ },
70
+ '& .tab-items-viewport.has-start::before, & .tab-items-viewport.has-end::after': {
71
+ opacity: 1,
72
+ },
73
+ },
74
+ });
75
+ this.cTabItemsMenu = this.$style({
76
+ display: 'none',
77
+ position: 'fixed',
78
+ inset: 'auto',
79
+ margin: 0,
80
+ width: '22rem',
81
+ maxWidth: 'calc(100vw - 16px)',
82
+ maxHeight: 'calc(100dvh - 16px)',
83
+ overflowY: 'auto',
84
+ $nest: {
85
+ '&:popover-open': { display: 'flex', flexDirection: 'column', gap: '4px' },
86
+ '& .tab-items-select': {
87
+ flex: '1 1 0',
88
+ minWidth: 0,
89
+ height: 'auto',
90
+ minHeight: '40px',
91
+ justifyContent: 'flex-start',
92
+ textTransform: 'none',
93
+ paddingBlock: '8px',
94
+ },
95
+ '& .tab-items-select svg': { flexShrink: 0 },
96
+ '& .tab-items-label': {
97
+ flex: '1 1 0',
98
+ minWidth: 0,
99
+ whiteSpace: 'normal',
100
+ overflowWrap: 'anywhere',
101
+ textAlign: 'start',
102
+ },
103
+ },
104
+ });
105
+ this.cHeaderActions = this.$style({
106
+ $nest: {
107
+ '& .header-actions-panel > ul > li > details > ul': {
108
+ maxHeight: 'calc(100dvh - 5rem)',
109
+ overflowY: 'auto',
110
+ },
111
+ '@media (width < 64rem)': {
112
+ $nest: {
113
+ '& .header-actions-panel': {
114
+ width: '16rem',
115
+ maxWidth: 'calc(100vw - 1rem)',
116
+ maxHeight: 'calc(100dvh - 5rem)',
117
+ overflowY: 'auto',
118
+ },
119
+ '& .header-actions-panel > ul > li > details > ul': {
120
+ width: 'auto',
121
+ maxWidth: '100%',
122
+ whiteSpace: 'normal',
123
+ overflowWrap: 'anywhere',
124
+ },
125
+ },
126
+ },
127
+ '@media (min-width: 64rem)': {
128
+ $nest: {
129
+ '& .header-actions-panel > ul > li > details > ul': {
130
+ right: 0,
131
+ maxWidth: 'calc(100vw - 1rem)',
132
+ },
133
+ },
134
+ },
135
+ },
136
+ });
9
137
  this.cTab = this.$style({
10
138
  fontSize: '1.0rem',
11
139
  $nest: {
@@ -1,5 +1,11 @@
1
1
  export default {
2
2
  'Home': 'Home',
3
+ 'MoreActions': 'More actions',
4
+ 'AllTabs': 'All tabs',
5
+ 'CloseTab': 'Close tab: %s',
6
+ 'Language': 'Language',
7
+ 'ColorMode': 'Color mode',
8
+ 'Theme': 'Theme',
3
9
  'Logout': 'Logout',
4
10
  'Account Settings': 'Account Settings',
5
11
  'LanguageEnglish': 'English',
@@ -1,5 +1,11 @@
1
1
  export default {
2
2
  'Home': '首页',
3
+ 'MoreActions': '更多操作',
4
+ 'AllTabs': '全部页签',
5
+ 'CloseTab': '关闭页签:%s',
6
+ 'Language': '语言',
7
+ 'ColorMode': '明暗模式',
8
+ 'Theme': '主题',
3
9
  'Logout': '退出登录',
4
10
  'Account Settings': '账户设置',
5
11
  'LanguageEnglish': '英语',
@@ -7,6 +7,7 @@ import type { ControllerLayoutWebProps } from '../../component/layoutWeb/control
7
7
 
8
8
  import { ControllerLayoutWeb } from '../../component/layoutWeb/controller.tsx';
9
9
  import { RenderLayoutWeb } from '../../component/layoutWeb/render.tsx';
10
+ import { StyleLayoutWeb } from '../../component/layoutWeb/style.ts';
10
11
  export type ZLayoutWebProps = {
11
12
  controllerRef?: (ref: ControllerLayoutWeb) => void;
12
13
  } & ControllerLayoutWebProps;
@@ -21,16 +22,17 @@ declare module 'zova-module-home-layoutweb' {
21
22
  }
22
23
  }
23
24
  declare module 'zova-module-home-layoutweb' {
24
- export interface RenderLayoutWeb extends ControllerLayoutWeb {}
25
- export interface RenderContent extends ControllerLayoutWeb {}
26
- export interface RenderHeader extends ControllerLayoutWeb {}
27
- export interface RenderLocale extends ControllerLayoutWeb {}
28
- export interface RenderTabs extends ControllerLayoutWeb {}
29
- export interface RenderTheme extends ControllerLayoutWeb {}
30
- export interface RenderUser extends ControllerLayoutWeb {}
25
+ export interface StyleLayoutWeb extends ControllerLayoutWeb {}
26
+ export interface RenderLayoutWeb extends StyleLayoutWeb {}
27
+ export interface RenderContent extends StyleLayoutWeb {}
28
+ export interface RenderHeader extends StyleLayoutWeb {}
29
+ export interface RenderLocale extends StyleLayoutWeb {}
30
+ export interface RenderTabs extends StyleLayoutWeb {}
31
+ export interface RenderTheme extends StyleLayoutWeb {}
32
+ export interface RenderUser extends StyleLayoutWeb {}
31
33
  }
32
34
  export const ZLayoutWeb = defineComponent((_props: ZLayoutWebProps) => {
33
- useController(ControllerLayoutWeb, RenderLayoutWeb, undefined);
35
+ useController(ControllerLayoutWeb, RenderLayoutWeb, StyleLayoutWeb);
34
36
  return () => {};
35
37
  }, prepareComponentOptions());
36
38
  declare module 'zova-module-a-bean' {
@@ -165,6 +165,31 @@ declare module 'zova' {
165
165
  }
166
166
  }
167
167
  /** render: end */
168
+ /** style: begin */
169
+ export * from '../component/layoutWeb/style.ts';
170
+
171
+ import 'zova';
172
+ declare module 'zova' {
173
+
174
+
175
+ }
176
+ declare module 'zova-module-home-layoutweb' {
177
+
178
+ export interface StyleLayoutWeb {
179
+ /** @internal */
180
+ get scope(): ScopeModuleHomeLayoutweb;
181
+ }
182
+ }
183
+ /** style: end */
184
+ /** style: begin */
185
+ import type { StyleLayoutWeb } from '../component/layoutWeb/style.ts';
186
+ import 'zova';
187
+ declare module 'zova' {
188
+ export interface IBeanRecordLocal {
189
+ 'home-layoutweb.style.layoutWeb': StyleLayoutWeb;
190
+ }
191
+ }
192
+ /** style: end */
168
193
  /** config: begin */
169
194
  export * from '../config/config.ts';
170
195
  import { config } from '../config/config.ts';
@@ -39,14 +39,28 @@ export class ControllerLayoutWeb extends BeanControllerBase {
39
39
  private viewportWidth: number = 0;
40
40
  private _windowResizeHandler?: () => void;
41
41
 
42
+ headerActionsOpen = false;
43
+ headerActionsId = 'home-layoutweb-header-actions';
44
+ private _headerActionsElement?: HTMLDivElement;
45
+ private _headerOutsideHandler: (event: PointerEvent) => void;
46
+ private _headerClickHandler: (event: MouseEvent) => void;
47
+
42
48
  protected async __init__() {
49
+ this._headerClickHandler = event => this.onHeaderActionsClick(event);
50
+ this._headerOutsideHandler = event => {
51
+ if (event.target instanceof Node && !this._headerActionsElement?.contains(event.target)) {
52
+ this.closeHeaderActions();
53
+ }
54
+ };
43
55
  // viewport
44
56
  if (process.env.CLIENT) {
45
57
  this._windowResizeHandler = () => {
46
58
  this.viewportWidth = document.documentElement.clientWidth;
59
+ this.closeHeaderActions();
47
60
  };
48
61
  this._windowResizeHandler();
49
62
  window.addEventListener('resize', this._windowResizeHandler);
63
+ document.addEventListener('pointerdown', this._headerOutsideHandler);
50
64
  }
51
65
  // belowBreakpoint
52
66
  this.belowBreakpoint = this.$computed(() => {
@@ -78,8 +92,77 @@ export class ControllerLayoutWeb extends BeanControllerBase {
78
92
  }
79
93
 
80
94
  protected __dispose__() {
95
+ this.setHeaderActionsElement();
81
96
  if (this._windowResizeHandler) {
82
97
  window.removeEventListener('resize', this._windowResizeHandler);
98
+ document.removeEventListener('pointerdown', this._headerOutsideHandler);
99
+ }
100
+ }
101
+
102
+ setHeaderActionsElement(element?: HTMLDivElement) {
103
+ if (element === this._headerActionsElement) return;
104
+ this._headerActionsElement?.removeEventListener('click', this._headerClickHandler, true);
105
+ this._headerActionsElement = element;
106
+ element?.addEventListener('click', this._headerClickHandler, true);
107
+ }
108
+
109
+ toggleHeaderActions() {
110
+ if (this.headerActionsOpen) {
111
+ this.closeHeaderActions();
112
+ } else {
113
+ this.headerActionsOpen = true;
114
+ }
115
+ }
116
+
117
+ closeHeaderActions(restoreFocus = false) {
118
+ this.headerActionsOpen = false;
119
+ this._headerActionsElement
120
+ ?.querySelectorAll<HTMLDetailsElement>('details[open]')
121
+ .forEach(details => {
122
+ details.open = false;
123
+ });
124
+ if (restoreFocus) {
125
+ this._headerActionsElement?.querySelector<HTMLButtonElement>('button')?.focus();
126
+ }
127
+ }
128
+
129
+ onHeaderActionsFocusout(event: FocusEvent) {
130
+ if (
131
+ this.headerActionsOpen &&
132
+ (!(event.relatedTarget instanceof Node) ||
133
+ !this._headerActionsElement?.contains(event.relatedTarget))
134
+ ) {
135
+ this.closeHeaderActions();
136
+ }
137
+ }
138
+
139
+ onHeaderActionsClick(event: MouseEvent) {
140
+ if (!this.headerActionsOpen || !(event.target instanceof Element)) return;
141
+ const action = event.target.closest('a, button');
142
+ if (action?.closest('details') && !action.matches(':disabled, [aria-disabled="true"]')) {
143
+ this.closeHeaderActions(true);
144
+ }
145
+ }
146
+
147
+ onHeaderActionsKeydown(event: KeyboardEvent) {
148
+ if (
149
+ event.key !== 'Escape' ||
150
+ event.defaultPrevented ||
151
+ !(event.target instanceof Element) ||
152
+ event.target.closest('dialog[open], [role="dialog"]')
153
+ ) {
154
+ return;
155
+ }
156
+ const details = event.target.closest<HTMLDetailsElement>('details[open]');
157
+ if (details) {
158
+ event.preventDefault();
159
+ event.stopPropagation();
160
+ details.open = false;
161
+ details.querySelector<HTMLElement>('summary')?.focus();
162
+ } else if (this.headerActionsOpen) {
163
+ event.preventDefault();
164
+ event.stopPropagation();
165
+ this.closeHeaderActions(true);
83
166
  }
84
167
  }
85
168
 
@@ -1,3 +1,4 @@
1
+ import { classes } from 'typestyle';
1
2
  import { BeanRenderBase, Use } from 'zova';
2
3
  import { Render } from 'zova-module-a-bean';
3
4
  import { ZIcon } from 'zova-module-a-icon';
@@ -12,7 +13,7 @@ export class RenderHeader extends BeanRenderBase {
12
13
  public render() {
13
14
  return (
14
15
  <>
15
- <div class="navbar bg-base-300 w-full">
16
+ <div class="navbar bg-base-300 w-full flex-wrap lg:flex-nowrap">
16
17
  <div class="flex-none">
17
18
  <button
18
19
  class="btn btn-square btn-ghost"
@@ -24,13 +25,41 @@ export class RenderHeader extends BeanRenderBase {
24
25
  </button>
25
26
  </div>
26
27
  <div class="text-xl px-4">{this.sys.env.APP_TITLE}</div>
27
- <div class="mx-2 min-w-0 flex-1 px-2">{this.$$r.$$renderTabs.renderTabs()}</div>
28
- <div class="hidden flex-none lg:block">
29
- <ul class="menu menu-horizontal">
30
- {this.$$r.$$renderLocale.render()}
31
- {this.$$r.$$renderTheme.renderThemeDark()}
32
- {this.$$r.$$renderUser.render()}
33
- </ul>
28
+ <div class="order-last mx-2 min-w-0 basis-full px-2 lg:order-none lg:basis-auto lg:flex-1">
29
+ {this.$$r.$$renderTabs.renderTabs()}
30
+ </div>
31
+ <div
32
+ ref={ref => this.setHeaderActionsElement?.((ref as HTMLDivElement | null) ?? undefined)}
33
+ class={classes('relative ml-auto flex-none lg:ml-0', this.cHeaderActions)}
34
+ data-testid="header-actions"
35
+ onFocusout={event => this.onHeaderActionsFocusout?.(event)}
36
+ onKeydown={event => this.onHeaderActionsKeydown(event)}
37
+ >
38
+ <button
39
+ type="button"
40
+ class="btn btn-square btn-ghost lg:hidden"
41
+ aria-label={this.scope.locale.MoreActions()}
42
+ title={this.scope.locale.MoreActions()}
43
+ aria-expanded={this.headerActionsOpen}
44
+ aria-controls={this.headerActionsId}
45
+ onClick={() => this.toggleHeaderActions()}
46
+ >
47
+ <ZIcon name="::more-horiz" width={24} aria-hidden="true" />
48
+ </button>
49
+ <div
50
+ id={this.headerActionsId}
51
+ class={classes(
52
+ 'header-actions-panel absolute right-0 top-full z-30 rounded-box bg-base-100 shadow-lg lg:static lg:bg-transparent lg:shadow-none',
53
+ this.headerActionsOpen ? 'block' : 'hidden',
54
+ 'lg:block',
55
+ )}
56
+ >
57
+ <ul class="menu w-full lg:w-auto lg:menu-horizontal">
58
+ {this.$$r.$$renderLocale.render()}
59
+ {this.$$r.$$renderTheme.renderThemeDark()}
60
+ {this.$$r.$$renderUser.render()}
61
+ </ul>
62
+ </div>
34
63
  </div>
35
64
  </div>
36
65
  </>
@@ -1,6 +1,7 @@
1
1
  import { BeanRenderBase, ClientOnly } from 'zova';
2
2
  import { Render } from 'zova-module-a-bean';
3
3
  import { $icon } from 'zova-module-a-icon';
4
+ import { ZButton } from 'zova-module-basic-button';
4
5
  import { closeNearestDetails } from 'zova-module-home-base';
5
6
 
6
7
  @Render()
@@ -12,29 +13,45 @@ export class RenderLocale extends BeanRenderBase {
12
13
  return (
13
14
  <li>
14
15
  <details>
15
- <summary>{$icon('::language', 24)}</summary>
16
+ <summary aria-label={this.scope.locale.Language()} title={this.scope.locale.Language()}>
17
+ {$icon('::language', 24)}
18
+ <span class="lg:hidden">{this.scope.locale.Language()}</span>
19
+ </summary>
16
20
  <ClientOnly>
17
21
  <ul class="bg-base-100 rounded-t-none p-2 w-48">
18
22
  {Object.keys(locales).map(key => {
19
23
  const title = this.$scopeBase.locale[locales[key]]();
20
24
  return (
21
25
  <li key={key} class={this.app.meta.locale.current === key ? 'disabled' : ''}>
22
- <a
23
- onClick={event => {
26
+ <ZButton
27
+ disabled={this.app.meta.locale.current === key}
28
+ onPerform={async event => {
29
+ closeNearestDetails({ currentTarget: event.target });
24
30
  if (metaLocale) {
25
- this.app.$gotoPage(currentRoute.matched[0].path, {
26
- params: { ...currentRoute.params, locale: key },
31
+ const options = {
32
+ params: {
33
+ ...currentRoute.params,
34
+ locale: key === this.sys.config.locale.default ? '' : key,
35
+ },
27
36
  query: currentRoute.query,
28
- });
37
+ };
38
+ const name = this.$router.getRealRouteName(currentRoute.name);
39
+ const alias =
40
+ name && this.$router.getAliasPath(name as never, options as never);
41
+ // A root alias with only a query must not resolve against the current path.
42
+ const target = alias?.startsWith('?') ? `/${alias}` : alias;
43
+ await this.app.$gotoPage(
44
+ target || currentRoute.matched[0].path,
45
+ target ? undefined : options,
46
+ );
29
47
  } else {
30
48
  this.app.meta.locale.current = key as any;
31
49
  }
32
- closeNearestDetails(event);
33
50
  }}
34
51
  >
35
52
  {$icon(this.app.meta.locale.current === key ? '::done' : '::none', 24)}
36
53
  {title}
37
- </a>
54
+ </ZButton>
38
55
  </li>
39
56
  );
40
57
  })}
@@ -20,7 +20,9 @@ export class RenderTabs extends BeanRenderBase {
20
20
  return this._renderMenuItem(tab.info as TypeMenuItem, true, tab.tabKey);
21
21
  });
22
22
  const domWrapper = (
23
- <ul class="menu menu-horizontal w-max min-w-full flex-nowrap gap-1 px-0">{domTabs}</ul>
23
+ <ul class="menu menu-horizontal w-full flex-wrap gap-1 px-0 lg:w-max lg:min-w-full lg:flex-nowrap">
24
+ {domTabs}
25
+ </ul>
24
26
  );
25
27
  if (!$$modelTabs.cache) return domWrapper;
26
28
  return <ClientOnly>{domWrapper}</ClientOnly>;
@@ -3,6 +3,7 @@ import type { IThemeRecord } from 'zova-module-a-style';
3
3
  import { BeanRenderBase, ClientOnly } from 'zova';
4
4
  import { Render } from 'zova-module-a-bean';
5
5
  import { $icon } from 'zova-module-a-icon';
6
+ import { ZButton } from 'zova-module-basic-button';
6
7
  import { closeNearestDetails } from 'zova-module-home-base';
7
8
 
8
9
  @Render()
@@ -25,7 +26,10 @@ export class RenderTheme extends BeanRenderBase {
25
26
  return (
26
27
  <li>
27
28
  <details>
28
- <summary>{$icon('::dark-theme', 24)}</summary>
29
+ <summary aria-label={this.scope.locale.ColorMode()} title={this.scope.locale.ColorMode()}>
30
+ {$icon('::dark-theme', 24)}
31
+ <span class="lg:hidden">{this.scope.locale.ColorMode()}</span>
32
+ </summary>
29
33
  <ClientOnly>
30
34
  <ul class="bg-base-100 rounded-t-none p-2 w-48">
31
35
  {themes.map(item => {
@@ -34,15 +38,16 @@ export class RenderTheme extends BeanRenderBase {
34
38
  key={item.mode.toString()}
35
39
  class={this.$theme.darkMode === item.mode ? 'disabled' : ''}
36
40
  >
37
- <a
38
- onClick={event => {
41
+ <ZButton
42
+ disabled={this.$theme.darkMode === item.mode}
43
+ onPerform={event => {
39
44
  this.$theme.darkMode = item.mode as any;
40
- closeNearestDetails(event);
45
+ closeNearestDetails({ currentTarget: event.target });
41
46
  }}
42
47
  >
43
48
  {$icon(this.$theme.darkMode === item.mode ? '::done' : '::none', 24)}
44
49
  {item.title}
45
- </a>
50
+ </ZButton>
46
51
  </li>
47
52
  );
48
53
  })}
@@ -8,30 +8,62 @@ import { closeNearestDetails } from 'zova-module-home-base';
8
8
  export class RenderUser extends BeanRenderBase {
9
9
  public render() {
10
10
  return (
11
- <ClientOnly>
12
- <li>
13
- <details>
14
- <summary>
15
- <img
16
- class="h-6 w-6 rounded-full object-cover"
17
- alt="avatar"
18
- src={
19
- resolveImagePreviewUrl(
20
- this.$passport.user?.avatar,
21
- this.sys.config.api.baseURL,
22
- ) || this.$scopeBase.config.avatar.empty
23
- }
11
+ <li class="min-w-0">
12
+ <details>
13
+ <summary aria-label={this.scope.locale.Account()} title={this.scope.locale.Account()}>
14
+ <ClientOnly
15
+ v-slots={{
16
+ placeholder: () => (
17
+ <img
18
+ class="h-6 w-6 shrink-0 rounded-full object-cover"
19
+ alt=""
20
+ src={this.$scopeBase.config.avatar.empty}
21
+ />
22
+ ),
23
+ default: () => (
24
+ <img
25
+ class="h-6 w-6 shrink-0 rounded-full object-cover"
26
+ alt=""
27
+ src={
28
+ resolveImagePreviewUrl(
29
+ this.$passport.user?.avatar,
30
+ this.sys.config.api.baseURL,
31
+ ) || this.$scopeBase.config.avatar.empty
32
+ }
33
+ />
34
+ ),
35
+ }}
36
+ />
37
+ <span class="h-lh min-w-0 flex-1 truncate lg:w-32 lg:flex-none">
38
+ <ClientOnly
39
+ v-slots={{
40
+ placeholder: () => '',
41
+ default: () =>
42
+ this.$passport.isAuthenticated
43
+ ? this.$passport.displayName
44
+ : this.scope.locale.NotLoggedIn(),
45
+ }}
24
46
  />
25
- {this.$passport.isAuthenticated
26
- ? this.$passport.displayName
27
- : this.scope.locale.NotLoggedIn()}
28
- </summary>
47
+ </span>
48
+ </summary>
49
+ <ClientOnly>
29
50
  <ul class="bg-base-100 rounded-t-none p-2 w-40">
30
51
  {this.$passport.isAuthenticated ? (
31
52
  <>
32
53
  <li>
33
54
  <a
55
+ href={this.$router.getPagePath('/home/user/account')}
34
56
  onClick={event => {
57
+ if (
58
+ event.button !== 0 ||
59
+ event.metaKey ||
60
+ event.ctrlKey ||
61
+ event.shiftKey ||
62
+ event.altKey
63
+ ) {
64
+ return;
65
+ }
66
+ event.preventDefault();
35
67
  this.app.$gotoPage('/home/user/account');
36
68
  closeNearestDetails(event);
37
69
  }}
@@ -42,7 +74,7 @@ export class RenderUser extends BeanRenderBase {
42
74
  <li>
43
75
  <ZButton
44
76
  onPerform={async event => {
45
- closeNearestDetails(event);
77
+ closeNearestDetails({ currentTarget: event.target });
46
78
  await this.$passport.logout().mutateAsync();
47
79
  }}
48
80
  >
@@ -53,7 +85,18 @@ export class RenderUser extends BeanRenderBase {
53
85
  ) : (
54
86
  <li>
55
87
  <a
88
+ href={this.app.$getPagePathLogin(true)}
56
89
  onClick={event => {
90
+ if (
91
+ event.button !== 0 ||
92
+ event.metaKey ||
93
+ event.ctrlKey ||
94
+ event.shiftKey ||
95
+ event.altKey
96
+ ) {
97
+ return;
98
+ }
99
+ event.preventDefault();
57
100
  this.app.$gotoLogin(true);
58
101
  closeNearestDetails(event);
59
102
  }}
@@ -63,9 +106,9 @@ export class RenderUser extends BeanRenderBase {
63
106
  </li>
64
107
  )}
65
108
  </ul>
66
- </details>
67
- </li>
68
- </ClientOnly>
109
+ </ClientOnly>
110
+ </details>
111
+ </li>
69
112
  );
70
113
  }
71
114
  }