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
package/ui/Data/Item.tsx CHANGED
@@ -1,12 +1,14 @@
1
1
  "use client";
2
2
  import type { Dayjs } from "akanjs/base";
3
- import { clsx, type DataAction, type DataColumn, usePage } from "akanjs/client";
3
+ import { cn, type DataAction, type DataColumn, usePage } from "akanjs/client";
4
4
  import { capitalize } from "akanjs/common";
5
5
  import type { BaseObject } from "akanjs/constant";
6
6
  import type { SliceMeta } from "akanjs/fetch";
7
7
  import { st } from "akanjs/store";
8
8
  import type { ReactNode } from "react";
9
9
  import { AiOutlineDelete, AiOutlineEdit, AiOutlineEye, AiOutlineMore } from "react-icons/ai";
10
+ import { badgeRecipe } from "../Badge";
11
+ import { buttonRecipe } from "../Button";
10
12
  import { Dropdown } from "../Dropdown";
11
13
  import { ObjectId } from "../ObjectId";
12
14
  import { Popconfirm } from "../Popconfirm";
@@ -129,7 +131,7 @@ export default function Item<T extends string, Full extends { id: string }, Ligh
129
131
  });
130
132
 
131
133
  return (
132
- <div className={clsx("flex flex-col", className)}>
134
+ <div className={cn("flex flex-col", className)}>
133
135
  {children ? (
134
136
  <div className="flex justify-center">
135
137
  <div className="relative size-full" onClick={onClick}>
@@ -143,16 +145,14 @@ export default function Item<T extends string, Full extends { id: string }, Ligh
143
145
  ) : null}
144
146
  <div className="mt-2 h-full rounded-lg bg-primary/5 p-2">
145
147
  <div className="mb-2 flex justify-between">
146
- <div className="[&_.badge]:badge-xs [&_.badge]:p-2">
147
- {columns.find((c) => c === "id") && <ObjectId id={model.id} />}
148
- </div>
148
+ <div>{columns.find((c) => c === "id") && <ObjectId id={model.id} />}</div>
149
149
 
150
150
  <div className="flex items-end justify-center gap-2">
151
151
  {columns.find((c) => c === "createdAt") && (
152
152
  <RecentTime date={(model as unknown as BaseObject).createdAt} className="text-xs opacity-60" />
153
153
  )}
154
154
  {columns.find((c) => c === "status") && (
155
- <StatusTag status={(model as unknown as { status: string }).status} className="badge-xs mr-0 p-2" />
155
+ <StatusTag status={(model as unknown as { status: string }).status} className="mr-0 p-2" />
156
156
  )}
157
157
  </div>
158
158
  </div>
@@ -165,7 +165,7 @@ export default function Item<T extends string, Full extends { id: string }, Ligh
165
165
  ))}
