uibee 3.2.0 → 3.3.1

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 (162) hide show
  1. package/dist/components/alert/alert.d.ts +15 -0
  2. package/dist/components/alert/alert.js +29 -0
  3. package/dist/components/badge/badge.d.ts +16 -0
  4. package/dist/components/badge/badge.js +26 -0
  5. package/dist/components/buttons/button.d.ts +25 -0
  6. package/dist/components/buttons/button.js +44 -0
  7. package/dist/components/code/code.d.ts +23 -0
  8. package/dist/components/code/code.js +39 -0
  9. package/dist/components/confirm/confirmPopup.d.ts +23 -0
  10. package/dist/components/confirm/confirmPopup.js +52 -0
  11. package/dist/components/container/accordion.d.ts +25 -0
  12. package/dist/components/container/accordion.js +41 -0
  13. package/dist/components/container/card.d.ts +13 -0
  14. package/dist/components/container/card.js +10 -0
  15. package/dist/components/container/expandableCard.d.ts +32 -0
  16. package/dist/components/container/expandableCard.js +70 -0
  17. package/dist/components/container/glassCard.d.ts +13 -0
  18. package/dist/components/container/glassCard.js +10 -0
  19. package/dist/components/container/highlight.d.ts +10 -0
  20. package/dist/components/container/highlight.js +10 -0
  21. package/dist/components/container/iconBubble.d.ts +25 -0
  22. package/dist/components/container/iconBubble.js +21 -0
  23. package/dist/components/container/leftBarPanel.d.ts +15 -0
  24. package/dist/components/container/leftBarPanel.js +10 -0
  25. package/dist/components/container/page.d.ts +15 -0
  26. package/dist/components/container/page.js +16 -0
  27. package/dist/components/container/pulseDot.d.ts +12 -0
  28. package/dist/components/container/pulseDot.js +23 -0
  29. package/dist/components/container/statCard.d.ts +18 -0
  30. package/dist/components/container/statCard.js +25 -0
  31. package/dist/components/container/tabs.d.ts +35 -0
  32. package/dist/components/container/tabs.js +36 -0
  33. package/dist/components/empty/emptyState.d.ts +19 -0
  34. package/dist/components/empty/emptyState.js +27 -0
  35. package/dist/components/footer/footer.d.ts +49 -0
  36. package/dist/components/footer/footer.js +109 -0
  37. package/dist/components/footer/loginContent.d.ts +18 -0
  38. package/dist/components/footer/loginContent.js +138 -0
  39. package/dist/components/inputs/checkbox.d.ts +20 -0
  40. package/dist/components/inputs/checkbox.js +76 -0
  41. package/dist/components/inputs/fileInput.d.ts +19 -0
  42. package/dist/components/inputs/fileInput.js +108 -0
  43. package/dist/components/inputs/input.d.ts +16 -0
  44. package/dist/components/inputs/input.js +157 -0
  45. package/dist/components/inputs/multiSelect.d.ts +37 -0
  46. package/dist/components/inputs/multiSelect.js +99 -0
  47. package/dist/components/inputs/radio.d.ts +20 -0
  48. package/dist/components/inputs/radio.js +70 -0
  49. package/dist/components/inputs/range.d.ts +14 -0
  50. package/dist/components/inputs/range.js +60 -0
  51. package/dist/components/inputs/searchInput.d.ts +11 -0
  52. package/dist/components/inputs/searchInput.js +56 -0
  53. package/dist/components/inputs/select.d.ts +42 -0
  54. package/dist/components/inputs/select.js +164 -0
  55. package/dist/components/inputs/shared/colorPickerPopup.js +293 -0
  56. package/dist/components/inputs/shared/dateTimePickerPopup.js +225 -0
  57. package/dist/components/inputs/shared/fieldWrapper.js +33 -0
  58. package/dist/components/inputs/shared/index.js +6 -0
  59. package/dist/components/inputs/shared/inputError.js +17 -0
  60. package/dist/components/inputs/shared/inputInfo.js +13 -0
  61. package/dist/components/inputs/shared/inputLabel.js +19 -0
  62. package/dist/components/inputs/switch.d.ts +14 -0
  63. package/dist/components/inputs/switch.js +37 -0
  64. package/dist/components/inputs/tagInput.d.ts +31 -0
  65. package/dist/components/inputs/tagInput.js +72 -0
  66. package/dist/components/inputs/textarea.d.ts +14 -0
  67. package/dist/components/inputs/textarea.js +76 -0
  68. package/dist/components/inputs/toggle.d.ts +21 -0
  69. package/dist/components/inputs/toggle.js +33 -0
  70. package/dist/components/login/loginPage.d.ts +15 -0
  71. package/dist/components/login/loginPage.js +80 -0
  72. package/dist/components/logo/logo.d.ts +9 -0
  73. package/dist/components/logo/logo.js +138 -0
  74. package/dist/components/logo/logoSmall.d.ts +4 -0
  75. package/dist/components/logo/logoSmall.js +56 -0
  76. package/dist/components/markdownrender/markdownRender.d.ts +16 -0
  77. package/dist/components/markdownrender/markdownRender.js +59 -0
  78. package/dist/components/modal/modal.d.ts +22 -0
  79. package/dist/components/modal/modal.js +59 -0
  80. package/dist/components/navbar/bubble.js +36 -0
  81. package/dist/components/navbar/navbar.d.ts +50 -0
  82. package/dist/components/navbar/navbar.js +99 -0
  83. package/dist/components/navbar/navbarDropdown.d.ts +15 -0
  84. package/dist/components/navbar/navbarDropdown.js +63 -0
  85. package/dist/components/navbar/navbarItem.d.ts +23 -0
  86. package/dist/components/navbar/navbarItem.js +38 -0
  87. package/dist/components/sidebar/sidebar.d.ts +34 -0
  88. package/dist/components/sidebar/sidebar.js +135 -0
  89. package/dist/components/spinner/spinner.d.ts +11 -0
  90. package/dist/components/spinner/spinner.js +16 -0
  91. package/dist/components/table/body.js +253 -0
  92. package/dist/components/table/constants.js +58 -0
  93. package/dist/components/table/empty.js +24 -0
  94. package/dist/components/table/format.js +39 -0
  95. package/dist/components/table/header.js +109 -0
  96. package/dist/components/table/menu.d.ts +18 -0
  97. package/dist/components/table/menu.js +76 -0
  98. package/dist/components/table/pagination.d.ts +14 -0
  99. package/dist/components/table/pagination.js +130 -0
  100. package/dist/components/table/skeleton.js +39 -0
  101. package/dist/components/table/table.d.ts +7 -0
  102. package/dist/components/table/table.js +213 -0
  103. package/dist/components/table/types.d.ts +52 -0
  104. package/dist/components/table/utils.js +9 -0
  105. package/dist/components/toast/toastItem.js +61 -0
  106. package/dist/components/toast/toaster.d.ts +13 -0
  107. package/dist/components/toast/toaster.js +117 -0
  108. package/dist/components/toggle/language.d.ts +10 -0
  109. package/dist/components/toggle/language.js +34 -0
  110. package/dist/components/toggle/theme.d.ts +8 -0
  111. package/dist/components/toggle/theme.js +135 -0
  112. package/dist/components/version/version.d.ts +13 -0
  113. package/dist/components/version/version.js +19 -0
  114. package/dist/components/vulnerability/constants.d.ts +4 -0
  115. package/dist/components/vulnerability/constants.js +17 -0
  116. package/dist/components/vulnerability/severityPill.d.ts +15 -0
  117. package/dist/components/vulnerability/severityPill.js +20 -0
  118. package/dist/{style.css → globals.css} +155 -226
  119. package/dist/hooks/useClickOutside.d.ts +6 -0
  120. package/dist/{useClickOutside-Cmp-RsP3.js → hooks/useClickOutside.js} +1 -1
  121. package/dist/hooks/useDarkMode.d.ts +4 -0
  122. package/dist/hooks/useDarkMode.js +19 -0
  123. package/dist/hooks/useVisibility.d.ts +7 -0
  124. package/dist/hooks/useVisibility.js +25 -0
  125. package/dist/icons/icons.d.ts +420 -0
  126. package/dist/{icons-lZYQ6Vlr.js → icons/icons.js} +1 -1
  127. package/dist/src/components/index.d.ts +51 -901
  128. package/dist/src/components/index.js +49 -4160
  129. package/dist/src/hooks/index.d.ts +3 -14
  130. package/dist/src/hooks/index.js +3 -42
  131. package/dist/src/icons/index.d.ts +1 -419
  132. package/dist/src/icons/index.js +1 -1
  133. package/dist/src/styles/index.js +1 -1
  134. package/dist/src/utils/index.d.ts +5 -37
  135. package/dist/src/utils/index.js +5 -124
  136. package/dist/utils/auth/callback.d.ts +14 -0
  137. package/dist/utils/auth/callback.js +53 -0
  138. package/dist/utils/auth/getDomain.js +6 -0
  139. package/dist/utils/auth/login.d.ts +12 -0
  140. package/dist/utils/auth/login.js +17 -0
  141. package/dist/utils/auth/logout.d.ts +10 -0
  142. package/dist/utils/auth/logout.js +20 -0
  143. package/dist/utils/auth/token.d.ts +10 -0
  144. package/dist/utils/auth/token.js +30 -0
  145. package/dist/utils/logoConsoleOutput/logoConsoleOutput.d.ts +4 -0
  146. package/dist/utils/logoConsoleOutput/logoConsoleOutput.js +11 -0
  147. package/package.json +1 -1
  148. package/src/components/buttons/button.tsx +2 -0
  149. package/src/components/footer/footer.tsx +65 -28
  150. package/src/components/index.ts +0 -1
  151. package/src/components/inputs/checkbox.tsx +2 -0
  152. package/src/components/inputs/input.tsx +2 -0
  153. package/src/components/inputs/radio.tsx +2 -0
  154. package/src/components/inputs/range.tsx +2 -0
  155. package/src/components/inputs/shared/colorPickerPopup.tsx +2 -0
  156. package/src/components/inputs/shared/dateTimePickerPopup.tsx +2 -0
  157. package/src/components/inputs/textarea.tsx +2 -0
  158. package/src/components/inputs/toggle.tsx +2 -0
  159. package/src/components/sidebar/sidebar.tsx +37 -10
  160. package/src/components/toast/toastItem.tsx +2 -0
  161. package/src/globals.css +6 -1
  162. package/tsdown.config.ts +1 -0
