@sikka/hawa 0.1.17 → 0.1.20

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 (38) hide show
  1. package/README.md +2 -16
  2. package/dist/styles.css +78 -3
  3. package/es/elements/Button.d.ts +1 -1
  4. package/es/elements/HawaRadio.d.ts +1 -0
  5. package/es/elements/InterfaceSettings.d.ts +2 -0
  6. package/es/elements/Popover.d.ts +1 -0
  7. package/es/index.es.js +3 -3
  8. package/es/layout/AppLayout.d.ts +49 -0
  9. package/es/layout/Sidebar.d.ts +36 -0
  10. package/es/layout/Sidebar2.d.ts +20 -0
  11. package/es/layout/index.d.ts +2 -0
  12. package/lib/elements/Button.d.ts +1 -1
  13. package/lib/elements/HawaRadio.d.ts +1 -0
  14. package/lib/elements/InterfaceSettings.d.ts +2 -0
  15. package/lib/elements/Popover.d.ts +1 -0
  16. package/lib/index.js +3 -3
  17. package/lib/layout/AppLayout.d.ts +49 -0
  18. package/lib/layout/Sidebar.d.ts +36 -0
  19. package/lib/layout/Sidebar2.d.ts +20 -0
  20. package/lib/layout/index.d.ts +2 -0
  21. package/package.json +2 -1
  22. package/src/blocks/AuthForms/SignInBlock.tsx +0 -2
  23. package/src/blocks/AuthForms/SignInForm.tsx +1 -1
  24. package/src/elements/Button.tsx +1 -0
  25. package/src/elements/DropdownMenu.tsx +0 -2
  26. package/src/elements/HawaRadio.tsx +8 -2
  27. package/src/elements/InterfaceSettings.tsx +15 -2
  28. package/src/elements/Label.tsx +0 -2
  29. package/src/elements/Popover.tsx +28 -22
  30. package/src/elements/Select.tsx +0 -2
  31. package/src/elements/Tooltip.tsx +1 -106
  32. package/src/layout/AppLayout.tsx +445 -0
  33. package/src/layout/HawaAppLayoutSimplified.tsx +78 -98
  34. package/src/layout/Sidebar.tsx +224 -0
  35. package/src/layout/Sidebar2.tsx +77 -0
  36. package/src/layout/index.ts +3 -0
  37. package/src/styles.css +78 -3
  38. package/tailwind.config.js +20 -1
