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.
- package/dist/components/alert/alert.d.ts +15 -0
- package/dist/components/alert/alert.js +29 -0
- package/dist/components/badge/badge.d.ts +16 -0
- package/dist/components/badge/badge.js +26 -0
- package/dist/components/buttons/button.d.ts +25 -0
- package/dist/components/buttons/button.js +44 -0
- package/dist/components/code/code.d.ts +23 -0
- package/dist/components/code/code.js +39 -0
- package/dist/components/confirm/confirmPopup.d.ts +23 -0
- package/dist/components/confirm/confirmPopup.js +52 -0
- package/dist/components/container/accordion.d.ts +25 -0
- package/dist/components/container/accordion.js +41 -0
- package/dist/components/container/card.d.ts +13 -0
- package/dist/components/container/card.js +10 -0
- package/dist/components/container/expandableCard.d.ts +32 -0
- package/dist/components/container/expandableCard.js +70 -0
- package/dist/components/container/glassCard.d.ts +13 -0
- package/dist/components/container/glassCard.js +10 -0
- package/dist/components/container/highlight.d.ts +10 -0
- package/dist/components/container/highlight.js +10 -0
- package/dist/components/container/iconBubble.d.ts +25 -0
- package/dist/components/container/iconBubble.js +21 -0
- package/dist/components/container/leftBarPanel.d.ts +15 -0
- package/dist/components/container/leftBarPanel.js +10 -0
- package/dist/components/container/page.d.ts +15 -0
- package/dist/components/container/page.js +16 -0
- package/dist/components/container/pulseDot.d.ts +12 -0
- package/dist/components/container/pulseDot.js +23 -0
- package/dist/components/container/statCard.d.ts +18 -0
- package/dist/components/container/statCard.js +25 -0
- package/dist/components/container/tabs.d.ts +35 -0
- package/dist/components/container/tabs.js +36 -0
- package/dist/components/empty/emptyState.d.ts +19 -0
- package/dist/components/empty/emptyState.js +27 -0
- package/dist/components/footer/footer.d.ts +49 -0
- package/dist/components/footer/footer.js +109 -0
- package/dist/components/footer/loginContent.d.ts +18 -0
- package/dist/components/footer/loginContent.js +138 -0
- package/dist/components/inputs/checkbox.d.ts +20 -0
- package/dist/components/inputs/checkbox.js +76 -0
- package/dist/components/inputs/fileInput.d.ts +19 -0
- package/dist/components/inputs/fileInput.js +108 -0
- package/dist/components/inputs/input.d.ts +16 -0
- package/dist/components/inputs/input.js +157 -0
- package/dist/components/inputs/multiSelect.d.ts +37 -0
- package/dist/components/inputs/multiSelect.js +99 -0
- package/dist/components/inputs/radio.d.ts +20 -0
- package/dist/components/inputs/radio.js +70 -0
- package/dist/components/inputs/range.d.ts +14 -0
- package/dist/components/inputs/range.js +60 -0
- package/dist/components/inputs/searchInput.d.ts +11 -0
- package/dist/components/inputs/searchInput.js +56 -0
- package/dist/components/inputs/select.d.ts +42 -0
- package/dist/components/inputs/select.js +164 -0
- package/dist/components/inputs/shared/colorPickerPopup.js +293 -0
- package/dist/components/inputs/shared/dateTimePickerPopup.js +225 -0
- package/dist/components/inputs/shared/fieldWrapper.js +33 -0
- package/dist/components/inputs/shared/index.js +6 -0
- package/dist/components/inputs/shared/inputError.js +17 -0
- package/dist/components/inputs/shared/inputInfo.js +13 -0
- package/dist/components/inputs/shared/inputLabel.js +19 -0
- package/dist/components/inputs/switch.d.ts +14 -0
- package/dist/components/inputs/switch.js +37 -0
- package/dist/components/inputs/tagInput.d.ts +31 -0
- package/dist/components/inputs/tagInput.js +72 -0
- package/dist/components/inputs/textarea.d.ts +14 -0
- package/dist/components/inputs/textarea.js +76 -0
- package/dist/components/inputs/toggle.d.ts +21 -0
- package/dist/components/inputs/toggle.js +33 -0
- package/dist/components/login/loginPage.d.ts +15 -0
- package/dist/components/login/loginPage.js +80 -0
- package/dist/components/logo/logo.d.ts +9 -0
- package/dist/components/logo/logo.js +138 -0
- package/dist/components/logo/logoSmall.d.ts +4 -0
- package/dist/components/logo/logoSmall.js +56 -0
- package/dist/components/markdownrender/markdownRender.d.ts +16 -0
- package/dist/components/markdownrender/markdownRender.js +59 -0
- package/dist/components/modal/modal.d.ts +22 -0
- package/dist/components/modal/modal.js +59 -0
- package/dist/components/navbar/bubble.js +36 -0
- package/dist/components/navbar/navbar.d.ts +50 -0
- package/dist/components/navbar/navbar.js +99 -0
- package/dist/components/navbar/navbarDropdown.d.ts +15 -0
- package/dist/components/navbar/navbarDropdown.js +63 -0
- package/dist/components/navbar/navbarItem.d.ts +23 -0
- package/dist/components/navbar/navbarItem.js +38 -0
- package/dist/components/sidebar/sidebar.d.ts +34 -0
- package/dist/components/sidebar/sidebar.js +135 -0
- package/dist/components/spinner/spinner.d.ts +11 -0
- package/dist/components/spinner/spinner.js +16 -0
- package/dist/components/table/body.js +253 -0
- package/dist/components/table/constants.js +58 -0
- package/dist/components/table/empty.js +24 -0
- package/dist/components/table/format.js +39 -0
- package/dist/components/table/header.js +109 -0
- package/dist/components/table/menu.d.ts +18 -0
- package/dist/components/table/menu.js +76 -0
- package/dist/components/table/pagination.d.ts +14 -0
- package/dist/components/table/pagination.js +130 -0
- package/dist/components/table/skeleton.js +39 -0
- package/dist/components/table/table.d.ts +7 -0
- package/dist/components/table/table.js +213 -0
- package/dist/components/table/types.d.ts +52 -0
- package/dist/components/table/utils.js +9 -0
- package/dist/components/toast/toastItem.js +61 -0
- package/dist/components/toast/toaster.d.ts +13 -0
- package/dist/components/toast/toaster.js +117 -0
- package/dist/components/toggle/language.d.ts +10 -0
- package/dist/components/toggle/language.js +34 -0
- package/dist/components/toggle/theme.d.ts +8 -0
- package/dist/components/toggle/theme.js +135 -0
- package/dist/components/version/version.d.ts +13 -0
- package/dist/components/version/version.js +19 -0
- package/dist/components/vulnerability/constants.d.ts +4 -0
- package/dist/components/vulnerability/constants.js +17 -0
- package/dist/components/vulnerability/severityPill.d.ts +15 -0
- package/dist/components/vulnerability/severityPill.js +20 -0
- package/dist/{style.css → globals.css} +155 -226
- package/dist/hooks/useClickOutside.d.ts +6 -0
- package/dist/{useClickOutside-Cmp-RsP3.js → hooks/useClickOutside.js} +1 -1
- package/dist/hooks/useDarkMode.d.ts +4 -0
- package/dist/hooks/useDarkMode.js +19 -0
- package/dist/hooks/useVisibility.d.ts +7 -0
- package/dist/hooks/useVisibility.js +25 -0
- package/dist/icons/icons.d.ts +420 -0
- package/dist/{icons-lZYQ6Vlr.js → icons/icons.js} +1 -1
- package/dist/src/components/index.d.ts +51 -901
- package/dist/src/components/index.js +49 -4160
- package/dist/src/hooks/index.d.ts +3 -14
- package/dist/src/hooks/index.js +3 -42
- package/dist/src/icons/index.d.ts +1 -419
- package/dist/src/icons/index.js +1 -1
- package/dist/src/styles/index.js +1 -1
- package/dist/src/utils/index.d.ts +5 -37
- package/dist/src/utils/index.js +5 -124
- package/dist/utils/auth/callback.d.ts +14 -0
- package/dist/utils/auth/callback.js +53 -0
- package/dist/utils/auth/getDomain.js +6 -0
- package/dist/utils/auth/login.d.ts +12 -0
- package/dist/utils/auth/login.js +17 -0
- package/dist/utils/auth/logout.d.ts +10 -0
- package/dist/utils/auth/logout.js +20 -0
- package/dist/utils/auth/token.d.ts +10 -0
- package/dist/utils/auth/token.js +30 -0
- package/dist/utils/logoConsoleOutput/logoConsoleOutput.d.ts +4 -0
- package/dist/utils/logoConsoleOutput/logoConsoleOutput.js +11 -0
- package/package.json +1 -1
- package/src/components/buttons/button.tsx +2 -0
- package/src/components/footer/footer.tsx +65 -28
- package/src/components/index.ts +0 -1
- package/src/components/inputs/checkbox.tsx +2 -0
- package/src/components/inputs/input.tsx +2 -0
- package/src/components/inputs/radio.tsx +2 -0
- package/src/components/inputs/range.tsx +2 -0
- package/src/components/inputs/shared/colorPickerPopup.tsx +2 -0
- package/src/components/inputs/shared/dateTimePickerPopup.tsx +2 -0
- package/src/components/inputs/textarea.tsx +2 -0
- package/src/components/inputs/toggle.tsx +2 -0
- package/src/components/sidebar/sidebar.tsx +37 -10
- package/src/components/toast/toastItem.tsx +2 -0
- package/src/globals.css +6 -1
- 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,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,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
|
|
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'>
|
|
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
|
-
{
|
|
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
|
-
|
|
68
|
-
sm:max-w-88 sm:justify-items-end sm:justify-self-end
|
|
69
|
-
md:col-start-2 md:row-start-1 md:
|
|
70
|
-
${
|
|
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
|
-
{
|
|
121
|
+
{restColumns.map((col, i) => (
|
|
73
122
|
<div key={i} className='sm:justify-self-center md:justify-self-end'>
|
|
74
|
-
<
|
|
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
|
|
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={`
|
|
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-
|
|
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} />
|
package/src/components/index.ts
CHANGED
|
@@ -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({
|
|
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 =>
|
|
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={
|
|
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(
|
|
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
|
|
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
|
-
${
|
|
180
|
+
${subActive
|
|
154
181
|
? 'text-login'
|
|
155
182
|
: 'text-login-300 hover:bg-login-800/30 hover:text-login-100'
|
|
156
183
|
}
|
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
|
|