@@ -0,0 +1,14 @@
1
+ import { AuthCallbackProps } from "uibee/utils";
2
+
3
+ //#region src/utils/auth/callback.d.ts
4
+ declare function authCallback({
5
+ req,
6
+ tokenURL,
7
+ clientID,
8
+ clientSecret,
9
+ redirectPath,
10
+ userInfoURL,
11
+ tokenRedirectPath
12
+ }: AuthCallbackProps): Promise<Response>;
13
+ //#endregion
14
+ export { authCallback as default };
@@ -0,0 +1,53 @@
1
+ import { getDomain } from "./getDomain.js";
2
+ import { NextResponse } from "next/server.js";
3
+ //#region src/utils/auth/callback.ts
4
+ async function authCallback({ req, tokenURL, clientID, clientSecret, redirectPath, userInfoURL, tokenRedirectPath }) {
5
+ const domain = getDomain(req);
6
+ const searchParams = new URL(req.url).searchParams;
7
+ if (!searchParams) return NextResponse.json({ error: "No search parameters found." }, { status: 400 });
8
+ const code = searchParams.get("code");
9
+ if (!code) return NextResponse.json({ error: "No authorization code found." }, { status: 400 });
10
+ try {
11
+ const tokenResponse = await fetch(tokenURL, {
12
+ method: "POST",
13
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
14
+ body: new URLSearchParams({
15
+ client_id: clientID,
16
+ client_secret: clientSecret,
17
+ code,
18
+ redirect_uri: `${domain}${redirectPath}`,
19
+ grant_type: "authorization_code"
20
+ }).toString()
21
+ });
22
+ const tokenResponseBody = await tokenResponse.text();
23
+ if (!tokenResponse.ok) return new Response(JSON.stringify(`Failed to obtain token: ${tokenResponseBody}`), {
24
+ status: 500,
25
+ headers: { "Content-Type": "application/json" }
26
+ });
27
+ const token = JSON.parse(tokenResponseBody);
28
+ const userInfoResponse = await fetch(userInfoURL, { headers: { Authorization: `Bearer ${token.access_token}` } });
29
+ if (!userInfoResponse.ok) {
30
+ const userInfoError = await userInfoResponse.text();
31
+ return new Response(`No user info found: ${userInfoError}`, {
32
+ status: 500,
33
+ headers: { "Content-Type": "application/json" }
34
+ });
35
+ }
36
+ const userInfo = await userInfoResponse.json();
37
+ const redirectUrl = new URL(`${domain}${tokenRedirectPath}`);
38
+ redirectUrl.search = new URLSearchParams({
39
+ id: userInfo.sub,
40
+ name: userInfo.name,
41
+ username: userInfo.nickname,
42
+ email: userInfo.email,
43
+ groups: userInfo.groups.join(","),
44
+ access_token: token.access_token
45
+ }).toString();
46
+ return NextResponse.redirect(redirectUrl.toString());
47
+ } catch (err) {
48
+ console.error("Error during OAuth flow:", err.message);
49
+ return NextResponse.json({ error: "Authentication failed" }, { status: 500 });
50
+ }
51
+ }
52
+ //#endregion
53
+ export { authCallback as default };
@@ -0,0 +1,6 @@
1
+ //#region src/utils/auth/getDomain.ts
2
+ function getDomain(req) {
3
+ return `${req.headers.get("x-forwarded-proto") ?? new URL(req.url).protocol.replace(":", "")}://${req.headers.get("host") ?? req.headers.get("x-forwarded-host") ?? new URL(req.url).host}`;
4
+ }
5
+ //#endregion
6
+ export { getDomain };
@@ -0,0 +1,12 @@
1
+ import { NextResponse } from "next/server.js";
2
+ import { AuthLoginProps } from "uibee/utils";
3
+
4
+ //#region src/utils/auth/login.d.ts
5
+ declare function AuthLogin({
6
+ req,
7
+ clientID,
8
+ redirectPath,
9
+ authURL
10
+ }: AuthLoginProps): Promise<NextResponse<unknown>>;
11
+ //#endregion
12
+ export { AuthLogin as default };
@@ -0,0 +1,17 @@
1
+ import { getDomain } from "./getDomain.js";
2
+ import { NextResponse } from "next/server.js";
3
+ //#region src/utils/auth/login.ts
4
+ async function AuthLogin({ req, clientID, redirectPath, authURL }) {
5
+ const domain = getDomain(req);
6
+ const state = Math.random().toString(36).substring(5);
7
+ const authQueryParams = new URLSearchParams({
8
+ client_id: clientID,
9
+ redirect_uri: `${domain}${redirectPath}`,
10
+ response_type: "code",
11
+ scope: "openid profile email",
12
+ state
13
+ }).toString();
14
+ return NextResponse.redirect(`${authURL}?${authQueryParams}`);
15
+ }
16
+ //#endregion
17
+ export { AuthLogin as default };
@@ -0,0 +1,10 @@
1
+ import { NextResponse } from "next/server.js";
2
+ import { AuthLogoutProps } from "uibee/utils";
3
+
4
+ //#region src/utils/auth/logout.d.ts
5
+ declare function AuthLogout({
6
+ req,
7
+ path
8
+ }: AuthLogoutProps): Promise<NextResponse<unknown>>;
9
+ //#endregion
10
+ export { AuthLogout as default };
@@ -0,0 +1,20 @@
1
+ import { getDomain } from "./getDomain.js";
2
+ import { NextResponse } from "next/server.js";
3
+ //#region src/utils/auth/logout.ts
4
+ async function AuthLogout({ req, path }) {
5
+ const domain = getDomain(req);
6
+ const response = NextResponse.redirect(new URL(path || "/", domain));
7
+ [
8
+ "access_token",
9
+ "user_id",
10
+ "user_name",
11
+ "user_nickname",
12
+ "user_email",
13
+ "user_groups"
14
+ ].forEach((cookieName) => {
15
+ response.cookies.delete(cookieName);
16
+ });
17
+ return response;
18
+ }
19
+ //#endregion
20
+ export { AuthLogout as default };
@@ -0,0 +1,10 @@
1
+ import { NextResponse } from "next/server.js";
2
+ import { AuthTokenProps } from "uibee/utils";
3
+
4
+ //#region src/utils/auth/token.d.ts
5
+ declare function AuthToken({
6
+ req,
7
+ redirectPath
8
+ }: AuthTokenProps): Promise<NextResponse<unknown>>;
9
+ //#endregion
10
+ export { AuthToken as default };
@@ -0,0 +1,30 @@
1
+ import { getDomain } from "./getDomain.js";
2
+ import { NextResponse } from "next/server.js";
3
+ //#region src/utils/auth/token.ts
4
+ async function AuthToken({ req, redirectPath }) {
5
+ const domain = getDomain(req);
6
+ const url = new URL(req.url);
7
+ const token = url.searchParams.get("access_token");
8
+ const btg = url.searchParams.get("btg");
9
+ const redirectCookie = req.cookies.get("redirect_after_login");
10
+ const redirect = redirectCookie ? redirectCookie.value : redirectPath || "/";
11
+ if (!token) return NextResponse.json({ error: "No access token provided" }, { status: 400 });
12
+ if (btg) return NextResponse.redirect(new URL(redirect, domain));
13
+ const accessToken = url.searchParams.get("access_token");
14
+ const userID = url.searchParams.get("id");
15
+ const username = url.searchParams.get("name");
16
+ const userNickname = url.searchParams.get("username");
17
+ const userEmail = url.searchParams.get("email");
18
+ const userGroups = url.searchParams.get("groups");
19
+ const response = NextResponse.redirect(new URL(redirect, domain));
20
+ response.cookies.set("access_token", accessToken);
21
+ response.cookies.set("user_id", userID);
22
+ response.cookies.set("user_name", username);
23
+ response.cookies.set("user_nickname", userNickname);
24
+ response.cookies.set("user_email", userEmail);
25
+ response.cookies.set("user_groups", userGroups);
26
+ if (redirectCookie) response.cookies.delete("redirect_after_login");
27
+ return response;
28
+ }
29
+ //#endregion
30
+ export { AuthToken as default };
@@ -0,0 +1,4 @@
1
+ //#region src/utils/logoConsoleOutput/logoConsoleOutput.d.ts
2
+ declare function LogoConsoleOutput(): void;
3
+ //#endregion
4
+ export { LogoConsoleOutput };
@@ -0,0 +1,11 @@
1
+ //#region src/utils/logoConsoleOutput/logoConsoleOutput.ts
2
+ function LogoConsoleOutput() {
3
+ const ua = navigator.userAgent;
4
+ if (!ua.includes("Chrome") && !ua.includes("Firefox") && !ua.includes("Safari")) {
5
+ console.log("Login - Linjeforeningen for IT");
6
+ return;
7
+ }
8
+ console.log("%c███████╗ ███████╗\n██╔════╝ ╚════██║\n██║%c ██╗ ██████╗ ██████╗ ██╗███╗ ██╗ %c██║\n╚═╝%c ██║ ██╔═══██╗██╔════╝ ██║████╗ ██║ %c╚═╝%c\n ██║ ██║ ██║██║ ███╗██║██╔██╗ ██║\n ██║ ██║ ██║██║ ██║██║██║╚██╗██║\n%c██╗%c ██████╗╚██████╔╝╚██████╔╝██║██║ ╚████║ %c██╗\n██║%c ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝╚═╝ ╚═══╝ %c██║\n███████╗%c %c███████║\n╚══════╝ ╚══════╝%c\n\n - Laget av TekKom med 🍕 og ❤️", "color: #fd8738", "", "color: #fd8738", "", "color: #fd8738", "", "color: #fd8738", "", "color: #fd8738", "", "color: #fd8738", "", "color: #fd8738", "");
9
+ }
10
+ //#endregion
11
+ export { LogoConsoleOutput };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uibee",
3
- "version": "3.2.0",
3
+ "version": "3.3.1",
4
4
  "description": "Shared components, functions and hooks for reuse across Login projects",