@@ -0,0 +1,445 @@
1
+ import React, { useEffect, useRef, useState } from "react"
2
+ import clsx from "clsx"
3
+ import useDiscloser from "../hooks/useDiscloser"
4
+ import useBreakpoint from "../hooks/useBreakpoint"
5
+ import { Button, DropdownMenu, Tooltip } from "../elements"
6
+ import { SidebarGroup, SidebarRoot } from "./Sidebar"
7
+
8
+ type AppLayoutTypes = {
9
+ /** The pages of the side drawer */
10
+ drawerItems: Item[]
11
+ // The direction of the layout
12
+ direction?: "rtl" | "ltr"
13
+ // The title of the current selected page, make sure it's the same as the drawerItem slug
14
+ currentPage: string
15
+ pageTitle?: string
16
+ logoSymbol?: any
17
+ logoLink?: string
18
+ logoText?: any
19
+ children?: any
20
+ topBar?: boolean
21
+ username?: string
22
+ email?: string
23
+ drawerSize?: "sm" | "md" | "large"
24
+ profileMenuItems?: ProfileItem[]
25
+ onSettingsClick?: () => void
26
+ DrawerFooterActions?: any
27
+ texts?: {
28
+ expandSidebar?: string
29
+ collapseSidebar?: string
30
+ }
31
+ }
32
+ type Item = {
33
+ value: string
34
+ label: string
35
+ icon?: any
36
+ subitems?: SubItem[]
37
+ onClick?: () => void
38
+ }
39
+ type SubItem = {
40
+ value: string
41
+ label: string
42
+ icon?: any
43
+ onClick?: () => void
44
+ }
45
+ type ProfileSubItem = {
46
+ label: string
47
+ value: string
48
+ highlighted?: boolean
49
+ }
50
+ type ProfileItem = {
51
+ label: string
52
+ value: string
53
+ highlighted?: boolean
54
+ subitems?: ProfileSubItem[] // Note the use of the optional modifier
55
+ }
56
+
57
+ export const AppLayout: React.FunctionComponent<AppLayoutTypes> = ({
58
+ direction = "rtl",
59
+ drawerSize = "md",
60
+ onSettingsClick,
61
+ DrawerFooterActions,
62
+ ...props
63
+ }) => {
64
+ const [openSideMenu, setOpenSideMenu] = useState(false)
65
+ const [openSubItem, setOpenSubitem] = useState("")
66
+ const { isOpen, onClose, onOpen } = useDiscloser(false)
67
+ const [collapsed, setCollapsed] = useState(false)
68
+
69
+ const [keepOpen, setKeepOpen] = useState(false)
70
+ const ref = useRef(null)
71
+ const isRTL = direction === "rtl"
72
+ let size
73
+ if (typeof window !== "undefined") {
74
+ size = useBreakpoint()
75
+ } else {
76
+ size = 1200
77
+ }
78
+ useEffect(() => {
79
+ const handleClickOutside = (event) => {
80
+ if (ref.current && !ref.current.contains(event.target) && !keepOpen) {
81
+ setOpenSideMenu(false)
82
+ }
83
+ }
84
+ document.addEventListener("click", handleClickOutside, true)
85
+ return () => {
86
+ document.removeEventListener("click", handleClickOutside, true)
87
+ }
88
+ }, [keepOpen])
89
+
90
+ let drawerDefaultStyle =
91
+ "fixed top-0 z-40 flex h-full flex-col justify-between overflow-x-clip bg-card transition-all"
92
+ //The width of the drawer when closed
93
+ let closeDrawerWidth = 56
94
+ //The width of the drawer when opened
95
+ let openDrawerWidth = 200
96
+ let drawerSizeStyle = {
97
+ opened: {
98
+ sm: "100",
99
+ md: openDrawerWidth,
100
+ lg: "250",
101
+ },
102
+ closed: {
103
+ sm: "56",
104
+ md: "56",
105
+ lg: "56",
106
+ },
107
+ }
108
+
109
+ let drawerSizeCondition =
110
+ size > 600 ? drawerSizeStyle[keepOpen ? "opened" : "closed"][drawerSize] : 0
111
+ return (
112
+ <div className="fixed left-0">
113
+ {props.topBar && (
114
+ <div
115
+ className={clsx(
116
+ "fixed left-0 right-0 top-0 z-30 flex h-14 w-full items-center justify-between bg-primary-foreground p-2",
117
+ isRTL ? "flex-row-reverse" : "flex-row"
118
+ )}
119
+ >
120
+ {/* Nav Side Of Navbar */}
121
+ {size > 600 ? (
122
+ <div
123
+ className={clsx(
124
+ "dark:text-white",
125
+ isRTL
126
+ ? [size > 600 ? "mr-14" : "mr-2", keepOpen ? "mr-40" : ""]
127
+ : [size > 600 ? "ml-14" : "ml-2", keepOpen ? "ml-40" : ""]
128
+ )}
129
+ style={
130
+ isRTL
131
+ ? {
132
+ marginRight: `${
133
+ drawerSizeStyle[keepOpen ? "opened" : "closed"][
134
+ drawerSize
135
+ ]
136
+ }px`,
137
+ }
138
+ : {
139
+ marginLeft: `${
140
+ drawerSizeStyle[keepOpen ? "opened" : "closed"][
141
+ drawerSize
142
+ ]
143
+ }px`,
144
+ }
145
+ }
146
+ >
147
+ {props.pageTitle}
148
+ </div>
149
+ ) : (
150
+ // Mobile Drawer Menu Button
151
+ <div
152
+ dir={direction}
153
+ className="flex items-center justify-center gap-0.5"
154
+ >
155
+ <div
156
+ onClick={() => setOpenSideMenu(true)}
157
+ className="z-40 mx-1 cursor-pointer rounded p-2 transition-all hover:bg-gray-100"
158
+ >
159
+ <svg
160
+ stroke="currentColor"
161
+ fill="currentColor"
162
+ stroke-width="0"
163
+ viewBox="0 0 20 20"
164
+ aria-hidden="true"
165
+ height="1.6em"
166
+ width="1.6em"
167
+ >
168
+ <path
169
+ fill-rule="evenodd"
170
+ d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
171
+ clip-rule="evenodd"
172
+ ></path>
173
+ </svg>
174
+ </div>
175
+ {/* Mobile Page Title */}
176
+ {props.pageTitle ? (
177
+ <div className="text-sm">{props.pageTitle}</div>
178
+ ) : (
179
+ <div></div>
180
+ )}
181
+ </div>
182
+ )}
183
+
184
+ <div
185
+ className={clsx(
186
+ "flex gap-2 dark:text-white",
187
+ isRTL ? "flex-row-reverse" : "flex-row"
188
+ )}
189
+ >
190
+ {/* User Info */}
191
+ {size > 600 ? (
192
+ <div
193
+ className={isRTL ? "text-left text-xs" : "text-right text-xs"}
194
+ >
195
+ <div className="font-bold">{props.username}</div>{" "}
196
+ <div>{props.email}</div>
197
+ </div>
198
+ ) : null}
199
+ {/* Profile Icon & Menu */}
200
+ <DropdownMenu
201
+ triggerClassname="mx-2"
202
+ align="end"
203
+ alignOffset={8}
204
+ side={"bottom"}
205
+ sideOffset={5}
206
+ direction={isRTL ? "rtl" : "ltr"}
207
+ trigger={
208
+ <div className="relative h-8 w-8 cursor-pointer overflow-clip rounded ring-1 ring-primary/30 dark:bg-gray-600">
209
+ <AvatarIcon />
210
+ </div>
211
+ }
212
+ items={props.profileMenuItems}
213
+ onItemSelect={(e) => console.log("selecting item ", e)}
214
+ />
215
+ </div>
216
+ </div>
217
+ )}
218
+ {/*
219
+ * ----------------------------------------------------------------------------------------------------
220
+ * Drawer Container
221
+ * ----------------------------------------------------------------------------------------------------
222
+ */}
223
+ <div
224
+ className={clsx(
225
+ "fixed top-0 z-40 flex h-full flex-col justify-between overflow-x-clip transition-all",
226
+ isRTL ? "right-0" : "left-0"
227
+ )}
228
+ style={{
229
+ width:
230
+ size > 600
231
+ ? openSideMenu
232
+ ? `${drawerSizeStyle["opened"][drawerSize]}px`
233
+ : `${drawerSizeStyle["closed"][drawerSize]}px`
234
+ : openSideMenu
235
+ ? `${drawerSizeStyle["opened"][drawerSize]}px`
236
+ : "0px",
237
+ }}
238
+ onMouseEnter={() => {
239
+ setOpenSideMenu(true)
240
+ setCollapsed((prev) => !prev)
241
+ }}
242
+ onMouseLeave={() => {
243
+ keepOpen ? setOpenSideMenu(true) : setOpenSideMenu(false)
244
+ setCollapsed((prev) => !prev)
245
+ }}
246
+ ref={ref}
247
+ >
248
+ {/*
249
+ * ----------------------------------------------------------------------------------------------------
250
+ * Drawer Header
251
+ * ----------------------------------------------------------------------------------------------------
252
+ */}
253
+ <div
254
+ dir={direction}
255
+ className={clsx(
256
+ "fixed z-50 mb-2 flex h-14 w-full flex-row items-center justify-center bg-primary-foreground transition-all"
257
+ )}
258
+ style={{
259
+ width:
260
+ size > 600
261
+ ? `${openSideMenu ? openDrawerWidth : 56}px`
262
+ : `${openSideMenu ? openDrawerWidth : 0}px`,
263
+ }}
264
+ >
265
+ {/*
266
+ * ----------------------------------------------------------------------------------------------------
267
+ * Full Logo
268
+ * ----------------------------------------------------------------------------------------------------
269
+ */}
270
+ <img
271
+ className={clsx(
272
+ "h-9 opacity-0 transition-all",
273
+ // isRTL ? "right-2.5" : "left-2.5",
274
+ !openSideMenu ? "invisible opacity-0" : "visible opacity-100"
275
+ // size > 600 ? "" : "right-4"
276
+ )}
277
+ // className={clsx(
278
+ // "fixed top-2.5 h-9 transition-all",
279
+ // isRTL ? "right-2.5" : "left-2.5",
280
+ // !openSideMenu ? "invisible opacity-0" : "visible opacity-100"
281
+ // )}
282
+ src={props.logoLink}
283
+ />
284
+ {/*
285
+ * ----------------------------------------------------------------------------------------------------
286
+ * Logo Symbol
287
+ * ----------------------------------------------------------------------------------------------------
288
+ */}
289
+ {size > 600 ? (
290
+ <img
291
+ className={clsx(
292
+ "fixed top-2.5 h-9 transition-all",
293
+ isRTL ? "right-2.5" : "left-2.5",
294
+ openSideMenu ? "invisible opacity-0" : "visible opacity-100"
295
+ )}
296
+ src={props.logoSymbol}
297
+ />
298
+ ) : null}
299
+ </div>
300
+ {/*
301
+ * ----------------------------------------------------------------------------------------------------
302
+ * Drawer Content Container
303
+ * ----------------------------------------------------------------------------------------------------
304
+ */}
305
+ <div
306
+ className={clsx(
307
+ // "no-scrollbar", TODO: make this optional to hide scrollbar or not
308
+ "fixed bottom-14 top-14 bg-primary-foreground p-2 py-2 transition-all",
309
+ // bg-yellow-400
310
+ openSideMenu ? "overflow-auto" : "overflow-hidden"
311
+ )}
312
+ style={{
313
+ height: "calc(100% - 112px)",
314
+ width:
315
+ size > 600
316
+ ? `${openSideMenu ? openDrawerWidth : 56}px`
317
+ : `${openSideMenu ? openDrawerWidth : 0}px`,
318
+ }}
319
+ >
320
+ {/*
321
+ * ----------------------------------------------------------------------------------------------------
322
+ * Drawer Items
323
+ * ----------------------------------------------------------------------------------------------------
324
+ */}
325
+
326
+ <SidebarRoot>
327
+ <SidebarGroup
328
+ isOpen={openSideMenu}
329
+ onItemClick={(values) => {
330
+ console.log("Clicked main item value:", values[0])
331
+ // setSelectedItem(values)
332
+ }}
333
+ onSubItemClick={(values) => {
334
+ console.log("Parent item value:", values[0])
335
+ console.log("Subitem value:", values[1])
336
+ // setSelectedItem(values)
337
+ }}
338
+ items={props.drawerItems}
339
+ />
340
+ </SidebarRoot>
341
+ </div>
342
+ {/*
343
+ * ----------------------------------------------------------------------------------------------------
344
+ * Drawer Footer
345
+ * ----------------------------------------------------------------------------------------------------
346
+ */}
347
+ <div
348
+ className={clsx(
349
+ "fixed bottom-0 flex h-14 w-full items-center justify-center gap-2 overflow-clip bg-primary-foreground transition-all",
350
+ direction === "rtl" ? "flex-row-reverse" : "flex-row"
351
+ )}
352
+ style={{
353
+ width:
354
+ size > 600
355
+ ? `${openSideMenu ? openDrawerWidth : 56}px`
356
+ : `${openSideMenu ? openDrawerWidth : 0}px`,
357
+ }}
358
+ >
359
+ {DrawerFooterActions && openSideMenu ? (
360
+ <>{DrawerFooterActions}</>
361
+ ) : null}
362
+
363
+ {/* Expand Button */}
364
+ {size > 600 && openSideMenu ? (
365
+ <Tooltip
366
+ side={"left"}
367
+ delayDuration={500}
368
+ content={
369
+ keepOpen
370
+ ? props.texts?.collapseSidebar || "Collapse Sidebar"
371
+ : props.texts?.expandSidebar || "Expand Sidebar"
372
+ }
373
+ >
374
+ <Button
375
+ variant="light"
376
+ onClick={() => setKeepOpen(!keepOpen)}
377
+ size="smallIcon"
378
+ >
379
+ <svg
380
+ className={clsx(
381
+ "h-6 w-6 shrink-0 text-primary transition-all disabled:bg-gray-200 ",
382
+ keepOpen
383
+ ? isRTL
384
+ ? "-rotate-90"
385
+ : "rotate-90"
386
+ : isRTL
387
+ ? "rotate-90"
388
+ : "-rotate-90"
389
+ )}
390
+ fill="currentColor"
391
+ viewBox="0 0 20 20"
392
+ >
393
+ <path
394
+ fillRule="evenodd"
395
+ d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
396
+ clipRule="evenodd"
397
+ ></path>
398
+ </svg>
399
+ </Button>
400
+ </Tooltip>
401
+ ) : null}
402
+ </div>
403
+ </div>
404
+ {/*
405
+ * ----------------------------------------------------------------------------------------------------
406
+ * Children Container
407
+ * ----------------------------------------------------------------------------------------------------
408
+ */}
409
+ <div
410
+ className="fixed overflow-y-auto"
411
+ style={
412
+ isRTL
413
+ ? {
414
+ height: `calc(100% - ${props.topBar ? "56" : "0"}px)`,
415
+ width: `calc(100% - ${drawerSizeCondition}px)`,
416
+ left: "0px",
417
+ top: props.topBar ? "56px" : "0px",
418
+ }
419
+ : {
420
+ height: `calc(100% - ${props.topBar ? "56" : "0"}px)`,
421
+ width: `calc(100% - ${drawerSizeCondition}px)`,
422
+ left: `${drawerSizeCondition}px`,
423
+ top: props.topBar ? "56px" : "0px",
424
+ }
425
+ }
426
+ >
427
+ {props.children}
428
+ </div>
429
+ </div>
430
+ )
431
+ }
432
+
433
+ const AvatarIcon = () => (
434
+ <svg
435
+ className="absolute -left-1 h-10 w-10 text-gray-400"
436
+ fill="currentColor"
437
+ viewBox="0 0 20 20"
438
+ >
439
+ <path
440
+ fillRule="evenodd"
441
+ d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
442
+ clipRule="evenodd"
443
+ ></path>
444
+ </svg>
445
+ )