akanjs 2.4.2-rc.2 → 3.0.0-alpha.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.
Files changed (170) hide show
  1. package/base/baseEnv.ts +0 -6
  2. package/client/cn.ts +52 -0
  3. package/client/csrTypes.ts +2 -2
  4. package/client/frameConfig.ts +2 -2
  5. package/client/index.ts +1 -0
  6. package/client/types.ts +0 -4
  7. package/common/fileUpload.ts +1 -1
  8. package/common/index.ts +1 -0
  9. package/common/isThenable.ts +12 -0
  10. package/constant/purify.ts +9 -10
  11. package/fetch/client/fetchClient.ts +1 -1
  12. package/fetch/client/httpClient.ts +9 -5
  13. package/fetch/fetchType/sliceFetch.type.ts +1 -1
  14. package/package.json +4 -7
  15. package/server/akanApp.ts +2 -0
  16. package/server/systemPageDocument.tsx +12 -12
  17. package/signal/endpointInfo.ts +2 -1
  18. package/signal/slice.ts +1 -1
  19. package/signal/sliceInfo.ts +2 -1
  20. package/store/action.ts +18 -2
  21. package/store/baseSt.ts +2 -2
  22. package/store/state.ts +9 -1
  23. package/store/storeInstance.ts +2 -0
  24. package/test/sampleOf.ts +3 -3
  25. package/test/signalTestRuntime.ts +0 -2
  26. package/types/base/baseEnv.d.ts +0 -2
  27. package/types/client/cn.d.ts +12 -0
  28. package/types/client/index.d.ts +1 -0
  29. package/types/client/types.d.ts +0 -3
  30. package/types/common/fileUpload.d.ts +1 -1
  31. package/types/common/index.d.ts +1 -0
  32. package/types/common/isThenable.d.ts +9 -0
  33. package/types/constant/purify.d.ts +3 -2
  34. package/types/fetch/fetchType/sliceFetch.type.d.ts +1 -1
  35. package/types/signal/endpointInfo.d.ts +2 -2
  36. package/types/signal/slice.d.ts +1 -1
  37. package/types/signal/sliceInfo.d.ts +2 -2
  38. package/types/store/action.d.ts +1 -1
  39. package/types/store/state.d.ts +4 -2
  40. package/types/test/sampleOf.d.ts +2 -2
  41. package/types/ui/Badge.d.ts +6 -0
  42. package/types/ui/Button.d.ts +26 -6
  43. package/types/ui/Load/Units.d.ts +3 -1
  44. package/types/ui/RecentTime.d.ts +1 -1
  45. package/types/ui/Signal/Collapse.d.ts +12 -0
  46. package/types/ui/Signal/style.d.ts +5 -6
  47. package/types/ui/Switch.d.ts +14 -0
  48. package/types/ui/Tooltip.d.ts +11 -0
  49. package/types/ui/UiOverride/Provider.d.ts +6 -5
  50. package/types/ui/UiOverride/context.d.ts +22 -1
  51. package/types/ui/UiOverride/index.d.ts +2 -1
  52. package/types/ui/UiOverride/override.d.ts +7 -6
  53. package/types/ui/UiOverride/useUiRecipe.d.ts +8 -0
  54. package/types/ui/index.d.ts +5 -1
  55. package/types/ui/recipe/badgeRecipe.d.ts +13 -0
  56. package/types/ui/recipe/buttonRecipe.d.ts +15 -0
  57. package/types/ui/recipe/factory.d.ts +24 -0
  58. package/types/ui/recipe/index.d.ts +18 -0
  59. package/types/ui/recipe/inputRecipe.d.ts +13 -0
  60. package/types/ui/recipe.d.ts +1 -0
  61. package/ui/Badge.tsx +17 -0
  62. package/ui/BottomSheet.tsx +8 -10
  63. package/ui/Button.tsx +158 -48
  64. package/ui/Clipboard.tsx +3 -3
  65. package/ui/Constant/Doc.tsx +108 -65
  66. package/ui/Constant/Mermaid.tsx +9 -9
  67. package/ui/Data/CardList.tsx +3 -3
  68. package/ui/Data/Dashboard.tsx +22 -13
  69. package/ui/Data/Insight.tsx +6 -6
  70. package/ui/Data/Item.tsx +49 -34
  71. package/ui/Data/ListContainer.tsx +11 -7
  72. package/ui/Data/Pagination.tsx +2 -2
  73. package/ui/DatePicker.tsx +5 -5
  74. package/ui/Dialog/Content.tsx +2 -2
  75. package/ui/Dialog/Modal.tsx +12 -8
  76. package/ui/Dialog/Provider.tsx +2 -2
  77. package/ui/DragAction.tsx +2 -2
  78. package/ui/DraggableList.tsx +10 -5
  79. package/ui/Dropdown.tsx +33 -26
  80. package/ui/Empty.tsx +5 -5
  81. package/ui/Field.tsx +82 -70
  82. package/ui/Image.tsx +2 -2
  83. package/ui/Input.tsx +62 -60
  84. package/ui/Layout/BottomInset.tsx +2 -2
  85. package/ui/Layout/BottomTab.tsx +5 -5
  86. package/ui/Layout/Header.tsx +3 -3
  87. package/ui/Layout/LeftSider.tsx +6 -5
  88. package/ui/Layout/Navbar.tsx +2 -12
  89. package/ui/Layout/RightSider.tsx +10 -8
  90. package/ui/Layout/Sider.tsx +5 -4
  91. package/ui/Layout/Template.tsx +2 -2
  92. package/ui/Layout/TopInset.tsx +2 -2
  93. package/ui/Layout/Unit.tsx +2 -2
  94. package/ui/Layout/View.tsx +2 -2
  95. package/ui/Layout/Zone.tsx +2 -2
  96. package/ui/Link/Back.tsx +2 -2
  97. package/ui/Link/Close.tsx +2 -2
  98. package/ui/Link/CsrLink.tsx +6 -4
  99. package/ui/Link/Lang.tsx +2 -2
  100. package/ui/Link/SsrLink.tsx +6 -5
  101. package/ui/Load/Page.tsx +1 -1
  102. package/ui/Load/Pagination.tsx +2 -2
  103. package/ui/Load/Units.tsx +19 -4
  104. package/ui/Load/View.tsx +2 -2
  105. package/ui/Loading/Area.tsx +4 -2
  106. package/ui/Loading/Button.tsx +2 -2
  107. package/ui/Loading/Input.tsx +2 -2
  108. package/ui/Loading/ProgressBar.tsx +2 -2
  109. package/ui/Loading/Skeleton.tsx +6 -6
  110. package/ui/Loading/Spin.tsx +2 -2
  111. package/ui/Menu.tsx +29 -27
  112. package/ui/Model/Edit.tsx +3 -3
  113. package/ui/Model/EditModal.tsx +5 -5
  114. package/ui/Model/EditWrapper.tsx +2 -2
  115. package/ui/Model/New.tsx +3 -3
  116. package/ui/Model/NewWrapper_Client.tsx +2 -2
  117. package/ui/Model/Remove.tsx +5 -4
  118. package/ui/Model/RemoveWrapper.tsx +2 -2
  119. package/ui/Model/SureToRemove.tsx +7 -6
  120. package/ui/Model/View.tsx +2 -2
  121. package/ui/Model/ViewEditModal.tsx +20 -21
  122. package/ui/Model/ViewWrapper.tsx +2 -2
  123. package/ui/More.tsx +2 -2
  124. package/ui/ObjectId.tsx +9 -3
  125. package/ui/Pagination.tsx +14 -11
  126. package/ui/Popconfirm.tsx +20 -11
  127. package/ui/Radio.tsx +4 -4
  128. package/ui/RecentTime.tsx +30 -83
  129. package/ui/Refresh.tsx +1 -1
  130. package/ui/ScreenNavigator.tsx +2 -2
  131. package/ui/Select.tsx +21 -23
  132. package/ui/Signal/Arg.tsx +21 -18
  133. package/ui/Signal/Collapse.tsx +29 -0
  134. package/ui/Signal/Doc.tsx +27 -22
  135. package/ui/Signal/Listener.tsx +3 -3
  136. package/ui/Signal/Message.tsx +31 -17
  137. package/ui/Signal/Object.tsx +13 -8
  138. package/ui/Signal/PubSub.tsx +26 -16
  139. package/ui/Signal/Request.tsx +3 -2
  140. package/ui/Signal/Response.tsx +5 -4
  141. package/ui/Signal/RestApi.tsx +27 -17
  142. package/ui/Signal/style.ts +16 -16
  143. package/ui/Switch.tsx +62 -0
  144. package/ui/System/CSR.tsx +40 -35
  145. package/ui/System/Client.tsx +2 -2
  146. package/ui/System/DevModeToggle.tsx +10 -12
  147. package/ui/System/Messages.tsx +14 -16
  148. package/ui/System/Reconnect.tsx +7 -7
  149. package/ui/System/SSR.tsx +25 -21
  150. package/ui/System/SelectLanguage.tsx +24 -28
  151. package/ui/System/ThemeToggle.tsx +38 -45
  152. package/ui/Tab/Menu.tsx +20 -18
  153. package/ui/Tab/Panel.tsx +3 -3
  154. package/ui/Tab/Provider.tsx +2 -2
  155. package/ui/Table.tsx +11 -8
  156. package/ui/ToggleSelect.tsx +14 -18
  157. package/ui/Tooltip.tsx +55 -0
  158. package/ui/UiOverride/Provider.tsx +10 -6
  159. package/ui/UiOverride/context.ts +22 -2
  160. package/ui/UiOverride/index.ts +8 -1
  161. package/ui/UiOverride/override.ts +7 -6
  162. package/ui/UiOverride/useUiRecipe.ts +15 -0
  163. package/ui/Unauthorized.tsx +5 -5
  164. package/ui/index.ts +16 -0
  165. package/ui/recipe/badgeRecipe.ts +23 -0
  166. package/ui/recipe/buttonRecipe.ts +32 -0
  167. package/ui/recipe/factory.ts +32 -0
  168. package/ui/recipe/index.ts +18 -0
  169. package/ui/recipe/inputRecipe.ts +13 -0
  170. package/ui/styles.css +150 -21
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { getEnv } from "akanjs/base";
4
- import { clsx, getPathInfo, router, usePage, usePathCtx } from "akanjs/client";
4
+ import { cn, getPathInfo, router, usePage, usePathCtx } from "akanjs/client";
5
5
  import { Logger } from "akanjs/common";