5
5
  "homepage": "https://github.com/Login-Linjeforening-for-IT/uibee#readme",
6
6
  "bugs": {
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import Link from 'next/link'
2
4
  import type { JSX } from 'react'
3
5
 
@@ -1,4 +1,5 @@
1
1
  import type { ReactNode } from 'react'
2
+ import Link from 'next/link'
2
3
  import VersionTag from '@components/version/version'
3
4
 
4
5
  export type Lang = 'no' | 'en'
@@ -31,6 +32,41 @@ function t(s: BilingualString, lang: Lang): string {
31
32
  return typeof s === 'string' ? s : s[lang]
32
33
  }
33
34
 
35
+ function Column({ col, lang }: { col: FooterColumn; lang: Lang }) {
36
+ return (
37
+ <div>
38
+ <h4 className='pb-2 text-sm font-medium tracking-widest text-login-100'>
39
+ {t(col.heading, lang)}
40
+ </h4>
41
+ {col.items.map((item, j) =>
42
+ item.href ? (
43
+ item.href.startsWith('/') ? (
44
+ <Link
45
+ key={j}
46
+ className='link--underscore-hover block text-login-50'
47
+ href={item.href}
48
+ >
49
+ {t(item.label, lang)}
50
+ </Link>
51
+ ) : (
52
+ <a
53
+ key={j}
54
+ className='link--underscore-hover block text-login-50'
55
+ href={item.href}
56
+ >
57
+ {t(item.label, lang)}
58
+ </a>
59
+ )
60
+ ) : (
61
+ <p key={j} className='text-login-50'>
62
+ {t(item.label, lang)}
63
+ </p>
64
+ )
65
+ )}
66
+ </div>
67
+ )
68
+ }
69
+
34
70
  export default function Footer({
35
71
  logo,
36
72
  sponsor,
@@ -42,47 +78,49 @@ export default function Footer({
42
78
  className,
43
79
  }: FooterProps) {
44
80
  const year = new Date().getFullYear()
81
+ const [firstColumn, ...restColumns] = columns ?? []
45
82
  const hasRight = (columns && columns.length > 0) || (socialLinks && socialLinks.length > 0)
46
83
 
47
84
  return (
48
- <div className={`mt-24 bg-login-950 md:mt-40 ${className || ''}`}>
85
+ <div className={`max-md:mt-24 bg-login-950 md:mt-40 ${className || ''}`}>
49
86
  <div className={`
50
- mx-auto w-full px-4 pt-16 pb-24 md:max-w-304 md:px-12 md:pt-20 md:pb-4
87
+ mx-auto w-full pb-4 max-md:px-4 max-md:pt-16 md:max-w-304 md:px-12 md:pt-20
51
88
  ${hasRight ? 'md:grid md:grid-cols-[18rem_1fr] md:gap-x-12' : ''}
52
89
  `}>
53
- <div className='mx-auto grid w-full max-w-60 gap-16 md:row-span-2 md:max-w-72 md:gap-20'>
90
+ <div className='mx-auto grid w-full max-md:max-w-60 max-md:gap-16 md:row-span-2 md:max-w-72 md:gap-20'>
54
91
  <div className='block w-full'>{logo}</div>
55
92
  {sponsor && (
56
93
  <div>
57
94
  <div className='block w-full'>{sponsor.node}</div>
58
95
  {sponsor.label && (
59
- <p className='pt-8 text-center text-login-100'>{t(sponsor.label, lang)}</p>
96
+ <p className='pt-8 text-center text-login-100'>
97
+ {t(sponsor.label, lang)}
98
+ </p>
60
99
  )}
61
100
  </div>
62
101
  )}
63
102
  </div>
64
103
 
65
- {columns && columns.length > 0 && (
104
+ {firstColumn && (
105
+ <div className='
106
+ grid w-full self-start max-md:mt-16
107
+ max-sm:max-w-60 sm:max-md:max-w-88
108
+ md:col-start-2 md:row-start-1 md:max-w-136 md:justify-self-center
109
+ '>
110
+ <Column col={firstColumn} lang={lang} />
111
+ </div>
112
+ )}
113
+
114
+ {restColumns.length > 0 && (
66
115
  <div className={`
67
- mt-12 grid w-full max-w-60 gap-8
68
- sm:max-w-88 sm:justify-items-end sm:justify-self-end
69
- md:col-start-2 md:row-start-1 md:mt-0 md:max-w-136 md:gap-0 md:justify-self-end
70
- ${columns.length > 1 ? 'sm:grid-cols-2' : ''}
116
+ grid w-full self-start max-md:mt-16 max-md:gap-8
117
+ max-sm:max-w-60 sm:max-md:max-w-88 sm:justify-items-end sm:justify-self-end
118
+ md:col-start-2 md:row-start-1 md:max-w-136 md:justify-self-end
119
+ ${restColumns.length > 1 ? 'sm:grid-cols-2' : ''}
71
120
  `}>
72
- {columns.map((col, i) => (
121
+ {restColumns.map((col, i) => (
73
122
  <div key={i} className='sm:justify-self-center md:justify-self-end'>
74
- <h4 className='pb-2 text-sm font-medium tracking-widest text-login-100'>
75
- {t(col.heading, lang)}
76
- </h4>
77
- {col.items.map((item, j) =>
78
- item.href ? (
79
- <a key={j} className='link--underscore-hover block' href={item.href}>
80
- {t(item.label, lang)}
81
- </a>
82
- ) : (
83
- <p key={j}>{t(item.label, lang)}</p>
84
- )
85
- )}
123
+ <Column col={col} lang={lang} />
86
124
  </div>
87
125
  ))}
88
126
  </div>
@@ -90,11 +128,12 @@ export default function Footer({
90
128
 
91
129
  {socialLinks && socialLinks.length > 0 && (
92
130
  <div className='md:col-start-2 md:row-start-2 md:justify-self-end'>
93
- <div className='mx-auto mt-20 mb-12 flex w-fit flex-wrap justify-center gap-6'>
131
+ <div className='mx-auto mt-20 mb-12 grid w-fit gap-6 max-sm:grid-cols-3 sm:grid-flow-col'>
94
132
  {socialLinks.map((link, i) => (
95
133
  <a
96
134
  key={i}
97
- className={`block size-8 text-login-100 transition-all duration-200 group
135
+ className={`group mx-auto flex h-8 w-8 items-center justify-center
136
+ text-login-100 transition-all duration-200
98
137
  ${link.hoverClass ?? 'hover:text-login-50'}`}
99
138
  title={link.title}
100
139
  href={link.href}
@@ -108,11 +147,9 @@ export default function Footer({
108
147
  </div>
109
148
  )}
110
149
 
111
- <div className='mt-16 flex flex-col items-start gap-4 md:col-span-2 md:row-start-3 md:mt-24 md:grid
112
- md:grid-cols-[auto_min-content] md:items-end md:gap-8'
113
- >
150
+ <div className='mt-24 grid grid-cols-[auto_min-content] items-end gap-8 md:col-span-2 md:row-start-3'>
114
151
  <p className='text-xs wrap-break-word text-login-100'>
115
- Copyright © {year} {t(copyright, lang)}
152
+ {lang === 'no' ? 'Opphavsrett' : 'Copyright'} © {year} {t(copyright, lang)}
116
153
  </p>
117
154
  {version && (
118
155
  <VersionTag version={version.tag} url={version.href} />
@@ -1,4 +1,3 @@
1
- 'use client'
2
1
  export { default as Input } from './inputs/input'
3
2
  export { default as SearchInput } from './inputs/searchInput'
4
3
  export { default as Textarea } from './inputs/textarea'
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import { Check } from 'lucide-react'
2
4
  import { FieldWrapper, InputLabel } from './shared'
3
5
 
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import React, { type ChangeEvent, type JSX, useRef, useState, useId } from 'react'
2
4
  import { Calendar, Clock } from 'lucide-react'
3
5
  import { FieldWrapper } from './shared'
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import { FieldWrapper, InputLabel } from './shared'
2
4
 
3
5
  export type RadioOption = {
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import { FieldWrapper } from './shared'
2
4
 
3
5
  export type RangeProps = Omit<React.ComponentProps<'input'>, 'name'> & {
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import { type JSX, useState, useEffect, useRef } from 'react'
2
4
 
3
5
  export type ColorPickerPopupProps = {
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import { useState, useEffect } from 'react'
2
4
  import { ChevronLeft, ChevronRight } from 'lucide-react'
3
5
 
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import { useState } from 'react'
2
4
  import { Eye, Pencil } from 'lucide-react'
3
5
  import { FieldWrapper } from './shared'
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  type ToggleOption<T> = {
2
4
  value: T
3
5
  label?: string
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import { useState, type ElementType, type ReactNode } from 'react'
4
- import { usePathname } from 'next/navigation'
4
+ import { usePathname, useSearchParams } from 'next/navigation'
5
5
  import Link from 'next/link'
6
6
  import { ChevronLeft, ChevronRight } from 'lucide-react'
7
7
 
@@ -20,23 +20,49 @@ export type SidebarItem = {
20
20
 
21
21
  type SidebarProps = {
22
22
  items: SidebarItem[]
23
- header?: ReactNode
23
+ header?: ReactNode | ((expanded: boolean) => ReactNode)
24
24
  bottomAction?: (expanded: boolean) => ReactNode
25
25
  mobile?: boolean
26
26
  initialExpanded?: boolean
27
+ onExpandedChange?: (expanded: boolean) => void
28
+ className?: string
27
29
  }
28
30
 
29
31
  const ITEM_HEIGHT = 48
30
32
  const GAP = 4
31
33
  const SUB_STRIDE = 40
32
34
 
33
- export default function Sidebar({ items, header, bottomAction, mobile = false, initialExpanded = true }: SidebarProps) {
35
+ export default function Sidebar({
36
+ items,
37
+ header,
38
+ bottomAction,
39
+ mobile = false,
40
+ initialExpanded = true,
41
+ onExpandedChange,
42
+ className = '',
43
+ }: SidebarProps) {
34
44
  const [expanded, setExpanded] = useState(initialExpanded)
35
45
  const pathname = usePathname()
46
+ const searchParams = useSearchParams()
47
+ const fullPath = pathname + (searchParams.toString() ? `?${searchParams.toString()}` : '')
48
+
49
+ function toggleExpanded() {
50
+ const next = !expanded
51
+ setExpanded(next)
52
+ onExpandedChange?.(next)
53
+ }
54
+
55
+ function isSubActive(sub: SidebarSubItem) {
56
+ return sub.path.includes('?')
57
+ ? fullPath.startsWith(sub.path)
58
+ : pathname === sub.path
59
+ }
36
60
 
37
61
  function isItemActive(item: SidebarItem) {
38
62
  return pathname === item.path ||
39
- !!(item.items?.some(sub => pathname === sub.path || pathname.startsWith(sub.path + '/')))
63
+ !!(item.items?.some(sub => sub.path.includes('?')
64
+ ? fullPath.startsWith(sub.path)
65
+ : pathname === sub.path || pathname.startsWith(sub.path + '/')))
40
66
  }
41
67
 
42
68
  const activeIndex = items.findIndex(isItemActive)
@@ -49,6 +75,7 @@ export default function Sidebar({ items, header, bottomAction, mobile = false, i
49
75
  flex flex-col border-r border-login-100/10 bg-login-900
50
76
  transition-all duration-300 ease-in-out
51
77
  ${mobile ? 'w-full' : `h-full ${expanded ? 'w-64' : 'w-20'}`}
78
+ ${className}
52
79
  `}>
53
80
  {!mobile && (
54
81
  <div className={`relative mb-2 p-4 transition-all duration-300 ${expanded ? 'h-16' : 'h-20'}`}>
@@ -57,11 +84,11 @@ export default function Sidebar({ items, header, bottomAction, mobile = false, i
57
84
  absolute top-4 flex items-center transition-all duration-300
58
85
  ${expanded ? 'left-4 gap-3' : 'left-1/2 -translate-x-1/2 gap-0'}
59
86
  `}>
60
- {header}
87
+ {typeof header === 'function' ? header(expanded) : header}
61
88
  </div>
62
89
  )}
63
90
  <button
64
- onClick={() => setExpanded(!expanded)}
91
+ onClick={toggleExpanded}
65
92
  className={`
66
93
  absolute cursor-pointer rounded-lg p-1.5
67
94
  text-login-200 transition-all duration-300 hover:bg-login-800
@@ -75,7 +102,7 @@ export default function Sidebar({ items, header, bottomAction, mobile = false, i
75
102
 
76
103
  {mobile && <div className='h-4' />}
77
104
 
78
- <div className='relative flex flex-1 flex-col gap-1 overflow-x-hidden overflow-y-auto px-3'>
105
+ <div className='relative flex flex-1 flex-col gap-1 overflow-x-hidden overflow-y-auto px-3 [scrollbar-width:none]'>
79
106
  {activeIndex >= 0 && (
80
107
  <span
81
108
  aria-hidden
@@ -87,7 +114,7 @@ export default function Sidebar({ items, header, bottomAction, mobile = false, i
87
114
  const isActive = isItemActive(item)
88
115
  const Icon = item.icon
89
116
  const activeSubIndex = item.items
90
- ? item.items.findIndex(sub => pathname === sub.path || pathname.startsWith(sub.path + '/'))
117
+ ? item.items.findIndex(isSubActive)
91
118
  : -1
92
119
 
93
120
  return (
@@ -142,7 +169,7 @@ export default function Sidebar({ items, header, bottomAction, mobile = false, i
142
169
  />
143
170
  )}
144
171
  {item.items.map((sub, subIndex) => {
145
- const isSubActive = pathname === sub.path || pathname.startsWith(sub.path + '/')
172
+ const subActive = isSubActive(sub)
146
173
  return (
147
174
  <Link
148
175
  key={`${index}-${subIndex}`}
@@ -150,7 +177,7 @@ export default function Sidebar({ items, header, bottomAction, mobile = false, i
150
177
  className={`
151
178
  relative z-10 rounded-lg p-2 text-sm
152
179
  transition-all duration-200
153
- ${isSubActive
180
+ ${subActive
154
181
  ? 'text-login'
155
182
  : 'text-login-300 hover:bg-login-800/30 hover:text-login-100'
156
183
  }
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import { AlertCircle, AlertTriangle, CheckCircle, Info, X } from 'lucide-react'
2
4
  import { useEffect, useLayoutEffect, useRef, useState } from 'react'
3
5
  import type { ToastType, ToastProps } from 'uibee/components'
package/src/globals.css CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  @config '../tailwind.config.ts';
4
4
 
5
+ /* Apply hover styles on :hover directly instead of gating them behind
6
+ @media (hover: hover), which never matches on devices reporting hover: none. */
7
+ @custom-variant hover (&:hover);
8
+ @custom-variant group-hover (&:is(:where(.group):hover *));
9
+
5
10
  @theme {
6
11
  --breakpoint-100px: 100px;
7
12
  --breakpoint-200px: 200px;
@@ -35,7 +40,7 @@
35
40
  --color-login-100: #b0b0b0;
36
41
  --color-login-75: #e4e4e4;
37
42
  --color-login-50: #ededed;
38
-
43
+
39
44
  --color-login-text: #ededed;
40
45
  }
41
46
 
package/tsdown.config.ts CHANGED
@@ -13,6 +13,7 @@ export default defineConfig({
13
13
  outExtensions: () => ({ js: '.js', dts: '.d.ts' }),
14
14
  dts: true,
15
15
  clean: true,
16
+ unbundle: true,
16
17
  deps: {
17
18
  onlyBundle: false,
18
19
  },