aboutsummaryrefslogtreecommitdiffstats
path: root/ui/src
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@atuin.sh>2024-07-10 15:56:33 +0100
committerGitHub <noreply@github.com>2024-07-10 15:56:33 +0100
commit8d9f677c4e9ccfcc6dc9297864dc49446fb5ee59 (patch)
tree1fe507403c4d690937834a815a663336bf104039 /ui/src
parentchore(deps): update to tonic 0.12, prost 0.13 (#2250) (diff)
downloadatuin-8d9f677c4e9ccfcc6dc9297864dc49446fb5ee59.zip
feat(gui): use fancy new side nav (#2243)
* feat(gui): use fancy new side nav * compact only sidebar, no expand-collapse * custom drag region, remove titlebar * add user popup * wire up login/logout/register, move user button to bottom and add menu * link help and feedback to forum
Diffstat (limited to '')
-rw-r--r--ui/src/App.tsx257
-rw-r--r--ui/src/assets/icon.svg1
-rw-r--r--ui/src/components/LoginOrRegister.tsx14
-rw-r--r--ui/src/components/Sidebar/Sidebar.tsx328
-rw-r--r--ui/src/components/Sidebar/index.tsx4
-rw-r--r--ui/src/components/runbooks/editor/Editor.tsx4
-rw-r--r--ui/src/lib/utils.ts31
-rw-r--r--ui/src/main.tsx6
-rw-r--r--ui/src/pages/Dotfiles.tsx2
-rw-r--r--ui/src/pages/History.tsx2
-rw-r--r--ui/src/pages/Home.tsx2
-rw-r--r--ui/src/pages/Runbooks.tsx2
-rw-r--r--ui/src/state/client.ts4
13 files changed, 569 insertions, 88 deletions
diff --git a/ui/src/App.tsx b/ui/src/App.tsx
index 6dba3d71..7a9ac395 100644
--- a/ui/src/App.tsx
+++ b/ui/src/App.tsx
@@ -1,10 +1,17 @@
import "./App.css";
+import { open } from "@tauri-apps/plugin-shell";
-import { useState, ReactElement } from "react";
+import { useState, ReactElement, useEffect } from "react";
import { useStore } from "@/state/store";
-import Button, { ButtonStyle } from "@/components/Button";
import { Toaster } from "@/components/ui/toaster";
+import {
+ SettingsIcon,
+ CircleHelpIcon,
+ KeyRoundIcon,
+ LogOutIcon,
+} from "lucide-react";
+import { Icon } from "@iconify/react";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
@@ -28,6 +35,35 @@ import Dotfiles from "./pages/Dotfiles.tsx";
import LoginOrRegister from "./components/LoginOrRegister.tsx";
import Runbooks from "./pages/Runbooks.tsx";
+import {
+ Avatar,
+ User,
+ Button,
+ ScrollShadow,
+ Spacer,
+ Tooltip,
+ Dropdown,
+ DropdownItem,
+ DropdownMenu,
+ DropdownSection,
+ DropdownTrigger,
+ Modal,
+ ModalContent,
+ ModalHeader,
+ ModalBody,
+ ModalFooter,
+ useDisclosure,
+ Checkbox,
+ Input,
+ Link,
+} from "@nextui-org/react";
+import { cn } from "@/lib/utils";
+import { sectionItems } from "@/components/Sidebar/sidebar-items";
+import Sidebar, { SidebarItem } from "@/components/Sidebar";
+import icon from "@/assets/icon.svg";
+import iconText from "@/assets/logo-light.svg";
+import { logout } from "./state/client.ts";
+
enum Section {
Home,
History,
@@ -54,90 +90,165 @@ function App() {
// pages
const [section, setSection] = useState(Section.Home);
const user = useStore((state) => state.user);
- console.log(user);
+ const refreshUser = useStore((state) => state.refreshUser);
+ const { isOpen, onOpen, onOpenChange } = useDisclosure();
- const navigation = [
- {
- name: "Home",
- icon: HomeIcon,
- section: Section.Home,
- },
- {
- name: "History",
- icon: ClockIcon,
- section: Section.History,
- },
- {
- name: "Dotfiles",
- icon: WrenchScrewdriverIcon,
- section: Section.Dotfiles,
- },
+ const navigation: SidebarItem[] = [
{
- name: "Runbooks",
- icon: ChevronRightSquare,
- section: Section.Runbooks,
+ key: "personal",
+ title: "Personal",
+ items: [
+ {
+ key: "home",
+ icon: "solar:home-2-linear",
+ title: "Home",
+ onPress: () => setSection(Section.Home),
+ },
+ {
+ key: "runbooks",
+ icon: "solar:notebook-linear",
+ title: "Runbooks",
+ onPress: () => {
+ console.log("runbooks");
+ setSection(Section.Runbooks);
+ },
+ },
+ {
+ key: "history",
+ icon: "solar:history-outline",
+ title: "History",
+ onPress: () => setSection(Section.History),
+ },
+ {
+ key: "dotfiles",
+ icon: "solar:file-smile-linear",
+ title: "Dotfiles",
+ onPress: () => setSection(Section.Dotfiles),
+ },
+ ],
},
];
return (
- <div>
- <div className="fixed inset-y-0 z-50 flex w-60 flex-col">
- <div className="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6 pb-4">
- <div className="flex h-16 shrink-0 items-center">
- <img className="h-8 w-auto" src={Logo} alt="Atuin" />
+ <div className="flex h-dvh w-full select-none">
+ <div className="relative flex h-full flex-col !border-r-small border-divider p-6 transition-width px-2 pb-6 pt-9 w-16 items-center">
+ <div className="flex items-center gap-0 px-3 justify-center">
+ <div className="flex h-8 w-8">
+ <img src={icon} alt="icon" className="h-8 w-8" />
</div>
- <nav className="flex flex-1 flex-col">
- <ul role="list" className="flex flex-1 flex-col gap-y-7">
- <li>
- <ul role="list" className="-mx-2 space-y-1 w-full">
- {navigation.map((item) => (
- <li key={item.name}>
- <button
- onClick={() => setSection(item.section)}
- className={classNames(
- section == item.section
- ? "bg-gray-50 text-green-600"
- : "text-gray-700 hover:text-green-600 hover:bg-gray-50",
- "group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold w-full",
- )}
- >
- <item.icon
- className={classNames(
- section == item.section
- ? "text-green-600"
- : "text-gray-400 group-hover:text-green-600",
- "h-6 w-6 shrink-0",
- )}
- aria-hidden="true"
- />
- {item.name}
- </button>
- </li>
- ))}
- </ul>
- </li>
- <li className="mt-auto">
- {user && user.username === "" && !user.username && (
- <Dialog>
- <DialogTrigger className="w-full">
- <Button
- text={"Login or Register"}
- style={ButtonStyle.PrimarySmFill}
- />
- </DialogTrigger>
- <DialogContent>
- <LoginOrRegister />
- </DialogContent>
- </Dialog>
+ </div>
+
+ <ScrollShadow className="-mr-6 h-full max-h-full py-6 pr-6">
+ <Sidebar
+ defaultSelectedKey="home"
+ isCompact={true}
+ items={navigation}
+ />
+ </ScrollShadow>
+
+ <Spacer y={2} />
+
+ <div className="flex items-center gap-3 px-3">
+ <Dropdown showArrow placement="right-start">
+ <DropdownTrigger>
+ <Button disableRipple isIconOnly radius="full" variant="light">
+ <Avatar
+ isBordered
+ className="flex-none"
+ size="sm"
+ name={user.username || ""}
+ />
+ </Button>
+ </DropdownTrigger>
+ <DropdownMenu aria-label="Custom item styles">
+ <DropdownItem
+ key="profile"
+ isReadOnly
+ className="h-14 opacity-100"
+ textValue="Signed in as"
+ >
+ <User
+ avatarProps={{
+ size: "sm",
+ name: user.username || "Anonymous User",
+ showFallback: true,
+ imgProps: {
+ className: "transition-none",
+ },
+ }}
+ classNames={{
+ name: "text-default-600",
+ description: "text-default-500",
+ }}
+ description={
+ user.bio || (user.username && "No bio") || "Sign up now"
+ }
+ name={user.username || "Anonymous User"}
+ />
+ </DropdownItem>
+
+ <DropdownItem
+ key="settings"
+ description="Configure Atuin"
+ startContent={<Icon icon="solar:settings-linear" width={24} />}
+ >
+ Settings
+ </DropdownItem>
+
+ <DropdownSection aria-label="Help & Feedback">
+ <DropdownItem
+ key="help_and_feedback"
+ description="Get in touch"
+ onPress={() => open("https://forum.atuin.sh")}
+ startContent={
+ <Icon width={24} icon="solar:question-circle-linear" />
+ }
+ >
+ Help & Feedback
+ </DropdownItem>
+
+ {(user.username && (
+ <DropdownItem
+ key="logout"
+ startContent={
+ <Icon width={24} icon="solar:logout-broken" />
+ }
+ onClick={() => {
+ logout();
+ refreshUser();
+ }}
+ >
+ Log Out
+ </DropdownItem>
+ )) || (
+ <DropdownItem
+ key="signup"
+ description="Sync, backup and share your data"
+ className="bg-emerald-100"
+ startContent={<KeyRoundIcon size="18px" />}
+ onPress={onOpen}
+ >
+ Log in or Register
+ </DropdownItem>
)}
- </li>
- </ul>
- </nav>
+ </DropdownSection>
+ </DropdownMenu>
+ </Dropdown>
</div>
</div>
{renderMain(section)}
+
<Toaster />
+ <Modal isOpen={isOpen} onOpenChange={onOpenChange} placement="top-center">
+ <ModalContent className="p-8">
+ {(onClose) => (
+ <>
+ <LoginOrRegister onClose={onClose} />
+ </>
+ )}
+ </ModalContent>
+ </Modal>
</div>
);
}
diff --git a/ui/src/assets/icon.svg b/ui/src/assets/icon.svg
new file mode 100644
index 00000000..0e4dd607
--- /dev/null
+++ b/ui/src/assets/icon.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" width="344.41" height="309.08" viewBox="0 0 344.41 309.08"><g id="Layer_1-2"><path d="m99.75,291.76c0-5.07,4.12-9.19,9.19-9.19,2.46,0,4.77.96,6.5,2.7,1.4,1.38,2.29,3.16,2.59,5.13.07.41.11.87.11,1.36v10.52s-1.68.27-1.68.27c-2.22.36-4.56.63-6.95.83-2.36.22-4.89.36-7.71.43l-2.05.05v-12.1Zm-2.84,12.05c-.1,0-.19,0-.28,0-2.45-.06-4.4-.15-6.2-.3h-.13c-2.76-.21-5.49-.52-8.1-.94l-1.68-.27v-10.52c0-5.07,4.12-9.19,9.19-9.19,2.46,0,4.77.96,6.5,2.7,1.74,1.72,2.7,4.03,2.7,6.49v12.05h-2Zm22.07-15.26c0-.43.03-.91.1-1.38.68-4.47,4.59-7.82,9.1-7.82,2.44,0,4.74.96,6.49,2.69,1.75,1.76,2.71,4.07,2.71,6.51v5.57s-.54.58-.54.58c-1.71,1.81-4.39,3.42-7.97,4.79-2.19.83-4.7,1.56-7.45,2.17l-2.43.54v-13.65Zm146.15-19.7c0-4.73,3.84-8.58,8.57-8.58,2.29,0,4.44.89,6.06,2.52,1.3,1.3,2.14,2.96,2.41,4.79.06.33.1.77.1,1.28v9.76s-1.68.27-1.68.27c-2.09.33-4.23.59-6.38.76-2.15.2-4.54.33-7.04.39l-2.04.04v-11.22Zm-2.43,11.18c-.08,0-.17,0-.25,0-2.07-.04-3.9-.13-5.68-.27h-.13c-2.55-.2-5.05-.49-7.41-.86l-1.69-.27v-9.37s6.25-1.01,6.25-1.01c3.03-.49,5.97-1.43,8.74-2.8l1,2.01,1-.21c.11.52.16,1.06.16,1.61v11.18h-2Zm20.01-14.12c0-.37.03-.76.08-1.24.65-4.21,4.29-7.33,8.49-7.33,2.28,0,4.43.89,6.06,2.5,1.63,1.63,2.53,3.79,2.53,6.07v5.17s-.55.58-.55.58c-1.6,1.68-4.07,3.17-7.35,4.42-1.95.75-4.25,1.41-6.83,1.99l-2.43.54v-12.69Zm-240.96-5.95c-.09,0-.18,0-.26,0-2.12-.05-3.94-.15-5.66-.28h-.13c-2.46-.18-4.95-.47-7.41-.86l-1.69-.27v-9.77c0-4.31,3.22-7.96,7.49-8.5l1.41-.18,4.2,8.42c1.01,2.01,2.23,3.91,3.61,5.63l.44.55v5.27h-2Z" fill="#fff"/><path d="m14.35,190.88l25.24,50.06c-1.34-.76-2.85-1.16-4.42-1.16-4.96,0-9,4.04-9,9v7.45l-2.45-.57c-1.67-.39-3.27-.84-4.76-1.33-3.39-1.17-9.03-3.68-9.03-7.47v-39.14c.16-6.06,1.7-11.84,4.42-16.84Zm275.98,14.02c-.1-.12-.2-.23-.31-.34l-17.42,42.87c-2.38,5.85-6.64,10.61-11.98,13.64.68.38,1.32.87,1.89,1.44.29.29.55.59.81.93l1.59,2.14,1.61-2.13c1.72-2.28,4.34-3.59,7.18-3.59,2.38,0,4.63.93,6.33,2.61l1.96,1.94,1.23-2.47c1.53-3.05,4.7-5.02,8.06-5.02,1.64,0,3.25.45,4.65,1.28l3.03,1.82v-32.27c-.24-8.66-3.3-16.77-8.63-22.85Zm-163.34,17.02c-6.75-7.69-16.31-11.75-27.65-11.75s-20.91,4.06-27.66,11.75c-5.81,6.62-9.14,15.45-9.39,24.92v42.82c0,4.03,6.09,6.73,9.75,7.99,1.62.53,3.37,1.02,5.19,1.45l2.46.58v-7.92c0-5.53,4.5-10.03,10.03-10.03,2.67,0,5.19,1.04,7.11,2.95.32.32.62.66.91,1.04l1.59,2.14,1.61-2.13c1.92-2.55,4.84-4.01,8.01-4.01,2.66,0,5.16,1.03,7.05,2.91l1.96,1.94,1.24-2.47c1.7-3.4,5.23-5.6,8.99-5.6,1.83,0,3.62.49,5.18,1.43l3.03,1.81v-34.97c-.25-9.42-3.59-18.26-9.39-24.87ZM332.31,60.61c-12.06-21.14-27.47-44.23-52.73-52.68-20.59-7.09-44.02-1.54-59.24,13.76-10.03,9.84-15.89,22.44-19.46,35.27-6.24,23.59-7.07,46.88-7.01,70.75.07,23.56,3.41,44.38,5.07,56.48,1.23,8.87,2.45,16.24,3.36,21.46-4.12,2.78-8.46,6.59-12.06,11.81-9.35,13.58-8.75,29.48-6.64,40.36,1.26,6.49,3.04,11.19,3.59,12.58,5.91,1.03,25.44,4.02,44.35-1.25,1.59,2.58,5.68,4.35,8.37,5.26,1.49.5,3.1.94,4.76,1.33l2.46.58v-7.45c0-1.4.32-2.72.9-3.91l-9.1,1.66c6.45-2.65,12.6-6.46,17.84-11.88,12.54-12.97,15.02-30.01,19.42-60.94,3.06-21.5,3.21-39.68,2.73-52.52,11.25,1.45,23.01-.57,33.51-6.77,6.36-3.67,11.69-8.43,15.91-13.91,12.92-16.83,15.23-40.44,3.98-59.97Z" fill="#26bd65"/><path d="m272.86,215.67c-.92,5.25-1.89,9.97-3.05,14.26l3.05-14.26Z" fill="#26bd65"/><path d="m112.87,102.44c-5.02-.03-9.86-1.12-14.38-3.23l-19.37-9.02c-1.93-.91-2.91-2.84-2.5-4.93.41-2.09,2.05-3.51,4.18-3.61l94.48-4.69c.5-.03,1.02-.04,1.54-.05h.22c1.94,0,3.92.15,5.88.45l10.68,1.68-.25,1.93c-.8,6.24-1.39,12.79-1.75,19.49l-.08,1.6-1.58.26c-1.82.3-3.71.46-5.6.46l-71.47-.35Z" fill="#b3e5fc"/><path d="m280.91,177.2c.86-10.19,1.2-20.5,1-30.64l-.04-2.1,2.1.07c.63.02,1.26.03,1.89.03.89,0,1.77-.02,2.66-.06l1.53-.07.46,1.46c2.85,9.01,2.29,18.71-1.56,27.32-1.06,2.36-2.77,4.38-4.94,5.83l-3.45,2.3.35-4.14Zm-137.27,20.12c2.2,0,4.4-.51,6.37-1.47l44.66-21.73-.19-1.43c-1.58-11.73-3.39-27.44-3.44-44.98,0-4.91,0-10.76.16-16.86v-.46s-.45-1.03-.45-1.03c-1.65-3.67-5.31-6.06-9.32-6.08l-68.83-.34c-4.28,0-8.15,2.7-9.63,6.71l-15.99,43.19c-2.47,6.69.22,14.12,6.38,17.67l42.99,24.85c2.22,1.28,4.74,1.96,7.3,1.96h0Zm-110.02-17.32c2.26,0,4.49-.38,6.62-1.12l32.98-11.48c5.71-1.99,10.19-6.38,12.28-12.04l16.11-43.53c1.88-5.07-.49-10.83-5.39-13.12l-22.31-10.4c-2.09-.98-4.33-1.47-6.64-1.47h-.3c-5.08.09-9.89,2.75-12.88,7.1l-38.98,57.01c-4.92,7.21-3.45,17.12,3.36,22.58l2.55,2.04c3.56,2.85,8.04,4.43,12.61,4.43h0Z" fill="#4fc3f7"/><path d="m162.77,274.65c-4.85,0-9.04-3.62-9.74-8.43l-7.79-53.52c-1.16-7.93,2.91-15.63,10.12-19.15l39.32-19.13,1.07,7.47.38,2.67c.87,6.3,1.84,12.55,2.86,18.57l.2,1.19-.96.73c-4.05,3.09-7.53,6.72-10.34,10.78-9.89,14.37-9.31,31.05-7.08,42.51.89,4.58,2.04,8.32,2.84,10.66l.77,2.24-20.06,3.27c-.57.09-1.06.13-1.53.13h-.06Zm-17.7-.33c-.45,0-.91-.04-1.36-.12l-1.65-.29v-27.24c-.51-20.29-14.11-42.18-42.73-42.18-20.07,0-42,13.21-42.73,42.23v8.28s-3.45-3.63-3.45-3.63c-2.33-2.45-4.07-5.46-5.05-8.7l-13.75-45.29c-2.45-8.07,1.87-16.57,9.84-19.34l31.41-10.93c1.66-.58,3.39-.87,5.16-.87,2.75,0,5.46.73,7.83,2.1l47.65,27.55c4.17,2.41,6.96,6.53,7.67,11.3l8.52,58.55c.32,2.2-.3,4.31-1.73,5.96-1.4,1.64-3.45,2.6-5.61,2.62h-.01ZM11.32,173.97l3.04-2.46,10.23,8.2c3.53,2.82,6.09,6.6,7.4,10.93l12.23,40.27-3.7,1.48-29.2-58.42Zm264.02,43.95c1.26-6.95,2.36-14.63,3.63-23.52l.03-.22c.14-.99.27-2.01.4-3.02v-.09s.12-.5.12-.5c.9-4.25,2.89-8.25,5.75-11.55l8.28-9.59,3.39,2.01-17.75,47.54-3.84-1.06Z" fill="#0288d1"/><path d="m252.88,68.7c-5.28,0-9.56,4.28-9.56,9.56s4.28,9.56,9.56,9.56,9.56-4.28,9.56-9.56-4.28-9.56-9.56-9.56Z" fill="#263238"/><circle cx="317.93" cy="78.26" r="9.56" fill="#263238"/><path d="m310.52,94.56c-1.41,1.55-2.91,2.72-4.44,3.48-1.11.55-2.21.86-3.28.99-.16.02-.32.04-.47.04h-.01c-2.84.2-5.46-.84-7.57-2.15-1.62-.8-3-1.71-4.07-2.5-.4-.29-.88-.49-1.4-.58-.5.12-.96.4-1.29.8-1.53,1.84-4.48,4.75-8.41,5.73-.71.19-1.44.31-2.21.35-.53.02-1.05.01-1.55-.04-4.13-.39-7.31-3.05-8.95-4.71-.94-.96-2.47-.98-3.43-.05-.95.94-.97,2.47-.04,3.43,2.22,2.26,6.68,5.91,12.67,6.2.25.01.5.02.76.02s.52,0,.78-.02c.74-.04,1.45-.12,2.14-.25,4.49-.83,8-3.46,10.31-5.76,4.11,3.18,8.49,4.67,12.69,4.36.66-.05,1.32-.14,1.98-.29,1.19-.25,2.35-.66,3.49-1.22,2.08-1.03,4.06-2.57,5.88-4.56.9-.99.83-2.52-.16-3.42-.99-.9-2.52-.83-3.42.15Z" fill="#263238"/><path d="m336.51,58.2c-13.31-23.33-29.23-46.12-55.36-54.86-22.35-7.7-47.57-1.84-64.2,14.87-9.54,9.36-16.33,21.61-20.75,37.49-1.17,4.42-2.14,8.84-2.97,13.23l-10.17-1.61c-1.8-.28-3.63-.42-5.44-.4-.46,0-.93.01-1.39.04l-103.33,5.13c-10.32.51-19.96,5.9-25.79,14.43L5.8,146.95c-6.81,9.95-7.69,22.82-2.3,33.61l4.02,8.04c-2.64,5.28-4.25,11.58-4.44,18.94v39.31c0,4.21,2.36,10.04,13.68,13.95,7.73,2.53,16.68,3.8,26.69,3.8,3,0,6.1-.11,9.28-.35.61.31,1.24.6,1.87.87v24.54c0,4.61,2.59,10.99,14.97,15.26,8.46,2.77,18.25,4.16,29.2,4.16,3.6,0,7.31-.14,11.16-.44.1-.01.22-.02.33-.04,1.07-.16,2.22-.31,3.42-.47,11.06-1.45,27.76-3.65,30.3-17.6.05-.29.08-.58.08-.87v-5.25l3.96.83c2.3.48,4.66.71,7.02.7,1.7-.02,3.41-.16,5.09-.43l53.15-8.66c4.63-.23,9.46-.77,14.32-1.78,2.08,2.1,5.27,4.13,10.11,5.8,7.74,2.53,16.68,3.81,26.7,3.81,3.28,0,6.68-.13,10.19-.41.1-.01.21-.02.3-.03.99-.15,2.04-.29,3.14-.43,10.1-1.33,25.37-3.33,27.69-16.09.05-.26.07-.53.07-.79v-39.31c-.27-10.59-3.49-18.97-8.44-25.29l10.22-27.36c3.82-10.22,2.37-21.67-3.86-30.62l-.36-.52c3.98-1.28,7.82-2.99,11.48-5.15,6.77-3.91,12.6-9.01,17.33-15.16,8.11-10.56,12.24-23.19,12.24-35.89,0-10.11-2.61-20.25-7.9-29.43Zm-147.48,69.5c.05,17.37,1.84,33.18,3.46,45.24l-43.36,21.1c-3.75,1.83-8.17,1.67-11.79-.42l-42.99-24.85c-5.31-3.06-7.63-9.51-5.51-15.25l15.99-43.19c1.2-3.26,4.32-5.42,7.8-5.4l68.77.34c3.25.02,6.18,1.94,7.51,4.9l.28.62c-.15,5.83-.17,11.48-.16,16.91Zm-110.44-42.06c.18-.92.88-1.93,2.31-2l94.48-4.69c.49-.03.99-.04,1.48-.05,1.92,0,3.86.14,5.76.43l8.75,1.38c-.86,6.67-1.42,13.23-1.76,19.64-1.8.3-3.63.44-5.45.43l-71.28-.35c-4.68-.03-9.3-1.06-13.54-3.04l-19.37-9.02c-1.29-.61-1.56-1.81-1.38-2.73Zm-61.84,66.43l38.98-57.01c2.56-3.74,6.73-6.15,11.26-6.23,2.11-.04,4.16.39,6.07,1.28l22.31,10.4c3.99,1.86,5.89,6.48,4.36,10.61l-16.11,43.53c-1.88,5.09-5.94,9.07-11.06,10.85l-32.98,11.48c-5.9,2.05-12.44.93-17.32-2.98l-2.55-2.04c-6-4.81-7.3-13.54-2.96-19.89Zm7.42,96.7v4.93c-1.58-.37-3.11-.8-4.59-1.28-4.73-1.63-7.65-3.76-7.65-5.57v-39.14c.07-2.67.44-5.38,1.13-8.03l19.23,38.48c-4.67,1.26-8.12,5.54-8.12,10.61Zm17.58,9.19c-.11,0-.21,0-.3,0-1.99-.05-3.8-.14-5.54-.28h-.07c-2.48-.18-4.92-.47-7.24-.84v-8.06c0-3.34,2.5-6.11,5.74-6.52l3.57,7.15c1.08,2.15,2.37,4.16,3.84,5.99v2.57Zm-28.64-84.89l10.23,8.2c3.2,2.56,5.55,6.03,6.74,9.95l12.23,40.27-29.2-58.42Zm83.8,128.74c-.12,0-.23-.01-.33-.01-2.17-.05-4.16-.14-6.06-.3h-.07c-2.72-.2-5.38-.51-7.93-.92v-8.82c0-3.96,3.22-7.19,7.19-7.19,1.92,0,3.73.75,5.09,2.11,1.36,1.35,2.11,3.16,2.11,5.08v10.05Zm19.23-1.23c-2.19.35-4.46.62-6.8.81-2.46.23-5,.36-7.59.42v-10.05c0-3.96,3.22-7.19,7.19-7.19,1.92,0,3.73.75,5.08,2.11,1.11,1.1,1.8,2.48,2.03,4.02.06.34.09.69.09,1.06v8.82Zm1.26-17.37c-2.26-2.24-5.27-3.48-8.46-3.48-3.92,0-7.41,1.89-9.6,4.8-.34-.46-.71-.88-1.12-1.28-2.27-2.26-5.29-3.52-8.51-3.52-6.64,0-12.03,5.4-12.03,12.03v5.39c-1.73-.4-3.41-.87-5.02-1.4-5.17-1.78-8.38-4.11-8.38-6.09v-42.82c.45-16.71,11.6-34.68,35.06-34.68s34.6,17.96,35.04,34.68v31.39c-1.86-1.12-3.99-1.72-6.2-1.72-1.75,0-3.43.38-4.95,1.07-2.51,1.15-4.59,3.14-5.83,5.63Zm17.98,10.12c-1.49,1.58-3.97,3.05-7.23,4.3-2.09.79-4.5,1.49-7.17,2.08v-11.16c0-.36.03-.74.08-1.1.53-3.47,3.59-6.1,7.12-6.1,1.91,0,3.72.76,5.08,2.11,1.36,1.37,2.12,3.18,2.12,5.09v4.78Zm13.8-22.95c-1.04,1.22-2.54,1.92-4.11,1.93-.33,0-.67-.03-1.01-.09v-25.56c-.77-30.35-23.73-44.18-44.73-44.18s-43.97,13.83-44.73,44.18v3.32c-2.09-2.2-3.68-4.89-4.59-7.9l-13.75-45.29c-2.13-7.01,1.66-14.46,8.58-16.87l31.41-10.93c3.75-1.31,7.89-.92,11.33,1.07l47.65,27.55c3.62,2.09,6.08,5.72,6.69,9.86l8.52,58.55c.23,1.6-.21,3.15-1.26,4.36Zm29.67-11.66c.9,4.63,2.05,8.43,2.91,10.93l-17.72,2.89c-.4.06-.81.1-1.21.1-3.9.03-7.26-2.86-7.82-6.72l-7.79-53.52c-1.03-7.05,2.62-13.94,9.02-17.06l36.86-17.93c.16,1.11.31,2.2.46,3.23.21,1.49.41,2.89.59,4.19.86,6.19,1.82,12.45,2.87,18.63-4.23,3.23-7.84,7-10.77,11.23-10.28,14.94-9.7,32.19-7.4,44.03Zm66.28,15.06c-1.59-.37-3.12-.8-4.59-1.29-.05-.01-.1-.03-.15-.05l4.74-.77v2.11Zm17.57-4.93v9.18c-.1,0-.2,0-.3,0-1.98-.04-3.8-.13-5.54-.27h-.06c-2.49-.19-4.92-.47-7.25-.84v-5.96l4.57-.74c3.28-.53,6.41-1.55,9.31-2.98-.28.14-.56.28-.85.41.08.39.12.8.12,1.21Zm17.58,8.06c-2,.32-4.07.57-6.22.74-2.24.21-4.56.33-6.93.38v-9.18c0-3.62,2.94-6.58,6.57-6.58,1.75,0,3.41.69,4.65,1.93,1.01,1.01,1.64,2.27,1.85,3.67.06.31.08.64.08.98v8.06Zm1.15-15.88c-1.78-1.76-4.06-2.84-6.52-3.11-.05.06-.1.12-.15.18,2.15-2.7,3.91-5.74,5.15-9.06l13.45-35.99c2.27,4.62,3.46,9.74,3.6,14.74v28.69c-1.7-1.02-3.65-1.57-5.68-1.57-4.23,0-8.01,2.47-9.85,6.12Zm2.48-114.52c1.58.05,3.14.04,4.7-.03,2.69,8.51,2.17,17.75-1.48,25.9-.94,2.09-2.43,3.78-4.23,4.98.87-10.27,1.21-20.58,1.01-30.85Zm2.87,33.81l8.28-9.59-17.75,47.54c1.28-7.05,2.4-14.92,3.67-23.82.14-1.01.27-2.03.4-3.05l.09-.41c.84-3.95,2.67-7.62,5.31-10.67Zm11.09,85.59v4.37c-1.37,1.44-3.63,2.79-6.61,3.93-1.91.73-4.11,1.36-6.55,1.9v-10.2c0-.33.03-.67.07-1,.49-3.18,3.28-5.57,6.5-5.57,1.76,0,3.41.69,4.65,1.92,1.25,1.25,1.94,2.91,1.94,4.65Zm26.62-148.29c-3.95,5.14-8.82,9.4-14.53,12.7-5.05,2.98-10.52,4.96-16.21,5.9-4.62.77-9.39.85-14.21.23-.06-.01-.12-.01-.18-.01-.02-.01-.04-.01-.07-.02-6.87-.53-13.53-2.18-19.78-4.9-3.9-1.7-7.61-3.8-11.05-6.25-4.46-3.16-8.49-6.9-11.99-11.11-1.7-2.06-4.75-2.34-6.81-.63-2.06,1.7-2.34,4.75-.64,6.81,4.04,4.86,8.69,9.17,13.83,12.82,3.98,2.83,8.28,5.26,12.79,7.23,5.92,2.58,12.14,4.31,18.55,5.2.37,15.84-.56,31.79-2.79,47.5-4.26,29.89-6.61,46.35-18.11,58.26-6.22,6.44-24.61,20.5-62.52,14.7-.72-2.13-1.67-5.33-2.42-9.17-1.94-10.03-2.5-24.53,5.87-36.69,1.44-2.08,3.08-4.01,4.92-5.8.14.56.25,1.12.39,1.68.32,1.29.67,2.59,1.04,3.88.61,2.12,2.55,3.5,4.65,3.5.45,0,.9-.06,1.34-.19,2.57-.74,4.06-3.42,3.32-5.99-.34-1.18-.66-2.37-.96-3.56-.73-2.91-1.35-5.9-1.84-8.86,0-.01,0-.02-.01-.03v-.02c-1.23-7.05-2.35-14.22-3.33-21.29-.18-1.31-.38-2.73-.59-4.23-1.75-12.31-4.38-30.92-4.44-51.61-.05-20.06.62-37.61,3.6-54.59.86-4.98,1.93-9.91,3.23-14.84,3.94-14.14,9.89-24.98,18.23-33.16,14.07-14.14,35.36-19.08,54.27-12.57,23.09,7.72,37.71,28.83,50.07,50.5,9.98,17.33,8.56,38.76-3.62,54.61Z" fill="#263238"/></g></svg> \ No newline at end of file
diff --git a/ui/src/components/LoginOrRegister.tsx b/ui/src/components/LoginOrRegister.tsx
index f05a9a24..97f8a790 100644
--- a/ui/src/components/LoginOrRegister.tsx
+++ b/ui/src/components/LoginOrRegister.tsx
@@ -6,6 +6,7 @@ import { useStore } from "@/state/store";
interface LoginProps {
toggleRegister: () => void;
+ onClose: () => void;
}
function Login(props: LoginProps) {
@@ -24,7 +25,7 @@ function Login(props: LoginProps) {
try {
await login(username, password, key);
refreshUser();
- console.log("Logged in");
+ props.onClose();
} catch (e: any) {
console.error(e);
setErrors(e);
@@ -171,6 +172,7 @@ function Login(props: LoginProps) {
interface RegisterProps {
toggleLogin: () => void;
+ onClose: () => void;
}
function Register(props: RegisterProps) {
@@ -185,13 +187,11 @@ function Register(props: RegisterProps) {
const email = form.email.value;
const password = form.password.value;
- console.log("Logging in...");
try {
await register(username, email, password);
refreshUser();
- console.log("Logged in");
+ props.onClose();
} catch (e: any) {
- console.error(e);
setErrors(e);
}
};
@@ -330,12 +330,12 @@ function Register(props: RegisterProps) {
);
}
-export default function LoginOrRegister() {
+export default function LoginOrRegister({ onClose }: { onClose: () => void }) {
let [login, setLogin] = useState<boolean>(false);
if (login) {
- return <Login toggleRegister={() => setLogin(false)} />;
+ return <Login onClose={onClose} toggleRegister={() => setLogin(false)} />;
}
- return <Register toggleLogin={() => setLogin(true)} />;
+ return <Register onClose={onClose} toggleLogin={() => setLogin(true)} />;
}
diff --git a/ui/src/components/Sidebar/Sidebar.tsx b/ui/src/components/Sidebar/Sidebar.tsx
new file mode 100644
index 00000000..99e2bf82
--- /dev/null
+++ b/ui/src/components/Sidebar/Sidebar.tsx
@@ -0,0 +1,328 @@
+"use client";
+
+import {
+ Accordion,
+ AccordionItem,
+ type ListboxProps,
+ type ListboxSectionProps,
+ type Selection,
+} from "@nextui-org/react";
+import React from "react";
+import {
+ Listbox,
+ Tooltip,
+ ListboxItem,
+ ListboxSection,
+} from "@nextui-org/react";
+import { Icon } from "@iconify/react";
+
+import { cn } from "@/lib/utils";
+
+export enum SidebarItemType {
+ Nest = "nest",
+}
+
+export type SidebarItem = {
+ key: string;
+ title: string;
+ icon?: string;
+ href?: string;
+ onPress?: () => void;
+ type?: SidebarItemType.Nest;
+ startContent?: React.ReactNode;
+ endContent?: React.ReactNode;
+ items?: SidebarItem[];
+ className?: string;
+};
+
+export type SidebarProps = Omit<ListboxProps<SidebarItem>, "children"> & {
+ items: SidebarItem[];
+ isCompact?: boolean;
+ hideEndContent?: boolean;
+ iconClassName?: string;
+ sectionClasses?: ListboxSectionProps["classNames"];
+ classNames?: ListboxProps["classNames"];
+ defaultSelectedKey: string;
+ onSelect?: (key: string) => void;
+};
+
+const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(
+ (
+ {
+ items,
+ isCompact,
+ defaultSelectedKey,
+ onSelect,
+ hideEndContent,
+ sectionClasses: sectionClassesProp = {},
+ itemClasses: itemClassesProp = {},
+ iconClassName,
+ classNames,
+ className,
+ ...props
+ },
+ ref,
+ ) => {
+ const [selected, setSelected] =
+ React.useState<React.Key>(defaultSelectedKey);
+
+ const sectionClasses = {
+ ...sectionClassesProp,
+ base: cn(sectionClassesProp?.base, "w-full", {
+ "p-0 max-w-[44px]": isCompact,
+ }),
+ group: cn(sectionClassesProp?.group, {
+ "flex flex-col gap-1": isCompact,
+ }),
+ heading: cn(sectionClassesProp?.heading, {
+ hidden: isCompact,
+ }),
+ };
+
+ const itemClasses = {
+ ...itemClassesProp,
+ base: cn(itemClassesProp?.base, {
+ "w-11 h-11 gap-0 p-0": isCompact,
+ }),
+ };
+
+ const renderNestItem = React.useCallback(
+ (item: SidebarItem) => {
+ const isNestType =
+ item.items &&
+ item.items?.length > 0 &&
+ item?.type === SidebarItemType.Nest;
+
+ if (isNestType) {
+ // Is a nest type item , so we need to remove the href
+ delete item.href;
+ }
+
+ return (
+ <ListboxItem
+ {...item}
+ key={item.key}
+ classNames={{
+ base: cn(
+ {
+ "h-auto p-0": !isCompact && isNestType,
+ },
+ {
+ "inline-block w-11": isCompact && isNestType,
+ },
+ ),
+ }}
+ endContent={
+ isCompact || isNestType || hideEndContent
+ ? null
+ : item.endContent ?? null
+ }
+ startContent={
+ isCompact || isNestType ? null : item.icon ? (
+ <Icon
+ className={cn(
+ "text-default-500 group-data-[selected=true]:text-foreground",
+ iconClassName,
+ )}
+ icon={item.icon}
+ width={24}
+ />
+ ) : (
+ item.startContent ?? null
+ )
+ }
+ title={isCompact || isNestType ? null : item.title}
+ >
+ {isCompact ? (
+ <Tooltip content={item.title} placement="right">
+ <div className="flex w-full items-center justify-center">
+ {item.icon ? (
+ <Icon
+ className={cn(
+ "text-default-500 group-data-[selected=true]:text-foreground",
+ iconClassName,
+ )}
+ icon={item.icon}
+ width={24}
+ />
+ ) : (
+ item.startContent ?? null
+ )}
+ </div>
+ </Tooltip>
+ ) : null}
+ {!isCompact && isNestType ? (
+ <Accordion className={"p-0"}>
+ <AccordionItem
+ key={item.key}
+ aria-label={item.title}
+ classNames={{
+ heading: "pr-3",
+ trigger: "p-0",
+ content: "py-0 pl-4",
+ }}
+ title={
+ item.icon ? (
+ <div
+ className={"flex h-11 items-center gap-2 px-2 py-1.5"}
+ >
+ <Icon
+ className={cn(
+ "text-default-500 group-data-[selected=true]:text-foreground",
+ iconClassName,
+ )}
+ icon={item.icon}
+ width={24}
+ />
+ <span className="text-small font-medium text-default-500 group-data-[selected=true]:text-foreground">
+ {item.title}
+ </span>
+ </div>
+ ) : (
+ item.startContent ?? null
+ )
+ }
+ >
+ {item.items && item.items?.length > 0 ? (
+ <Listbox
+ className={"mt-0.5"}
+ classNames={{
+ list: cn("border-l border-default-200 pl-4"),
+ }}
+ items={item.items}
+ variant="flat"
+ >
+ {item.items.map(renderItem)}
+ </Listbox>
+ ) : (
+ renderItem(item)
+ )}
+ </AccordionItem>
+ </Accordion>
+ ) : null}
+ </ListboxItem>
+ );
+ },
+ [isCompact, hideEndContent, iconClassName, items],
+ );
+
+ const renderItem = React.useCallback(
+ (item: SidebarItem) => {
+ const isNestType =
+ item.items &&
+ item.items?.length > 0 &&
+ item?.type === SidebarItemType.Nest;
+
+ if (isNestType) {
+ return renderNestItem(item);
+ }
+
+ return (
+ <ListboxItem
+ {...item}
+ key={item.key}
+ endContent={
+ isCompact || hideEndContent ? null : item.endContent ?? null
+ }
+ startContent={
+ isCompact ? null : item.icon ? (
+ <Icon
+ className={cn(
+ "text-default-500 group-data-[selected=true]:text-foreground",
+ iconClassName,
+ )}
+ icon={item.icon}
+ width={24}
+ />
+ ) : (
+ item.startContent ?? null
+ )
+ }
+ textValue={item.title}
+ title={isCompact ? null : item.title}
+ >
+ {isCompact ? (
+ <Tooltip content={item.title} placement="right">
+ <div className="flex w-full items-center justify-center">
+ {item.icon ? (
+ <Icon
+ className={cn(
+ "text-default-500 group-data-[selected=true]:text-foreground",
+ iconClassName,
+ )}
+ icon={item.icon}
+ width={24}
+ />
+ ) : (
+ item.startContent ?? null
+ )}
+ </div>
+ </Tooltip>
+ ) : null}
+ </ListboxItem>
+ );
+ },
+ [isCompact, hideEndContent, iconClassName, itemClasses?.base],
+ );
+
+ return (
+ <Listbox
+ key={isCompact ? "compact" : "default"}
+ ref={ref}
+ hideSelectedIcon
+ as="nav"
+ className={cn("list-none", className)}
+ classNames={{
+ ...classNames,
+ list: cn("items-center", classNames?.list),
+ }}
+ color="default"
+ itemClasses={{
+ ...itemClasses,
+ base: cn(
+ "px-3 min-h-11 rounded-large h-[44px] data-[selected=true]:bg-default-100",
+ itemClasses?.base,
+ ),
+ title: cn(
+ "text-small font-medium text-default-500 group-data-[selected=true]:text-foreground",
+ itemClasses?.title,
+ ),
+ }}
+ items={items}
+ selectedKeys={[selected] as unknown as Selection}
+ selectionMode="single"
+ variant="flat"
+ onSelectionChange={(keys) => {
+ const key = Array.from(keys)[0];
+
+ setSelected(key as React.Key);
+ onSelect?.(key as string);
+ }}
+ {...props}
+ >
+ {(item) => {
+ return item.items &&
+ item.items?.length > 0 &&
+ item?.type === SidebarItemType.Nest ? (
+ renderNestItem(item)
+ ) : item.items && item.items?.length > 0 ? (
+ <ListboxSection
+ key={item.key}
+ classNames={sectionClasses}
+ showDivider={isCompact}
+ title={item.title}
+ >
+ {item.items.map(renderItem)}
+ </ListboxSection>
+ ) : (
+ renderItem(item)
+ );
+ }}
+ </Listbox>
+ );
+ },
+);
+
+Sidebar.displayName = "Sidebar";
+
+export default Sidebar;
diff --git a/ui/src/components/Sidebar/index.tsx b/ui/src/components/Sidebar/index.tsx
new file mode 100644
index 00000000..10020952
--- /dev/null
+++ b/ui/src/components/Sidebar/index.tsx
@@ -0,0 +1,4 @@
+import Sidebar, { SidebarItem } from "./Sidebar";
+
+export type { SidebarItem };
+export default Sidebar;
diff --git a/ui/src/components/runbooks/editor/Editor.tsx b/ui/src/components/runbooks/editor/Editor.tsx
index 81ea84cf..3f05d9f3 100644
--- a/ui/src/components/runbooks/editor/Editor.tsx
+++ b/ui/src/components/runbooks/editor/Editor.tsx
@@ -60,6 +60,10 @@ export default function Editor() {
content: "Atuin runbooks",
id: "foo",
},
+ {
+ type: "run",
+ id: "bar",
+ },
],
});
diff --git a/ui/src/lib/utils.ts b/ui/src/lib/utils.ts
index d084ccad..c56d3687 100644
--- a/ui/src/lib/utils.ts
+++ b/ui/src/lib/utils.ts
@@ -1,6 +1,31 @@
-import { type ClassValue, clsx } from "clsx"
-import { twMerge } from "tailwind-merge"
+import type { ClassValue } from "clsx";
+
+import clsx from "clsx";
+import { extendTailwindMerge } from "tailwind-merge";
+
+const COMMON_UNITS = ["small", "medium", "large"];
+
+/**
+ * We need to extend the tailwind merge to include NextUI's custom classes.
+ *
+ * So we can use classes like `text-small` or `text-default-500` and override them.
+ */
+const twMerge = extendTailwindMerge({
+ extend: {
+ theme: {
+ opacity: ["disabled"],
+ spacing: ["divider"],
+ borderWidth: COMMON_UNITS,
+ borderRadius: COMMON_UNITS,
+ },
+ classGroups: {
+ shadow: [{ shadow: COMMON_UNITS }],
+ "font-size": [{ text: ["tiny", ...COMMON_UNITS] }],
+ "bg-image": ["bg-stripe-gradient"],
+ },
+ },
+});
export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs))
+ return twMerge(clsx(inputs));
}
diff --git a/ui/src/main.tsx b/ui/src/main.tsx
index 81da3460..dc4c24a3 100644
--- a/ui/src/main.tsx
+++ b/ui/src/main.tsx
@@ -1,10 +1,14 @@
import React from "react";
import ReactDOM from "react-dom/client";
+import { NextUIProvider, Spacer } from "@nextui-org/react";
import App from "./App";
import "./styles.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
- <App />
+ <NextUIProvider>
+ <div data-tauri-drag-region className="w-full h-8 absolute" />
+ <App />
+ </NextUIProvider>
</React.StrictMode>,
);
diff --git a/ui/src/pages/Dotfiles.tsx b/ui/src/pages/Dotfiles.tsx
index cd80be66..56f570be 100644
--- a/ui/src/pages/Dotfiles.tsx
+++ b/ui/src/pages/Dotfiles.tsx
@@ -98,7 +98,7 @@ export default function Dotfiles() {
console.log(current);
return (
- <div className="pl-60">
+ <div className="w-full flex-1 flex-col p-4">
<div className="p-10">
<Header current={current} setCurrent={setCurrent} />
Manage your shell aliases, variables and paths
diff --git a/ui/src/pages/History.tsx b/ui/src/pages/History.tsx
index 81c83f99..64c2fca6 100644
--- a/ui/src/pages/History.tsx
+++ b/ui/src/pages/History.tsx
@@ -84,7 +84,7 @@ export default function Search() {
return (
<>
- <div className="pl-60">
+ <div className="w-full flex-1 flex-col p-4">
<div className="p-10 history-header">
<Header />
<p>A history of all the commands you run in your shell.</p>
diff --git a/ui/src/pages/Home.tsx b/ui/src/pages/Home.tsx
index 9b1c0976..b7c4503d 100644
--- a/ui/src/pages/Home.tsx
+++ b/ui/src/pages/Home.tsx
@@ -125,7 +125,7 @@ export default function Home() {
}
return (
- <div className="pl-60">
+ <div className="w-full flex-1 flex-col p-4">
<div className="p-10">
<Header name={user.username} />
diff --git a/ui/src/pages/Runbooks.tsx b/ui/src/pages/Runbooks.tsx
index 71887310..4237e065 100644
--- a/ui/src/pages/Runbooks.tsx
+++ b/ui/src/pages/Runbooks.tsx
@@ -2,7 +2,7 @@ import Editor from "@/components/runbooks/editor/Editor";
export default function Runbooks() {
return (
- <div className="pl-60 p-4 ">
+ <div className="w-full flex-1 flex-col p-4">
<Editor />
</div>
);
diff --git a/ui/src/state/client.ts b/ui/src/state/client.ts
index 5ec0d8a7..c46fc4e6 100644
--- a/ui/src/state/client.ts
+++ b/ui/src/state/client.ts
@@ -20,6 +20,10 @@ export async function login(
return await invoke("login", { username, password, key });
}
+export async function logout(): Promise<string> {
+ return await invoke("logout");
+}
+
export async function register(
username: string,
email: string,