6
6
  import type { SsrLinkProps } from "./types";
7
7
 
@@ -28,16 +28,17 @@ export default function SsrLink({
28
28
  const path = internalPathInfo.path;
29
29
  if (href.startsWith("#")) {
30
30
  return (
31
- <a className={clsx(className, { [activeClassName ?? ""]: currentPath === path })} href={href}>
31
+ <a className={cn(className, currentPath === path && (activeClassName ?? ""))} href={href}>
32
32
  {children}
33
33
  </a>
34
34
  );
35
35
  }
36
36
  return (
37
37
  <a
38
- className={clsx(className, {
39
- [activeClassName ?? ""]: activeExact ? currentPath === path : currentPath.startsWith(path),
40
- })}
38
+ className={cn(
39
+ className,
40
+ (activeExact ? currentPath === path : currentPath.startsWith(path)) && (activeClassName ?? ""),
41
+ )}
41
42
  href={isExternal ? href : href.startsWith("#") ? href : requestHref}
42
43
 
43
44
  {...props}
package/ui/Load/Page.tsx CHANGED
@@ -31,7 +31,7 @@ const Page: <Return>(props: PageProps<Return>) => ReactNode =
31
31
  else {
32
32
 
33
33
  console.error(error);
34
- resolve(<div className="text-red-500">{message}</div>);
34
+ resolve(<div className="text-destructive">{message}</div>);
35
35
  }
36
36
  });
37
37
  });
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { clsx } from "akanjs/client";
2
+ import { cn } from "akanjs/client";
3
3
  import { capitalize, lowerlize } from "akanjs/common";
