diff --git a/demo/DemoApp.tsx b/demo/DemoApp.tsx
index 0e06b57..e90abc4 100644
--- a/demo/DemoApp.tsx
+++ b/demo/DemoApp.tsx
@@ -2,7 +2,7 @@ import React, {useState} from "react";
import "../index";
import {Checkbox} from "../src/Components/Forms/Checkbox";
import { Radio } from "../src/Components/Forms/Radio";
-import {FloppyDisk, TrashSimple, XCircle} from "@phosphor-icons/react";
+import {AirTrafficControl, Basket, FloppyDisk, House, TrashSimple, XCircle} from "@phosphor-icons/react";
import {Card} from "../src/Components/Card";
import {PasswordInput} from "../src/Components/Forms/PasswordInput";
import {RequiredField} from "../src/Components/Forms/RequiredField";
@@ -18,7 +18,10 @@ import {MainMenu} from "../src/Components/Menus/MainMenu";
import {SubmenuFloat} from "../src/Components/Menus/SubmenuFloat";
import {Submenu} from "../src/Components/Menus/Submenu";
import {SubmenuItem, SubmenuItemSubmenu} from "../src/Components/Menus/SubmenuItem";
-import {MainMenuItem, MainMenuItemSubmenu} from "../src/Components/Menus/MainMenuItem";
+import {MainMenuItemSubmenu, MainMenuLink} from "../src/Components/Menus/MainMenuItem";
+import {AppItem, AppLink, AppsMenu} from "../src/Components/Menus/AppsMenu";
+import {Application} from "../src/Application/Application";
+import {Outlet} from "react-router-dom";
export function DemoApp()
{
@@ -27,10 +30,10 @@ export function DemoApp()
const [selected, setSelected] = useState(null);
return (
-
+
- Home
- Test
+ Home
+ Test
Test 1
@@ -44,12 +47,12 @@ export function DemoApp()
First last choice
Another last choice
- }>Submenu in submenu
+ }> Submenu in submenu
- }>Submenu
+ }> Submenu
}>
- Submenu
+ Submenu
@@ -58,12 +61,8 @@ export function DemoApp()
TODO
- - Dropdown menus
- - Main menu
- - Tabs / Apps selectors
- App steps
- Pagination
- - Apps
- Global states
- Async
- Subapps
@@ -89,9 +88,9 @@ export function DemoApp()
A link button
-
-
-
+
+
+
Forms
@@ -303,15 +302,34 @@ export function DemoApp()
Test A
- Test B
+ Test B
}>
- Submenu
+ Submenu
} floatingOptions={{ placement: "right-start" }}>
-
+
+
App selectors
+
+
+
+
+ Home
+
+
+
+ Test link
+
+
+
+ Test 3
+
+
+
+
+
);
}
diff --git a/demo/NavTest.tsx b/demo/NavTest.tsx
new file mode 100644
index 0000000..1afa84e
--- /dev/null
+++ b/demo/NavTest.tsx
@@ -0,0 +1,14 @@
+import React from "react";
+import {Card} from "../src/Components/Card";
+
+/**
+ * Navigation test component.
+ */
+export function NavTest()
+{
+ return (
+
+ This is a navigation test.
+
+ )
+}
diff --git a/demo/demo.tsx b/demo/demo.tsx
index 1d3361d..8ce8eb1 100644
--- a/demo/demo.tsx
+++ b/demo/demo.tsx
@@ -1,11 +1,32 @@
import React from "react";
import {createRoot} from "react-dom/client";
import {DemoApp} from "./DemoApp";
+import {createBrowserRouter} from "react-router-dom";
+import {Kernel} from "../src/Application/Kernel";
+import {NavTest} from "./NavTest";
+
+// Router initialization.
+const router = createBrowserRouter([
+ {
+ path: "/",
+ element: ,
+ children: [
+ {
+ path: "test",
+ element: ,
+ }
+ ],
+ }
+])
document.addEventListener("DOMContentLoaded", () => {
const demoApp = document.getElementById("demo-app");
const root = createRoot(demoApp);
- root.render();
+ root.render(
+ Footer test.
+
+ } />);
});
diff --git a/package.json b/package.json
index 5d82e6e..b0d221c 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,8 @@
"@fontsource-variable/source-serif-4": "^5.0.19",
"@phosphor-icons/react": "^2.1.5",
"react": "^18.3.1",
- "react-merge-refs": "^2.1.1"
+ "react-merge-refs": "^2.1.1",
+ "react-router-dom": "^6.24.1"
},
"devDependencies": {
"@types/react": "^18.3.3",
diff --git a/src/Application/Application.tsx b/src/Application/Application.tsx
new file mode 100644
index 0000000..3676892
--- /dev/null
+++ b/src/Application/Application.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+
+/**
+ * Main Kernel UI application.
+ */
+export function Application({children}: React.PropsWithChildren<{}>)
+{
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/Application/Kernel.tsx b/src/Application/Kernel.tsx
new file mode 100644
index 0000000..7aef8b0
--- /dev/null
+++ b/src/Application/Kernel.tsx
@@ -0,0 +1,24 @@
+import { IconContext } from "@phosphor-icons/react";
+import React from "react";
+import {createBrowserRouter, RouterProvider} from "react-router-dom";
+
+/**
+ * Main Kernel UI app component which initializes everything.
+ */
+export function Kernel({header, footer, router}: {
+ header?: React.ReactNode;
+ footer?: React.ReactNode;
+ router: ReturnType;
+})
+{
+ return (
+
+ {header}
+
+ {footer}
+
+ );
+}
diff --git a/src/Components/Dates/Datepicker.tsx b/src/Components/Dates/Datepicker.tsx
index edf6ceb..188882e 100644
--- a/src/Components/Dates/Datepicker.tsx
+++ b/src/Components/Dates/Datepicker.tsx
@@ -46,7 +46,7 @@ export function Datepicker({date, onDateSelected, locale, className, ...divProps
onDateSelected(newDate);
}, [date, onDateSelected])
}>
-
+
@@ -57,7 +57,7 @@ export function Datepicker({date, onDateSelected, locale, className, ...divProps
onDateSelected(newDate);
}, [date, onDateSelected])
}>
-
+
diff --git a/src/Components/Forms/Checkbox.tsx b/src/Components/Forms/Checkbox.tsx
index a3fd756..3fd9fc6 100644
--- a/src/Components/Forms/Checkbox.tsx
+++ b/src/Components/Forms/Checkbox.tsx
@@ -7,7 +7,7 @@ export function Checkbox({children, className, type, ...inputProps}: React.Props
return (
);
diff --git a/src/Components/Forms/PasswordInput.tsx b/src/Components/Forms/PasswordInput.tsx
index 1cd5197..4b57ee3 100644
--- a/src/Components/Forms/PasswordInput.tsx
+++ b/src/Components/Forms/PasswordInput.tsx
@@ -15,7 +15,7 @@ export function PasswordInput({children, className, type, ...props}: React.Props
setShowPassword(!showPassword);
}}>
{
- showPassword ? :
+ showPassword ? :
}
diff --git a/src/Components/Forms/Radio.tsx b/src/Components/Forms/Radio.tsx
index 9e24aa0..ea64198 100644
--- a/src/Components/Forms/Radio.tsx
+++ b/src/Components/Forms/Radio.tsx
@@ -7,7 +7,7 @@ export function Radio({children, className, type, ...inputProps}: React.PropsWit
return (
);
diff --git a/src/Components/Menus/AppsMenu.tsx b/src/Components/Menus/AppsMenu.tsx
new file mode 100644
index 0000000..e4a1db2
--- /dev/null
+++ b/src/Components/Menus/AppsMenu.tsx
@@ -0,0 +1,54 @@
+import React from "react";
+import {classes} from "../../Utils";
+import {IconContext} from "@phosphor-icons/react";
+import {NavLink, NavLinkProps} from "react-router-dom";
+
+/**
+ * Main apps menu component.
+ */
+export function AppsMenu({className, children, ...props}: React.HTMLAttributes)
+{
+ return (
+
+ {(value) => (
+
+
+
+ )}
+
+ );
+}
+
+/**
+ * Component of an app item in apps menu.
+ */
+export function AppItem({className, children, ...props}: React.HTMLAttributes)
+{
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+/**
+ * Component of an app link in apps menu.
+ */
+export function AppLink({className, children, ...props}: NavLinkProps & React.HTMLAttributes)
+{
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/Components/Menus/MainMenuItem.tsx b/src/Components/Menus/MainMenuItem.tsx
index ef4c202..278b569 100644
--- a/src/Components/Menus/MainMenuItem.tsx
+++ b/src/Components/Menus/MainMenuItem.tsx
@@ -1,7 +1,11 @@
import React from "react";
import {classes, Modify} from "../../Utils";
import {SubmenuFloat} from "./SubmenuFloat";
+import {NavLink, NavLinkProps} from "react-router-dom";
+/**
+ * Main menu item properties.
+ */
export type MainMenuItemProperties = React.PropsWithChildren, {
}>>;
@@ -17,6 +21,24 @@ export const MainMenuItem = React.forwardRef, {
+}>>;
+
+/**
+ * A main menu item link.
+ */
+export const MainMenuLink = React.forwardRef(function MainMenuLink({children, ...props}: MainMenuLinkProperties, ref)
+{
+ return (
+
+ {children}
+
+ );
+});
+
/**
* A main menu item that open a submenu.
*/
diff --git a/src/Components/Menus/SubmenuItem.tsx b/src/Components/Menus/SubmenuItem.tsx
index 1a5dccb..0b750fa 100644
--- a/src/Components/Menus/SubmenuItem.tsx
+++ b/src/Components/Menus/SubmenuItem.tsx
@@ -1,7 +1,11 @@
import React from "react";
import {classes, Modify} from "../../Utils";
import {SubmenuFloat} from "./SubmenuFloat";
+import {NavLink, NavLinkProps} from "react-router-dom";
+/**
+ * Submenu item properties.
+ */
export type SubmenuItemProperties = React.PropsWithChildren, {
}>>;
@@ -15,6 +19,22 @@ export const SubmenuItem = React.forwardRef, {
+}>>;
+
+/**
+ * A submenu item link.
+ */
+export const SubmenuLink = React.forwardRef(function SubmenuLink({className, children, ...props}: SubmenuLinkProperties, ref)
+{
+ return (
+ {children}
+ );
+});
+
/**
* A submenu item that open a submenu.
*/
diff --git a/src/Components/Select/OptionsSuggestions.tsx b/src/Components/Select/OptionsSuggestions.tsx
index dbaea7b..6e6a06d 100644
--- a/src/Components/Select/OptionsSuggestions.tsx
+++ b/src/Components/Select/OptionsSuggestions.tsx
@@ -126,7 +126,7 @@ export function OptionsSuggestions({options
onClick={() => { onSelected(key, option); }}>
{(renderOption ?? defaultRenderOption)(option)}
-
+
));
}
diff --git a/src/Components/Select/Select.tsx b/src/Components/Select/Select.tsx
index e3852a2..1261cfc 100644
--- a/src/Components/Select/Select.tsx
+++ b/src/Components/Select/Select.tsx
@@ -205,17 +205,17 @@ export function Select(
{...props} />
-
+
{ // Showing each selected value.
selectedOptions.map(([optionKey, option]) => (
-
-
+
{(renderOption ?? defaultRenderOption)(option)}
))
diff --git a/src/styles/_colors.less b/src/styles/_colors.less
index c4653e5..b93d2e4 100644
--- a/src/styles/_colors.less
+++ b/src/styles/_colors.less
@@ -85,4 +85,5 @@
@menu-hover: rgba(255, 255, 255, 0.15); --menu-hover: @menu-hover;
+ @menu-active: rgba(0, 0, 0, 0.125); --menu-active: @menu-active;
}
diff --git a/src/styles/components/_button.less b/src/styles/components/_button.less
index d6178bd..335d8dd 100644
--- a/src/styles/components/_button.less
+++ b/src/styles/components/_button.less
@@ -152,13 +152,13 @@ a.button, button, input[type="submit"], input[type="reset"]
}
svg
- { // Icon style.*
+ { // Icon style.
display: inline-block;
margin-top: -0.2em;
margin-right: 0.2em;
vertical-align: middle;
}
- > svg:last-of-type
+ &.icon-only svg
{
margin-right: 0.05em;
}
diff --git a/src/styles/components/_menus.less b/src/styles/components/_menus.less
index 3bc0861..a1c45f2 100644
--- a/src/styles/components/_menus.less
+++ b/src/styles/components/_menus.less
@@ -1,2 +1,3 @@
+@import "menus/_apps-menu";
@import "menus/_main-menu";
@import "menus/_submenu";
diff --git a/src/styles/components/menus/_apps-menu.less b/src/styles/components/menus/_apps-menu.less
new file mode 100644
index 0000000..851f42c
--- /dev/null
+++ b/src/styles/components/menus/_apps-menu.less
@@ -0,0 +1,49 @@
+nav.apps.menu
+{
+ > ul
+ {
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ justify-content: center;
+ gap: 0.66em;
+
+ margin: auto;
+ padding: 0;
+
+ list-style: none;
+
+ > li
+ {
+ padding: 0;
+ }
+ }
+
+ a.app
+ {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ margin: auto;
+ padding: 1em;
+ min-width: 10em;
+
+ font-size: 1.1em;
+
+ svg
+ {
+ display: block;
+ margin: 0 auto 0.5em auto;
+
+ color: var(--primary);
+ }
+
+ &.active
+ {
+ outline: solid 2px var(--primary);
+ outline-offset: 2px;
+ }
+ }
+}
diff --git a/src/styles/components/menus/_main-menu.less b/src/styles/components/menus/_main-menu.less
index ffe1217..f49e922 100644
--- a/src/styles/components/menus/_main-menu.less
+++ b/src/styles/components/menus/_main-menu.less
@@ -1,8 +1,13 @@
nav.main.menu
{
+ position: sticky;
+ top: 0;
+
background: var(--primary-gradient);
color: var(--background);
+ z-index: 2;
+
.floating
{
justify-content: flex-start;
@@ -47,6 +52,19 @@ nav.main.menu
{
background: var(--menu-hover);
}
+
+ &.active
+ {
+ background: var(--menu-active);
+ }
+
+ svg
+ { // Icon style.
+ display: inline-block;
+ margin-top: -0.2em;
+ margin-right: 0.2em;
+ vertical-align: middle;
+ }
}
}
}
@@ -68,6 +86,11 @@ nav.main.menu
{
background: var(--menu-hover);
}
+
+ &.active
+ {
+ background: var(--menu-active);
+ }
}
}
}
diff --git a/src/styles/components/menus/_submenu.less b/src/styles/components/menus/_submenu.less
index 166aee1..d002b13 100644
--- a/src/styles/components/menus/_submenu.less
+++ b/src/styles/components/menus/_submenu.less
@@ -12,7 +12,7 @@
justify-content: flex-end;
}
- .submenu
+ > .submenu
{
display: flex;
flex-direction: column;
@@ -32,6 +32,14 @@
{
background: var(--background-darker);
}
+
+ svg
+ { // Icon style.
+ display: inline-block;
+ margin-top: -0.2em;
+ margin-right: 0.2em;
+ vertical-align: middle;
+ }
}
}
}
diff --git a/yarn.lock b/yarn.lock
index edf1fe6..2842f13 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -651,6 +651,13 @@ __metadata:
languageName: node
linkType: hard
+"@remix-run/router@npm:1.17.1":
+ version: 1.17.1
+ resolution: "@remix-run/router@npm:1.17.1"
+ checksum: 10c0/bee1631feb03975b64e1c7b574da432a05095dda2ff0f164c737e4952841a58d7b9861de87bd13a977fd970c74dcf8c558fc2d26c6ec01a9ae9041b1b4430869
+ languageName: node
+ linkType: hard
+
"@rollup/pluginutils@npm:^5.1.0":
version: 5.1.0
resolution: "@rollup/pluginutils@npm:5.1.0"
@@ -1826,6 +1833,7 @@ __metadata:
react: "npm:^18.3.1"
react-dom: "npm:^18.3.1"
react-merge-refs: "npm:^2.1.1"
+ react-router-dom: "npm:^6.24.1"
typescript: "npm:^5.4.5"
vite: "npm:^5.2.11"
vite-plugin-dts: "npm:^3.9.1"
@@ -2313,6 +2321,30 @@ __metadata:
languageName: node
linkType: hard
+"react-router-dom@npm:^6.24.1":
+ version: 6.24.1
+ resolution: "react-router-dom@npm:6.24.1"
+ dependencies:
+ "@remix-run/router": "npm:1.17.1"
+ react-router: "npm:6.24.1"
+ peerDependencies:
+ react: ">=16.8"
+ react-dom: ">=16.8"
+ checksum: 10c0/458c6c539304984c47b0ad8d5d5b1f8859cc0845e47591d530cb4fcb13498f70a89b42bc4daeea55d57cfa08408b453bcf601cabb2c987f554cdcac13805caa8
+ languageName: node
+ linkType: hard
+
+"react-router@npm:6.24.1":
+ version: 6.24.1
+ resolution: "react-router@npm:6.24.1"
+ dependencies:
+ "@remix-run/router": "npm:1.17.1"
+ peerDependencies:
+ react: ">=16.8"
+ checksum: 10c0/f50c78ca52c5154ab933c17708125e8bf71ccf2072993a80302526a0a23db9ceac6e36d5c891d62ccd16f13e60cd1b6533a2036523d1b09e0148ac49e34b2e83
+ languageName: node
+ linkType: hard
+
"react@npm:^18.3.1":
version: 18.3.1
resolution: "react@npm:18.3.1"