166
166
  {customActions.length ? (
167
167
  <Dropdown
168
- buttonClassName="m-1 text-center btn btn-square btn-ghost btn-sm "
168
+ buttonClassName={buttonRecipe({ variant: "ghost", size: "icon" }, "m-1 size-8 text-center")}
169
169
  value={<AiOutlineMore />}
170
170
  content={customActions.map((action) => <div key={action.key}>{action.label}</div>)}
171
171
  />
@@ -205,14 +205,20 @@ export const Action = <T extends string, M extends { id: string }, L extends { i
205
205
  };
206
206
  return action === "edit" ? (
207
207
  <button
208
- className={`btn btn-square btn-ghost btn-sm m-1 text-center ${outline && "btn-outline border-dashed"}`}
208
+ className={buttonRecipe({ variant: outline ? "outline" : "ghost", size: "icon" }, [
209
+ "m-1 size-8 text-center",
210
+ outline && "border-dashed",
211
+ ])}
209
212
  onClick={() => void storeDo[namesOfSlice.editModel](model.id)}
210
213
  >
211
214
  <AiOutlineEdit key={action} />
212
215
  </button>
213
216
  ) : action === "view" ? (
214
217
  <button
215
- className={`btn btn-square btn-ghost btn-sm m-1 text-center ${outline && "btn-outline border-dashed"}`}
218
+ className={buttonRecipe({ variant: outline ? "outline" : "ghost", size: "icon" }, [
219
+ "m-1 size-8 text-center",
220
+ outline && "border-dashed",
221
+ ])}
216
222
  onClick={() => void storeDo[namesOfSlice.viewModel](model.id)}
217
223
  >
218
224
  <AiOutlineEye key={action} />
@@ -223,7 +229,12 @@ export const Action = <T extends string, M extends { id: string }, L extends { i
223
229
  title={l("base.removeMsg")}
224
230
  onConfirm={() => void storeDo[namesOfSlice.removeModel](model.id)}
225
231
  >
226
- <button className={`btn btn-square btn-ghost btn-sm m-1 text-center ${outline && "btn-outline border-dashed"}`}>
232
+ <button
233
+ className={buttonRecipe({ variant: outline ? "outline" : "ghost", size: "icon" }, [
234
+ "m-1 size-8 text-center",
235
+ outline && "border-dashed",
236
+ ])}
237
+ >
227
238
  <AiOutlineDelete />
228
239
  </button>
229
240
  </Popconfirm>
@@ -233,28 +244,29 @@ export const Action = <T extends string, M extends { id: string }, L extends { i
233
244
  };
234
245
 
235
246
  const statusColors = {
236
- active: "badge-info badge-outline",
237
- applied: "badge-warning",
238
- approved: "badge-success",
239
- denied: "badge-error badge-outline",
240
- failed: "badge-error badge-outline",
241
- restricted: "badge-error",
242
- paused: "badge-outline",
243
- running: "badge-warning badge-outline",
244
- break: "badge-accent badge-outline",
245
- rejected: "badge-error badge-outline",
246
- hidden: "badge-outline",
247
- inProgress: "badge-accent",
248
- resolved: "badge-success badge-outline",
249
- finished: "badge-secondary",
247
+ active: "border-info text-info",
248
+ applied: "border-transparent bg-warning text-warning-foreground",
249
+ approved: "border-transparent bg-success text-success-foreground",
250
+ denied: "border-destructive text-destructive",
251
+ failed: "border-destructive text-destructive",
252
+ restricted: "border-transparent bg-destructive text-destructive-foreground",
253
+ paused: "",
254
+ running: "border-warning text-warning",
255
+ break: "border-accent text-accent",
256
+ rejected: "border-destructive text-destructive",
257
+ hidden: "",
258
+ inProgress: "border-transparent bg-accent text-accent-foreground",
259
+ resolved: "border-success text-success",
260
+ finished: "border-transparent bg-secondary text-secondary-foreground",
250
261
  };
251
262
  const StatusTag = ({ status, className }: { status: string; className?: string }) => {
252
263
  return (
253
264
  <div
254
- className={clsx(
255
- `badge mr-1 p-3 ${statusColors[status as keyof typeof statusColors] ?? "badge-outline"}`,
265
+ className={badgeRecipe({ variant: "outline" }, [
266
+ "mr-1 p-3",
267
+ statusColors[status as keyof typeof statusColors] ?? "",
256
268
  className,
257
- )}
269
+ ])}
258
270
  >
259
271
  {status}
260
272
  </div>
@@ -263,21 +275,24 @@ const StatusTag = ({ status, className }: { status: string; className?: string }
263
275
  Item.StatusTag = StatusTag;
264
276
 
265
277
  const roleColors = {
266
- user: "badge-success",
267
- business: "badge-warning",
268
- admin: "badge-error badge-outline",
269
- superAdmin: "badge-error",
270
- root: "badge-primary",
278
+ user: "border-transparent bg-success text-success-foreground",
279
+ business: "border-transparent bg-warning text-warning-foreground",
280
+ admin: "border-destructive text-destructive",
281
+ superAdmin: "border-transparent bg-destructive text-destructive-foreground",
282
+ root: "border-transparent bg-primary text-primary-foreground",
271
283
  };
272
284
  const RoleTags = ({ role }: { role: string | string[] }) => {
273
285
  return Array.isArray(role) ? (
274
286
  role.map((role) => (
275
- <div className={`badge mr-1 ${roleColors[role as keyof typeof roleColors]}`} key={role}>
287
+ <div
288
+ className={badgeRecipe({ variant: "outline" }, ["mr-1", roleColors[role as keyof typeof roleColors]])}
289
+ key={role}
290
+ >
276
291
  {role}
277
292
  </div>
278
293
  ))
279
294
  ) : (
280
- <div className="badge mr-1" style={{ backgroundColor: roleColors[role as keyof typeof roleColors] as string }}>
295
+ <div className={badgeRecipe({ variant: "outline" }, ["mr-1", roleColors[role as keyof typeof roleColors]])}>
281
296
  {role}
282
297
  </div>
283
298
  );
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import type { DataList } from "akanjs/base";
3
3
  import {
4
- clsx,
4
+ cn,
5
5
  type DataAction,
6
6
  type DataColumn,
7
7
  type DataTool,
@@ -23,6 +23,7 @@ import {
23
23
  AiOutlineRedo,
24
24
  } from "react-icons/ai";
25
25
 
26
+ import { buttonRecipe } from "../Button";
26
27
  import { Dropdown } from "../Dropdown";
27
28
  import { Loading } from "../Loading";
28
29
  import { Model } from "../Model";
@@ -191,7 +192,7 @@ export default function ListContainer<
191
192
  key: "export-csv",
192
193
  render: () => (
193
194
  <div
194
- className="btn btn-sm btn-ghost flex flex-nowrap justify-start gap-2"
195
+ className={buttonRecipe({ variant: "ghost", size: "sm" }, "flex flex-nowrap justify-start gap-2")}
195
196
  onClick={() => {
196
197
  const header = columns
197
198
  .map((column) => {
@@ -225,7 +226,7 @@ export default function ListContainer<
225
226
  {
226
227
  render: () => (
227
228
  <div
228
- className="btn btn-sm btn-ghost flex flex-nowrap justify-start gap-2"
229
+ className={buttonRecipe({ variant: "ghost", size: "sm" }, "flex flex-nowrap justify-start gap-2")}
229
230
  onClick={() => {
230
231
  const json = JSON.stringify(deepObjectify([...modelList], { serializable: true }));
231
232
  const blob = new Blob([json], { type: "application/json" });
@@ -240,7 +241,10 @@ export default function ListContainer<
240
241
  ];
241
242
  return (
242
243
  <Dropdown
243
- buttonClassName={`btn btn-primary btn-sm ${renderTemplate && create ? "rounded-l-none" : ""}`}
244
+ buttonClassName={buttonRecipe(
245
+ { variant: "primary", size: "sm" },
246
+ renderTemplate && create ? "rounded-l-none" : "",
247
+ )}
244
248
  value={<AiOutlineEllipsis />}
245
249
  content={toolList.map((tool) => <tool.render key={tool.key} />)}
246
250
  />
@@ -261,7 +265,7 @@ export default function ListContainer<
261
265
  );
262
266
  };
263
267
  return (
264
- <div className={clsx("m-4", className)}>
268
+ <div className={cn("m-4", className)}>
265
269
  <div className="mb-3 flex flex-wrap justify-between">
266
270
  <div className="flex pb-1">
267
271
  <p className="prose text-lg">
@@ -271,7 +275,7 @@ export default function ListContainer<
271
275
  {renderTemplate && create ? (
272
276
  <button
273
277
  onClick={() => void storeDo[namesOfSlice.newModel]()}
274
- className={`btn btn-sm btn-primary mr-[0.5px] rounded-r-none`}
278
+ className={buttonRecipe({ variant: "primary", size: "sm" }, "mr-[0.5px] rounded-r-none")}
275
279
  >
276
280
  <AiOutlinePlus /> {l("base.new")}
277
281
  </button>
@@ -281,7 +285,7 @@ export default function ListContainer<
281
285
  </div>
282
286
  <div className="flex">
283
287
  <button
284
- className="btn btn-primary btn-sm btn-square mx-1"
288
+ className={buttonRecipe({ variant: "primary", size: "icon" }, "mx-1 size-8")}
285
289
  onClick={() => void storeDo[namesOfSlice.refreshModel]()}
286
290
  >
287
291
  <AiOutlineRedo className="mx-2" />
@@ -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 { BaseInsight } from "akanjs/constant";
5
5
  import type { SliceMeta } from "akanjs/fetch";
@@ -39,7 +39,7 @@ export default function Pagination<T extends string>({ className, slice }: Pagin
39
39
  const lastPageOfModel = storeUse[namesOfSlice.lastPageOfModel]() as number;
40
40
  const pageOfModel = storeUse[namesOfSlice.pageOfModel]() as number;
41
41
  return (
42
- <div className={clsx("mt-4 flex flex-wrap justify-center", className)}>
42
+ <div className={cn("mt-4 flex flex-wrap justify-center", className)}>
43
43
  <Pagn
44
44
  currentPage={pageOfModel}
45
45
 
package/ui/DatePicker.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { type Dayjs, dayjs } from "akanjs/base";
3
- import { clsx, msg } from "akanjs/client";
3
+ import { cn, msg } from "akanjs/client";
4
4
  import { lazy } from "akanjs/webkit";
5
5
  import { type FocusEvent, useEffect, useRef } from "react";
6
6
  import { AiOutlineSwapRight } from "react-icons/ai";
@@ -58,7 +58,7 @@ const DefaultDatePicker = ({
58
58
 
59
59
  return (
60
60
  <ReactDatePicker
61
- className={clsx("input text-center", className)}
61
+ className={cn("input text-center", className)}
62
62
  selected={value ? value.toDate() : new Date()}
63
63
  disabledKeyboardNavigation
64
64
  onFocus={(e: FocusEvent<HTMLInputElement>) => {
@@ -112,7 +112,7 @@ const DefaultRangePicker = ({
112
112
 
113
113
  const pickerClassName = "m-0 input focus:outline-hidden z-50 p-3 text-center h-full w-full ";
114
114
  return (
115
- <div className={clsx("input flex h-full w-fit items-center gap-2 p-0", className)}>
115
+ <div className={cn("input flex h-full w-fit items-center gap-2 p-0", className)}>
116
116
  <ReactDatePicker
117
117
  className={pickerClassName}
118
118
  selected={value[0] ? value[0].toDate() : undefined}
@@ -125,7 +125,7 @@ const DefaultRangePicker = ({
125
125
  filterDate={(date: Date) => (!disabledDate?.(dayjs(date)) ? true : false)}
126
126
  dateFormat={format}
127
127
  />
128
- <AiOutlineSwapRight className="text-3xl text-gray-400" />
128
+ <AiOutlineSwapRight className="text-3xl text-muted-foreground" />
129
129
  <ReactDatePicker
130
130
  className={pickerClassName}
131
131
  selected={value[1] ? value[1].toDate() : undefined}
@@ -174,7 +174,7 @@ const DefaultTimePicker = ({
174
174
  return (
175
175
  <ReactDatePicker
176
176
  wrapperClassName="inline-block"
177
- className={clsx("inline-block w-auto", className)}
177
+ className={cn("inline-block w-auto", className)}
178
178
  selected={value ? value.toDate() : new Date()}
179
179
  onChange={handleDateChange}
180
180
  showTimeSelect
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { clsx } from "akanjs/client";
2
+ import { cn } from "akanjs/client";
3
3
  import type { ReactNode } from "react";
4
4
 
5
5
  export interface ContentProps {
@@ -7,5 +7,5 @@ export interface ContentProps {
7
7
  children?: ReactNode;
8
8
  }
9
9
  export const Content = ({ className, children }: ContentProps) => {
10
- return <div className={clsx("block w-full", className)}>{children}</div>;
10
+ return <div className={cn("block w-full", className)}>{children}</div>;
11
11
  };
@@ -1,11 +1,12 @@
1
1
  "use client";
2
2
  import { useDrag } from "@use-gesture/react";
3
- import { clsx, usePage } from "akanjs/client";
3
+ import { cn, usePage } from "akanjs/client";
4
4
  import { animated } from "akanjs/ui";
5
5
  import { type ReactNode, useCallback, useContext, useEffect, useId, useRef, useState } from "react";
6
6
  import { createPortal } from "react-dom";
7
7
  import { BiX } from "react-icons/bi";
8
8
  import { config, useSpring } from "react-spring";
9
+ import { buttonRecipe } from "../Button";
9
10
 
10
11
  import { DialogContext } from "./context";
11
12
 
@@ -185,7 +186,7 @@ export const Modal = ({ className, bodyClassName, confirmClose, children, onCanc
185
186
  return createPortal(
186
187
  <>
187
188
  <div
188
- className={clsx("fixed inset-0 z-10", showBackground && "animate-fadeIn bg-black/50 backdrop-blur-md")}
189
+ className={cn("fixed inset-0 z-10", showBackground && "animate-fadeIn bg-black/50 backdrop-blur-md")}
189
190
  onClick={(event) => {
190
191
  if (event.target !== event.currentTarget) return;
191
192
  requestClose();
@@ -205,14 +206,17 @@ export const Modal = ({ className, bodyClassName, confirmClose, children, onCanc
205
206
  <button
206
207
  type="button"
207
208
  aria-label="Close"
208
- className="btn btn-circle btn-sm absolute top-[-16px] right-0 z-20 md:top-[-40px]"
209
+ className={buttonRecipe(
210
+ { variant: "secondary", size: "icon" },
211
+ "absolute top-[-16px] right-0 z-20 size-8 rounded-full md:top-[-40px]",
212
+ )}
209
213
  onClick={() => requestClose()}
210
214
  >
211
215
  <BiX className="text-3xl" />
212
216
  </button>
213
217
  <div
214
- className={clsx(
215
- "mx-auto mt-6 flex max-h-[75vh] w-full max-w-screen animate-fadeIn flex-col items-center justify-center overflow-x-hidden rounded-lg bg-base-100 transition-all duration-100 sm:w-[90%] sm:px-2 sm:pb-2 md:mt-0 md:max-h-[90vh] md:pt-0",
218
+ className={cn(
219
+ "mx-auto mt-6 flex max-h-[75vh] w-full max-w-screen animate-fadeIn flex-col items-center justify-center overflow-x-hidden rounded-lg bg-background transition-all duration-100 sm:w-[90%] sm:px-2 sm:pb-2 md:mt-0 md:max-h-[90vh] md:pt-0",
216
220
  className,
217
221
  )}
218
222
  >
@@ -222,7 +226,7 @@ export const Modal = ({ className, bodyClassName, confirmClose, children, onCanc
222
226
  className="relative z-10 flex w-full animate-fadeIn cursor-pointer touch-pan-y flex-col items-center justify-center px-4 pt-1"
223
227
  >
224
228
  <div className="flex w-full cursor-pointer items-center justify-center pt-1 opacity-50">
225
- <div className="h-1 w-24 rounded-full bg-gray-500" />
229
+ <div className="h-1 w-24 rounded-full bg-muted-foreground" />
226
230
  </div>
227
231
  <div className="flex w-full items-center justify-start">
228
232
  <div className="w-full text-start font-bold text-lg">{title}</div>
@@ -230,8 +234,8 @@ export const Modal = ({ className, bodyClassName, confirmClose, children, onCanc
230
234
  </animated.div>
231
235
  <div
232
236
  id={contentId}
233
- className={clsx(
234
- "scrollbar-none relative m-2 flex size-full min-w-[90vw] overflow-x-hidden overflow-y-scroll border-base-content/30 border-t-[0.1px] p-4 sm:p-4 md:min-w-[384px] md:px-8 lg:min-w-[576px] xl:min-w-[768px]",
237
+ className={cn(
238
+ "scrollbar-none relative m-2 flex size-full min-w-[90vw] overflow-x-hidden overflow-y-scroll border-foreground/30 border-t-[0.1px] p-4 sm:p-4 md:min-w-[384px] md:px-8 lg:min-w-[576px] xl:min-w-[768px]",
235
239
  bodyClassName,
236
240
  )}
237
241
  >
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { clsx } from "akanjs/client";
2
+ import { cn } from "akanjs/client";
3
3
  import { type ReactNode, useEffect, useState } from "react";
4
4
 
5
5
  import { DialogContext } from "./context";
@@ -22,7 +22,7 @@ export const Provider = ({ className, defaultOpen = false, open = defaultOpen, c
22
22
  }, [open]);
23
23
  return (
24
24
  <DialogContext.Provider value={{ open: openState, setOpen: setOpenState, title, setTitle, action, setAction }}>
25
- <div data-open={openState} className={clsx("group/dialog", className)}>
25
+ <div data-open={openState} className={cn("group/dialog", className)}>
26
26
  {children}
27
27
  </div>
28
28
  </DialogContext.Provider>
package/ui/DragAction.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { useGesture } from "@use-gesture/react";
3
3
  import type { ReactDOMAttributes } from "@use-gesture/react/dist/declarations/src/types";
4
- import { clsx } from "akanjs/client";
4
+ import { cn } from "akanjs/client";
5
5
  import { animated } from "akanjs/ui";
6
6
  import { createContext, type ReactNode, useContext, useRef } from "react";
7
7
  import { SpringValue, useSpring } from "react-spring";
@@ -74,7 +74,7 @@ export const DragAction = ({ className, children, onClick, onLeft, onRight }: Dr
74
74
  );
75
75
  return (
76
76
  <DragActionContext.Provider value={{ bind, x, y, leftOpacity, leftWidth, rightOpacity, rightWidth, onClick }}>
77
- <div ref={ref} className={clsx("relative overflow-x-hidden", className)}>
77
+ <div ref={ref} className={cn("relative overflow-x-hidden", className)}>
78
78
  {children}
79
79
  </div>
80
80
  </DragActionContext.Provider>
@@ -1,11 +1,12 @@
1
1
  "use client";
2
2
  import { config, useSprings } from "@react-spring/web";
3
3
  import { useGesture } from "@use-gesture/react";
4
- import { clsx } from "akanjs/client";
4
+ import { cn } from "akanjs/client";
5
5
  import { animated } from "akanjs/ui";
6
6
  import { createContext, type ReactElement, type ReactNode, useContext, useRef } from "react";
7
7
  import { BiTrash } from "react-icons/bi";
8
8
  import { MdDragIndicator } from "react-icons/md";
9
+ import { buttonRecipe } from "./Button";
9
10
 
10
11
  const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
11
12
 
@@ -101,7 +102,7 @@ const DragList = <V,>({ className, mode = "vertical", children, onChange, onRemo
101
102
  });
102
103
 
103
104
  return (
104
- <div className={clsx(`isolate flex gap-0`, { "flex-col": mode === "vertical" }, className)}>
105
+ <div className={cn("isolate flex gap-0", mode === "vertical" && "flex-col", className)}>
105
106
  {springs.map(({ zIndex, shadow, movement, scale }, i) => (
106
107
  <animated.div
107
108
  ref={(el: HTMLDivElement | null) => {
@@ -110,6 +111,7 @@ const DragList = <V,>({ className, mode = "vertical", children, onChange, onRemo
110
111
  key={i}
111
112
  style={{
112
113
  zIndex,
114
+
113
115
  boxShadow: shadow.to((s) => `rgba(0, 0, 0, 0.15) 0px ${s}px ${2 * s}px 0px`),
114
116
  scale,
115
117
  ...(mode === "vertical" ? { y: movement } : { x: movement }),
@@ -140,7 +142,7 @@ DragList.Cursor = ({ className, children }: Cursor) => {
140
142
  const { bind } = useDragList();
141
143
  return (
142
144
  <div
143
- className={clsx("cursor-grab duration-200 hover:scale-[1.01] hover:opacity-70 hover:shadow-xl", className)}
145
+ className={cn("cursor-grab duration-200 hover:scale-[1.01] hover:opacity-70 hover:shadow-xl", className)}
144
146
  {...bind()}
145
147
  >
146
148
  {children}
@@ -168,7 +170,7 @@ const Item = ({
168
170
  }: ItemProps) => {
169
171
  const { onRemove } = useDragList();
170
172
  return (
171
- <div className={clsx("flex w-full items-center gap-2", className)}>
173
+ <div className={cn("flex w-full items-center gap-2", className)}>
172
174
  {cursor ? (
173
175
  <DraggableList.Cursor className={cursorClassName}>
174
176
  <MdDragIndicator className="text-xl" />
@@ -177,7 +179,10 @@ const Item = ({
177
179
  {children}
178
180
  {removable && (
179
181
  <button
180
- className={clsx("btn btn-xs btn-error btn-square btn-outline", removeClassName)}
182
+ className={buttonRecipe({ variant: "outline", size: "xs" }, [
183
+ "size-6 border-destructive p-0 text-destructive hover:bg-destructive hover:text-destructive-foreground",
184
+ removeClassName,
185
+ ])}
181
186
  onClick={() => {
182
187
  onRemove(value);
183
188
  }}
package/ui/Dropdown.tsx CHANGED
@@ -1,7 +1,8 @@
1
1
  "use client";
2
- import { clsx } from "akanjs/client";
3
- import { type ReactNode, useState } from "react";
2
+ import { cn } from "akanjs/client";
3
+ import { type ReactNode, useEffect, useRef, useState } from "react";
4
4
 
5
+ import { buttonRecipe } from "./Button";
5
6
  import { createOverridable } from "./UiOverride";
6
7
 
7
8
  export interface DropdownProps {
@@ -19,35 +20,41 @@ export interface DropdownProps {
19
20
 
20
21
  export const DefaultDropdown = ({ value, content, className, buttonClassName, dropdownClassName }: DropdownProps) => {
21
22
  const [opened, setOpened] = useState(false);
23
+ const ref = useRef<HTMLDivElement>(null);
24
+ useEffect(() => {
25
+ if (!opened) return;
26
+ const onClick = (e: MouseEvent) => {
27
+ if (ref.current && !ref.current.contains(e.target as Node)) setOpened(false);
28
+ };
29
+ document.addEventListener("mousedown", onClick);
30
+ return () => {
31
+ document.removeEventListener("mousedown", onClick);
32
+ };
33
+ }, [opened]);
22
34
  return (
23
- <div
24
- onClick={() => {
25
- setOpened(true);
26
- }}
27
- className={clsx("dropdown dropdown-end", className)}
28
- >
29
- <label
30
- tabIndex={0}
31
- className={clsx("btn flex", buttonClassName)}
35
+ <div ref={ref} className={cn("relative inline-block", className)}>
36
+ <button
37
+ type="button"
38
+ className={buttonRecipe({ variant: "ghost" }, ["flex", buttonClassName])}
32
39
  onClick={() => {
33
- setOpened(true);
40
+ setOpened((o) => !o);
34
41
  }}
35
42
  >
36
43
  {value}
37
- </label>
38
- <ul
39
- tabIndex={0}
40
- onClick={() => {
41
- if (opened) setOpened(false);
42
- }}
43
- className={clsx(
44
- "md:scrollbar-thin md:scrollbar-thumb-rounded-md md:scrollbar-thumb-gray-300 md:scrollbar-track-transparent z-[100] grid max-h-52 gap-2 overflow-auto whitespace-nowrap rounded-md bg-base-100 pr-3 shadow-sm",
45
- opened ? "dropdown-content size-fit p-1" : "size-0 overflow-hidden",
46
- dropdownClassName,
47
- )}
48
- >
49
- {content}
50
- </ul>
44
+ </button>
45
+ {opened ? (
46
+ <ul
47
+ onClick={() => {
48
+ setOpened(false);
49
+ }}
50
+ className={cn(
51
+ "absolute right-0 z-[100] mt-1 grid max-h-52 gap-2 overflow-auto whitespace-nowrap rounded-md bg-popover p-1 pr-3 text-popover-foreground shadow-md",
52
+ dropdownClassName,
53
+ )}
54
+ >
55
+ {content}
56
+ </ul>
57
+ ) : null}
51
58
  </div>
52
59
  );
53
60
  };
package/ui/Empty.tsx CHANGED
@@ -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 type { ReactNode } from "react";
4
4
  import { AiOutlineMeh } from "react-icons/ai";
5
5
 
@@ -20,11 +20,11 @@ export const DefaultEmpty = ({ className = "", description, children, minHeight
20
20
  const { l } = usePage();
21
21
  return (
22
22
  <div>
23
+ {/* minHeight is a runtime number, so it has to be a style prop: Tailwind extracts arbitrary values
24
+ from source text, so `min-h-[${minHeight}px]` compiles to no CSS and the prop is silently ignored. */}
23
25
  <div
24
- className={clsx(
25
- `min-h-[${minHeight}px] flex flex-col items-center justify-center gap-3 pt-6 pb-3 text-base-content/30`,
26
- className,
27
- )}
26
+ style={{ minHeight }}
27
+ className={cn("flex flex-col items-center justify-center gap-3 pt-6 pb-3 text-foreground/30", className)}
28
28
  >
29
29
  <AiOutlineMeh className="scale-150 text-4xl" />
30
30
  <p>{description ?? l("base.noData")}</p>