4
4
  import type { BaseInsight } from "akanjs/constant";
5
5
  import type { ClientInit, ServerInit } from "akanjs/fetch";
@@ -64,7 +64,7 @@ function Render<RefName extends string, Light>({ className, init, scrollToTop }:
64
64
  if (!loaded.current) loaded.current = true;
65
65
 
66
66
  return (
67
- <div className={clsx("mt-4 flex flex-wrap justify-center", className)}>
67
+ <div className={cn("mt-4 flex flex-wrap justify-center", className)}>
68
68
  {insight.count > limit && (
69
69
  <Pagn
70
70
  currentPage={page}
package/ui/Load/Units.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { DataList } from "akanjs/base";
3
- import { clsx } from "akanjs/client";
3
+ import { cn } from "akanjs/client";
4
4
  import { capitalize, isQueryEqual, lowerlize } from "akanjs/common";
5
5
  import type { BaseInsight } from "akanjs/constant";
6
6
  import { ConstantRegistry } from "akanjs/constant";
@@ -28,6 +28,8 @@ interface DefaultProps<L extends { id: string }> {
28
28
  renderList?: (list: DataList<L>) => ReactNode;
29
29
  reverse?: boolean;
30
30
  pagination?: boolean;
31
+ /** Max age in ms of the cached slice data before the client refetches on mount; `0` always refetches. */
32
+ staleTime?: number;
31
33
  }
32
34
 
33
35
  interface UnitsProps<RefName extends string, Light extends { id: string }> extends DefaultProps<Light> {
@@ -60,6 +62,7 @@ function Render<RefName extends string, Light extends { id: string }>({
60
62
  sort = (a, b) => 1,
61
63
  reverse,
62
64
  pagination,
65
+ staleTime,
63
66
  }: RenderProps<RefName, Light>) {
64
67
  const loaded = useRef(false);
65
68
  const storeUse = st.use as { [key: string]: () => unknown };
@@ -75,6 +78,7 @@ function Render<RefName extends string, Light extends { id: string }>({
75
78
  modelInsight: `${modelName}Insight`,
76
79
  modelInitList: `${modelName}InitList`,
77
80
  modelInitAt: `${modelName}InitAt`,
81
+ modelStaleAt: `${modelName}StaleAt`,
78
82
  modelObjList: `${modelName}ObjList`,
79
83
  modelObjInsight: `${modelName}ObjInsight`,
80
84
  pageOfModel: `pageOf${ModelName}`,
@@ -84,12 +88,14 @@ function Render<RefName extends string, Light extends { id: string }>({
84
88
  sortOfModel: `sortOf${ModelName}`,
85
89
  setPageOfModel: `setPageOf${ModelName}`,
86
90
  addPageOfModel: `addPageOf${ModelName}`,
91
+ refreshModel: `refresh${ModelName}`,
87
92
  };
88
93
  const namesOfSlice = {
89
94
  modelList: sliceName.replace(names.model, names.modelList),
90
95
  modelListLoading: sliceName.replace(names.model, names.modelListLoading),
91
96
  modelInitList: sliceName.replace(names.model, names.modelInitList),
92
97
  modelInitAt: sliceName.replace(names.model, names.modelInitAt),
98
+ modelStaleAt: sliceName.replace(names.model, names.modelStaleAt),
93
99
  modelInsight: sliceName.replace(names.model, names.modelInsight),
94
100
  pageOfModel: sliceName.replace(names.model, names.pageOfModel),
95
101
  lastPageOfModel: sliceName.replace(names.model, names.lastPageOfModel),
@@ -98,6 +104,7 @@ function Render<RefName extends string, Light extends { id: string }>({
98
104
  sortOfModel: sliceName.replace(names.model, names.sortOfModel),
99
105
  setPageOfModel: sliceName.replace(names.model, names.setPageOfModel),
100
106
  addPageOfModel: sliceName.replace(names.model, names.addPageOfModel),
107
+ refreshModel: sliceName.replace(names.model, names.refreshModel),
101
108
  };
102
109
  const modelList = storeUse[namesOfSlice.modelList]() as DataList<Light>;
103
110
  const modelListLoading = storeUse[namesOfSlice.modelListLoading]() as string | boolean;
@@ -106,6 +113,7 @@ function Render<RefName extends string, Light extends { id: string }>({
106
113
  const initModelObjInsight = (init as any)[names.modelObjInsight] as BaseInsight;
107
114
  const initLimitOfModel = (init as any)[names.limitOfModel] as number;
108
115
  const initPageOfModel = (init as any)[names.pageOfModel] as number;
116
+ const modelStaleAt = storeUse[namesOfSlice.modelStaleAt]() as Date;
109
117
 
110
118
  const useCache =
111
119
  !modelListLoading &&
@@ -143,6 +151,13 @@ function Render<RefName extends string, Light extends { id: string }>({
143
151
  loaded.current = true;
144
152
  }, []);
145
153
 
154
+ useEffect(() => {
155
+ const staleThreshold = Math.max(modelStaleAt.getTime(), staleTime === undefined ? 0 : Date.now() - staleTime);
156
+ if (storeGet<Date>()[namesOfSlice.modelInitAt].getTime() >= staleThreshold) return;
157
+ if (storeGet<boolean>()[namesOfSlice.modelListLoading]) return;
158
+ void storeDo[namesOfSlice.refreshModel]({ invalidate: true });
159
+ }, [modelStaleAt]);
160
+
146
161
  const modelInsight = storeUse[namesOfSlice.modelInsight]() as BaseInsight;
147
162
  const limitOfModel = storeUse[namesOfSlice.limitOfModel]() as number;
148
163
  const pageOfModel = storeUse[namesOfSlice.pageOfModel]() as number;
@@ -175,9 +190,7 @@ function Render<RefName extends string, Light extends { id: string }>({
175
190
  {modelDataList.length || renderEmpty === false ? (
176
191
  <ContainerWrapper
177
192
  containerRef={containerRef}
178
- className={clsx(className, {
179
- "grid-cols-1 md:grid-cols-1 lg:grid-cols-1": modelDataList.length === 0,
180
- })}
193
+ className={cn(className, modelDataList.length === 0 && "grid-cols-1 md:grid-cols-1 lg:grid-cols-1")}
181
194
  noDiv={noDiv}
182
195
  pagination={pagination}
183
196
  moreProps={moreProps}
@@ -237,6 +250,7 @@ export default function Units<RefName extends string, Light extends { id: string
237
250
  reverse,
238
251
  style,
239
252
  pagination = true,
253
+ staleTime,
240
254
  }: UnitsProps<RefName, Light>) {
241
255
  const props: UnitsProps<RefName, Light> = {
242
256
  containerRef,
@@ -254,6 +268,7 @@ export default function Units<RefName extends string, Light extends { id: string
254
268
  sort,
255
269
  reverse,
256
270
  pagination,
271
+ staleTime,
257
272
  };
258
273
 
259
274
  const { fulfilled, value: promiseInit } = useFetch(init);
package/ui/Load/View.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { clsx } from "akanjs/client";
2
+ import { cn } from "akanjs/client";
3
3
  import { ConstantRegistry } from "akanjs/constant";
4
4
  import type { ClientView, ServerView } from "akanjs/fetch";
5
5
  import { st } from "akanjs/store";
@@ -76,7 +76,7 @@ function Render<T extends string, Full extends { id: string }>({
76
76
  return noDiv && renderModel ? (
77
77
  <>{renderView(renderModel)}</>
78
78
  ) : renderModel ? (
79
- <div className={clsx("w-full", className)}>{renderView(renderModel)}</div>
79
+ <div className={cn("w-full", className)}>{renderView(renderModel)}</div>
80
80
  ) : null;
81
81
  }
82
82
 
@@ -1,8 +1,10 @@
1
+ import { AiOutlineLoading } from "react-icons/ai";
2
+
1
3
  export const Area = () => {
2
4
  return (
3
- <div className="absolute inset-0 flex size-full items-center justify-center bg-base-300/30">
5
+ <div className="absolute inset-0 flex size-full items-center justify-center bg-border/30">
4
6
  <div className="text-center">
5
- <span className="loading loading-sm loading-dots" />
7
+ <AiOutlineLoading className="mx-auto animate-spin text-primary/60 text-sm" />
6
8
  <div className="z-10 text-sm">Loading</div>
7
9
  </div>
8
10
  </div>
@@ -1,4 +1,4 @@
1
- import { clsx } from "akanjs/client";
1
+ import { cn } from "akanjs/client";
2
2
  import type { CSSProperties } from "react";
3
3
 
4
4
  export interface LoadingProps {
@@ -10,7 +10,7 @@ export const Button = ({ className = "", active, style }: LoadingProps) => {
10
10
  const activeClassName = active ? "animate-pulse" : "";
11
11
  return (
12
12
  <div
13
- className={clsx("inline-block h-8 w-16 rounded-md bg-gray-200 align-bottom", activeClassName, className)}
13
+ className={cn("inline-block h-8 w-16 rounded-md bg-muted align-bottom", activeClassName, className)}
14
14
  style={style}
15
15
  />
16
16
  );
@@ -1,4 +1,4 @@
1
- import { clsx } from "akanjs/client";
1
+ import { cn } from "akanjs/client";
2
2
  import type { CSSProperties } from "react";
3
3
 
4
4
  export interface LoadingProps {
@@ -10,7 +10,7 @@ export const Input = ({ className = "", active, style }: LoadingProps) => {
10
10
  const activeClassName = active ? "animate-pulse" : "";
11
11
  return (
12
12
  <div
13
- className={clsx("inline-block h-8 w-44 rounded-md bg-gray-200 align-bottom", activeClassName, className)}
13
+ className={cn("inline-block h-8 w-44 rounded-md bg-muted align-bottom", activeClassName, className)}
14
14
  style={style}
15
15
  />
16
16
  );
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { clsx } from "akanjs/client";
2
+ import { cn } from "akanjs/client";
3
3
  import { animated } from "akanjs/ui";
4
4
  import { useSpring } from "react-spring";
5
5
 
@@ -10,5 +10,5 @@ export interface ProgressBarProps {
10
10
  }
11
11
  export const ProgressBar = ({ className, value, max }: ProgressBarProps) => {
12
12
  const progress = useSpring({ value: 0, to: { value: value } });
13
- return <animated.progress className={clsx("progress w-full", className)} value={progress.value} max={max} />;
13
+ return <animated.progress className={cn("progress w-full", className)} value={progress.value} max={max} />;
14
14
  };
@@ -1,4 +1,4 @@
1
- import { clsx } from "akanjs/client";
1
+ import { cn } from "akanjs/client";
2
2
  import type { CSSProperties } from "react";
3
3
 
4
4
  export interface SkeletonProps {
@@ -10,12 +10,12 @@ export interface SkeletonProps {
10
10
  export const Skeleton = ({ className = "", active, style }: SkeletonProps) => {
11
11
  const activeClassName = active ? "animate-pulse" : "";
12
12
  return (
13
- <div className={clsx("w-full", activeClassName, className)} style={style}>
13
+ <div className={cn("w-full", activeClassName, className)} style={style}>
14
14
  <div className="flex flex-col justify-start space-y-3">
15
- <div className="h-4 w-2/5 rounded-md bg-gray-200"></div>
16
- <div className="h-4 w-full rounded-md bg-gray-200"></div>
17
- <div className="h-4 w-full rounded-md bg-gray-200"></div>
18
- <div className="h-4 w-3/5 rounded-md bg-gray-200"></div>
15
+ <div className="h-4 w-2/5 rounded-md bg-muted"></div>
16
+ <div className="h-4 w-full rounded-md bg-muted"></div>
17
+ <div className="h-4 w-full rounded-md bg-muted"></div>
18
+ <div className="h-4 w-3/5 rounded-md bg-muted"></div>
19
19
  </div>
20
20
  </div>
21
21
  );
@@ -1,4 +1,4 @@
1
- import { clsx } from "akanjs/client";
1
+ import { cn } from "akanjs/client";
2
2
  import type { ReactNode } from "react";
3
3
  import { AiOutlineLoading } from "react-icons/ai";
4
4
 
@@ -9,7 +9,7 @@ export interface SpinProps {
9
9
  }
10
10
  export const Spin = ({ indicator, isCenter, className }: SpinProps) => {
11
11
  return (
12
- <div className={clsx("inline-block py-1", className)}>
12
+ <div className={cn("inline-block py-1", className)}>
13
13
  <div className={isCenter ? "absolute inset-0 flex size-full flex-none items-center justify-center" : ""}>
14
14
  {indicator ? (
15
15
  <div className="[&>svg]:animate-spin">{indicator}</div>
package/ui/Menu.tsx CHANGED
@@ -1,8 +1,9 @@
1
1
  "use client";
2
- import { clsx } from "akanjs/client";
2
+ import { cn } from "akanjs/client";
3
3
  import { st } from "akanjs/store";
4
4
  import { type ReactNode, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
5
5
  import { AiFillCaretDown, AiOutlineEllipsis } from "react-icons/ai";
6
+ import { buttonRecipe } from "./Button";
6
7
 
7
8
  import { createOverridable } from "./UiOverride";
8
9
 
@@ -49,7 +50,7 @@ export const DefaultMenu = ({
49
50
  }: MenuProps) => {
50
51
  const [expandedKey, setExpandedKey] = useState<string>();
51
52
  const [currentKey, setCurrentKey] = useState<string | null>(defaultSelectedKeys?.[0] ?? null);
52
- const modeClassName = mode === "horizontal" ? "menu-horizontal flex-row " : "bg-base-200";
53
+ const modeClassName = mode === "horizontal" ? "flex-row " : "bg-muted";
53
54
  const menuRef = useRef<HTMLDivElement | null>(null);
54
55
  const LiRefs = useRef<HTMLLIElement[]>([]);
55
56
  const overflowLiRef = useRef<HTMLLIElement | null>(null);
@@ -58,20 +59,16 @@ export const DefaultMenu = ({
58
59
 
59
60
  const subMenuClassName =
60
61
  mode === "horizontal"
61
- ? `fixed menu-title bottom-0 translate-y-[98%] border-0 rounded-xs shadow-lg bg-base-100 hover:bg-base-100 flex flex-col`
62
+ ? `fixed bottom-0 flex translate-y-[98%] flex-col rounded-xs border-0 bg-background shadow-lg hover:bg-background`
62
63
  : "flex flex-col gap-0 p-0 bg-primary/10 hover:bg-primary/10 overflow-hidden";
63
64
 
64
65
  const subMenuItemClassName =
65
66
  mode === "inline"
66
- ? "w-full h-full bg-red-500 btn btn-ghost px-2 m-0 hover:bg-primary/20 btn btn-ghost text-primary-focus font-normal"
67
- : "w-full text-center duration-300 whitespace-nowrap btn btn-ghost text-primary-focus font-normal";
68
-
69
- const activeClassName =
70
- activeStyle === "active"
71
- ? "bg-primary text-primary-focus"
72
- : activeStyle === "bordered"
73
- ? " border-b-2 border-white"
74
- : activeStyle;
67
+ ? buttonRecipe({ variant: "ghost" }, "m-0 h-full w-full px-2 font-normal text-primary hover:bg-primary/20")
68
+ : buttonRecipe(
69
+ { variant: "ghost" },
70
+ "w-full whitespace-nowrap text-center font-normal text-primary duration-300",
71
+ );
75
72
 
76
73
  const [overflowMenuItems, setOverflowMenuItems] = useState<MenuItem[]>([]);
77
74
 
@@ -125,10 +122,10 @@ export const DefaultMenu = ({
125
122
  <div
126
123
  ref={menuRef}
127
124
  id="menu"
128
- className={clsx(mode === "horizontal" ? "w-full shrink overflow-hidden" : "w-fit", className)}
125
+ className={cn(mode === "horizontal" ? "w-full shrink overflow-hidden" : "w-fit", className)}
129
126
  >
130
127
  <ul
131
- className={clsx("menu size-full flex-nowrap overflow-y-auto p-0", modeClassName, ulClassName)}
128
+ className={cn("flex size-full flex-col flex-nowrap overflow-y-auto p-0", modeClassName, ulClassName)}
132
129
  style={{ ...style }}
133
130
  onMouseOver={onMouseOver}
134
131
  onMouseLeave={onMouseLeave}
@@ -145,10 +142,13 @@ export const DefaultMenu = ({
145
142
  }}
146
143
  id={item.key}
147
144
  key={item.key}
148
- className={clsx("relative m-0 duration-200 hover:opacity-70", overflowClassName, liClassName, {
149
- "bg-base-300": activeStyle === "active" && checkIsActive(item.key),
150
- "border-base-100 border-b-2": activeStyle === "bordered" && checkIsActive(item.key),
151
- })}
145
+ className={cn(
146
+ "relative m-0 duration-200 hover:opacity-70",
147
+ overflowClassName,
148
+ liClassName,
149
+ activeStyle === "active" && checkIsActive(item.key) && "bg-border",
150
+ activeStyle === "bordered" && checkIsActive(item.key) && "border-background border-b-2",
151
+ )}
152
152
  onClick={() => {
153
153
  if (!isOverflowItem) handleOnClick(item);
154
154
  }}
@@ -161,12 +161,12 @@ export const DefaultMenu = ({
161
161
  }}
162
162
  >
163
163
  <div className="flex h-full justify-between rounded-none">
164
- <div className={clsx("flex items-center gap-1", labelClassName?.(checkIsActive(item.key)))}>
164
+ <div className={cn("flex items-center gap-1", labelClassName?.(checkIsActive(item.key)))}>
165
165
  {item.icon}
166
166
 
167
- {!inlineCollapsed && <div className="whitespace-nowrap text-base-content">{item.label}</div>}
167
+ {!inlineCollapsed && <div className="whitespace-nowrap text-foreground">{item.label}</div>}
168
168
  {/* <div
169
- className={clsx(
169
+ className={cn(
170
170
  "whitespace-nowrap truncate ",
171
171
  mode === "horizontal" && !isOverflowItem && item.children
172
172
  ? "flex justify-start animate-menuOpen"
@@ -178,7 +178,7 @@ export const DefaultMenu = ({
178
178
  </div>
179
179
  {item.children && mode === "inline" && (
180
180
  <AiFillCaretDown
181
- className={clsx(
181
+ className={cn(
182
182
  "text-xs transition-transform duration-400",
183
183
  expandedKey === item.key ? "rotate-180" : "",
184
184
  )}
@@ -234,12 +234,12 @@ const OverflowMenu = ({ overflowItems, onClick }: OverflowMenuProps) => {
234
234
  <AiOutlineEllipsis />
235
235
  </div>
236
236
  {isOpen && (
237
- <div className="fixed -bottom-0 flex translate-y-[98%] flex-col rounded-xs border-0 bg-base-100 p-2 shadow-lg hover:bg-base-100">
237
+ <div className="fixed -bottom-0 flex translate-y-[98%] flex-col rounded-xs border-0 bg-background p-2 shadow-lg hover:bg-background">
238
238
  {overflowItems.map((item) => (
239
239
  <div
240
240
  key={item.key}
241
241
  onClick={() => onClick?.(item)}
242
- className="btn btn-ghost relative whitespace-nowrap font-normal text-primary-focus"
242
+ className={buttonRecipe({ variant: "ghost" }, "relative whitespace-nowrap font-normal text-primary")}
243
243
  onMouseEnter={() => {
244
244
  if (item.children && expandedKey !== item.key) setExpandedKey(item.key);
245
245
  }}
@@ -249,7 +249,7 @@ const OverflowMenu = ({ overflowItems, onClick }: OverflowMenuProps) => {
249
249
  >
250
250
  {item.label}
251
251
  {item.children && expandedKey === item.key && (
252
- <div className="absolute top-0 left-0 -translate-x-full bg-base-100 p-4 drop-shadow-sm">
252
+ <div className="absolute top-0 left-0 -translate-x-full bg-background p-4 drop-shadow-sm">
253
253
  {item.children.map((child) => (
254
254
  <div
255
255
  onClick={(e) => {
@@ -258,8 +258,10 @@ const OverflowMenu = ({ overflowItems, onClick }: OverflowMenuProps) => {
258
258
  setExpandedKey(undefined);
259
259
  }}
260
260
  key={child.key}
261
- className="btn btn-ghost flex items-center justify-center text-center font-normal text-primary-focus"
262
-
261
+ className={buttonRecipe(
262
+ { variant: "ghost" },
263
+ "flex items-center justify-center text-center font-normal text-primary",
264
+ )}
263
265
  >
264
266
  {child.label}
265
267
  </div>
package/ui/Model/Edit.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { clsx, usePage } from "akanjs/client";
1
+ import { cn, usePage } from "akanjs/client";
2
2
  import type { SliceMeta } from "akanjs/fetch";
3
3
  import type { ReactNode } from "react";
4
4
  import { AiOutlineEdit } from "react-icons/ai";
@@ -29,9 +29,9 @@ export default function Edit({
29
29
  }: EditProps) {
30
30
  const { l } = usePage();
31
31
  return (
32
- <div className={clsx("inline", wrapperClassName)}>
32
+ <div className={cn("inline", wrapperClassName)}>
33
33
  <EditWrapper
34
- className={clsx("flex w-full items-center justify-center gap-2", className)}
34
+ className={cn("flex w-full items-center justify-center gap-2", className)}
35
35
  slice={slice}
36
36
  modelId={modelId}
37
37
  modal={modal}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { clsx, isRscNavigationFromCache, router, usePage } from "akanjs/client";
2
+ import { cn, isRscNavigationFromCache, router, usePage } from "akanjs/client";
3
3
  import { capitalize, deepObjectify, lowerlize } from "akanjs/common";
4
4
  import { ConstantRegistry, immerify } from "akanjs/constant";
5
5
  import type { ClientEdit, ServerEdit, SliceMeta } from "akanjs/fetch";
@@ -74,15 +74,15 @@ const EditModel = <Full,>({
74
74
  : ({ children, className }: { children?: any; className?: string }) => {
75
75
  const modelFormLoading = storeUse[names.modelFormLoading]();
76
76
  return (
77
- <div className={clsx("", className)}>
77
+ <div className={cn("", className)}>
78
78
  {children}
79
- {modelFormLoading ? <div className="absolute inset-0 animate-pulse bg-base-100/50" /> : null}
79
+ {modelFormLoading ? <div className="absolute inset-0 animate-pulse bg-background/50" /> : null}
80
80
  </div>
81
81
  );
82
82
  };
83
83
  }, []);
84
84
 
85
- return <LoadingWrapper className={clsx("w-full", className)}>{children}</LoadingWrapper>;
85
+ return <LoadingWrapper className={cn("w-full", className)}>{children}</LoadingWrapper>;
86
86
  };
87
87
 
88
88
  interface EditModalProps<Full extends { id: string }> extends EditModelProps<Full> {
@@ -256,7 +256,7 @@ export default function EditModal<Full extends { id: string }>({
256
256
  };
257
257
  return (
258
258
  <Button
259
- className={clsx("btn btn-primary mt-4 w-full gap-2 rounded-2xl", submitClassName)}
259
+ className={cn("mt-4 w-full gap-2 rounded-2xl", submitClassName)}
260
260
  disabled={modelSubmit.disabled || !!disabled}
261
261
  onClick={async (e, { onError }) => {
262
262
  await handleSubmit({ onError });
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { clsx } from "akanjs/client";
2
+ import { cn } from "akanjs/client";
3
3
  import { capitalize } from "akanjs/common";
4
4
  import type { SliceMeta } from "akanjs/fetch";
5
5
  import { st } from "akanjs/store";
@@ -32,7 +32,7 @@ export default function EditWrapper({
32
32
  const storeDo = st.do as unknown as { [key: string]: (...args: any[]) => Promise<void> };
33
33
  return (
34
34
  <div
35
- className={clsx("cursor-pointer", className)}
35
+ className={cn("cursor-pointer", className)}
36
36
  onClick={(e) => {
37
37
  if (disabled) return;
38
38
  e.stopPropagation();
package/ui/Model/New.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { clsx, usePage } from "akanjs/client";
1
+ import { cn, usePage } from "akanjs/client";
2
2
  import type { SliceMeta } from "akanjs/fetch";
3
3
  import type { ReactNode } from "react";
4
4
  import { AiOutlinePlus } from "react-icons/ai";
@@ -29,9 +29,9 @@ export default function New({
29
29
  }: NewProps) {
30
30
  const { l } = usePage();
31
31
  return (
32
- <div className={clsx("inline", wrapperClassName)}>
32
+ <div className={cn("inline", wrapperClassName)}>
33
33
  <NewWrapper
34
- className={clsx("flex w-full items-center justify-center gap-2", className)}
34
+ className={cn("flex w-full items-center justify-center gap-2", className)}
35
35
  slice={slice}
36
36
  modal={modal}
37
37
  partial={partial}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import type { GetStateObject } from "akanjs/base";
3
- import { clsx } from "akanjs/client";
3
+ import { cn } from "akanjs/client";
4
4
  import { capitalize } from "akanjs/common";
5
5
  import { ConstantRegistry } from "akanjs/constant";
6
6
  import type { SliceMeta } from "akanjs/fetch";
@@ -39,7 +39,7 @@ export const NewWrapper_Client = <Full,>({
39
39
  const disabled = modelModal === "edit";
40
40
  return (
41
41
  <div
42
- className={clsx({ "cursor-pointer": !disabled, "pointer-events-none": disabled }, className)}
42
+ className={cn(!disabled && "cursor-pointer", disabled && "pointer-events-none", className)}
43
43
  onClick={(e) => {
44
44
  e.stopPropagation();
45
45
  if (disabled) return;
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { clsx, msg, router, usePage } from "akanjs/client";
2
+ import { cn, msg, router, usePage } from "akanjs/client";
3
3
  import { capitalize } from "akanjs/common";
4
4
  import type { SliceMeta } from "akanjs/fetch";
5
5
  import { st } from "akanjs/store";
@@ -32,7 +32,7 @@ export default function Remove({ className, name, modelId, slice, modal, redirec
32
32
  return (
33
33
  <>
34
34
  <div
35
- className={clsx("cursor-pointer", className)}
35
+ className={cn("cursor-pointer", className)}
36
36
  onClick={(e) => {
37
37
  e.stopPropagation();
38
38
  setModalOpen(true);
@@ -46,13 +46,14 @@ export default function Remove({ className, name, modelId, slice, modal, redirec
46
46
  setModalOpen(false);
47
47
  }}
48
48
  title={
49
- <div className="font-bold text-error text-lg">
49
+ <div className="font-bold text-destructive text-lg">
50
50
  {l("base.removeModel", { model: l(`${modelName}.modelName` as "base.new") })}
51
51
  </div>
52
52
  }
53
53
  action={
54
54
  <Button
55
- className="btn btn-warning w-full"
55
+ variant="warning"
56
+ className="w-full"
56
57
  onClick={async (e, { onError }) => {
57
58
  await storeDo[names.removeModel](modelId, { onError, modal });
58
59
  msg.success("base.removeSuccess", { data: { model: l(`${modelName}.modelName` as "base.new") } });
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { clsx, usePage } from "akanjs/client";
2
+ import { cn, usePage } from "akanjs/client";
3
3
  import { capitalize } from "akanjs/common";
4
4
  import type { SliceMeta } from "akanjs/fetch";
5
5
  import { st } from "akanjs/store";
@@ -32,7 +32,7 @@ export default function RemoveWrapper({ children, slice, name, modelId, classNam
32
32
  void storeDo[names.removeModel](modelId, { modal });
33
33
  }}
34
34
  >
35
- <div className={clsx("cursor-pointer", className)}>{children}</div>
35
+ <div className={cn("cursor-pointer", className)}>{children}</div>
36
36
  </Popconfirm>
37
37
  );
38
38
  }
@@ -1,11 +1,12 @@
1
1
  "use client";
2
- import { clsx, msg, router, usePage } from "akanjs/client";
2
+ import { cn, msg, router, usePage } from "akanjs/client";
3
3
  import { capitalize } from "akanjs/common";
4
4
  import type { SliceMeta } from "akanjs/fetch";
5
5
  import { st } from "akanjs/store";
6
6
  import { useMemo, useState } from "react";
7
7
  import { AiOutlineDelete } from "react-icons/ai";
8
8
 
9
+ import { buttonRecipe } from "../Button";
9
10
  import { Modal } from "../Modal";
10
11
 
11
12
  interface SureToRemoveProps {
@@ -46,8 +47,8 @@ export default function SureToRemove({
46
47
  }}
47
48
  >
48
49
  <div
49
- className={clsx(
50
- "flex size-full cursor-pointer flex-nowrap items-center justify-center gap-2 whitespace-nowrap text-error",
50
+ className={cn(
51
+ "flex size-full cursor-pointer flex-nowrap items-center justify-center gap-2 whitespace-nowrap text-destructive",
51
52
  className,
52
53
  )}
53
54
  >
@@ -59,14 +60,14 @@ export default function SureToRemove({
59
60
  setModalOpen(false);
60
61
  }}
61
62
  title={
62
- <div className="font-bold text-error text-lg">
63
+ <div className="font-bold text-destructive text-lg">
63
64
  {l("base.removeModel", { model: l(`${modelName}.modelName` as "base.new") })}
64
65
  </div>
65
66
  }
66
- bodyClassName="border-error"
67
+ bodyClassName="border-destructive"
67
68
  action={
68
69
  <button
69
- className="btn btn-error w-full"
70
+ className={buttonRecipe({ variant: "destructive" }, "w-full")}
70
71
  disabled={typeNameToRemove && repeatName !== name}
71
72
  onClick={async () => {
72
73
  await storeDo[names.removeModel](modelId);
package/ui/Model/View.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { type ReactNode, useMemo } from "react";
3
- import { AiOutlineMeh } from "react-icons/ai";
3
+ import { AiOutlineLoading, AiOutlineMeh } from "react-icons/ai";
4
4
 
5
5
  interface ViewProps<Full extends { id: string }> {
6
6
  className?: string;
@@ -41,7 +41,7 @@ export default function View<Full extends { id: string }>({
41
41
  : (loading ??
42
42
  (() => (
43
43
  <div className="flex size-full flex-col items-center justify-center gap-3 pt-6 pb-3">
44
- <span className="loading loading-dots loading-lg" />
44
+ <AiOutlineLoading className="animate-spin text-2xl text-primary/60" />
45
45
  Loading
46
46
  </div>
47
47
  )))