From 19b73ab4eabbaac85bc53dbad39424108448181e Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Sun, 17 Nov 2024 21:19:55 -0600 Subject: [PATCH] Feature/shadcn sidebar (#1744) * Added sidebar component * Added sidebar css styling (default colors / needs tweaking) * WIP - Sidebar progress * more cleanup * updated sidebar style * Fixed sidebar styling https://github.com/huntabyte/shadcn-svelte/issues/1496 * Fixed main content area * Workaround open mobile issue. * Removed dead code * Fixed whitespace --- .../ClientApp/.vscode/settings.json | 1 + .../ClientApp/package-lock.json | 11 +++ src/Exceptionless.Web/ClientApp/package.json | 3 +- src/Exceptionless.Web/ClientApp/src/app.css | 18 ++++ .../events/components/ExtendedDataItem.svelte | 8 +- .../shared/components/DarkModeButton.svelte | 4 +- .../shared/components/ui/sidebar/constants.ts | 6 ++ .../components/ui/sidebar/context.svelte.ts | 81 +++++++++++++++ .../shared/components/ui/sidebar/index.ts | 75 ++++++++++++++ .../ui/sidebar/sidebar-content.svelte | 24 +++++ .../ui/sidebar/sidebar-footer.svelte | 21 ++++ .../ui/sidebar/sidebar-group-action.svelte | 36 +++++++ .../ui/sidebar/sidebar-group-content.svelte | 21 ++++ .../ui/sidebar/sidebar-group-label.svelte | 34 +++++++ .../ui/sidebar/sidebar-group.svelte | 21 ++++ .../ui/sidebar/sidebar-header.svelte | 21 ++++ .../ui/sidebar/sidebar-input.svelte | 23 +++++ .../ui/sidebar/sidebar-inset.svelte | 24 +++++ .../ui/sidebar/sidebar-menu-action.svelte | 43 ++++++++ .../ui/sidebar/sidebar-menu-badge.svelte | 29 ++++++ .../ui/sidebar/sidebar-menu-button.svelte | 97 ++++++++++++++++++ .../ui/sidebar/sidebar-menu-item.svelte | 21 ++++ .../ui/sidebar/sidebar-menu-skeleton.svelte | 36 +++++++ .../ui/sidebar/sidebar-menu-sub-button.svelte | 43 ++++++++ .../ui/sidebar/sidebar-menu-sub-item.svelte | 14 +++ .../ui/sidebar/sidebar-menu-sub.svelte | 25 +++++ .../components/ui/sidebar/sidebar-menu.svelte | 21 ++++ .../ui/sidebar/sidebar-provider.svelte | 59 +++++++++++ .../components/ui/sidebar/sidebar-rail.svelte | 36 +++++++ .../ui/sidebar/sidebar-separator.svelte | 18 ++++ .../ui/sidebar/sidebar-trigger.svelte | 34 +++++++ .../components/ui/sidebar/sidebar.svelte | 98 +++++++++++++++++++ .../shared/components/ui/tooltip/index.ts | 18 ++++ .../ui/tooltip/tooltip-content.svelte | 21 ++++ .../src/lib/hooks/is-mobile.svelte.ts | 27 +++++ .../(app)/(components)/layouts/Footer.svelte | 8 +- .../(app)/(components)/layouts/Navbar.svelte | 33 ++----- .../(app)/(components)/layouts/Sidebar.svelte | 64 ++++++------ .../layouts/SidebarMenuItem.svelte | 30 ------ .../ClientApp/src/routes/(app)/+layout.svelte | 30 +++--- .../(app)/account/sessions/+page.svelte | 4 +- .../ClientApp/src/routes/+layout.svelte | 5 +- .../ClientApp/tailwind.config.js | 10 ++ 43 files changed, 1136 insertions(+), 120 deletions(-) create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/constants.ts create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/context.svelte.ts create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/index.ts create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-content.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-footer.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group-action.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group-content.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group-label.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-header.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-input.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-inset.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-action.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-badge.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-button.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-item.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-skeleton.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-sub-button.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-sub-item.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-sub.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-provider.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-rail.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-separator.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-trigger.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/tooltip/index.ts create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/tooltip/tooltip-content.svelte create mode 100644 src/Exceptionless.Web/ClientApp/src/lib/hooks/is-mobile.svelte.ts delete mode 100644 src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/SidebarMenuItem.svelte diff --git a/src/Exceptionless.Web/ClientApp/.vscode/settings.json b/src/Exceptionless.Web/ClientApp/.vscode/settings.json index 08fb7d3113..c6ed346fd4 100644 --- a/src/Exceptionless.Web/ClientApp/.vscode/settings.json +++ b/src/Exceptionless.Web/ClientApp/.vscode/settings.json @@ -13,6 +13,7 @@ "keyof", "legos", "lucene", + "lucide", "nameof", "navigatetofirstpage", "oidc", diff --git a/src/Exceptionless.Web/ClientApp/package-lock.json b/src/Exceptionless.Web/ClientApp/package-lock.json index 4f2a8deb17..e297ffded9 100644 --- a/src/Exceptionless.Web/ClientApp/package-lock.json +++ b/src/Exceptionless.Web/ClientApp/package-lock.json @@ -34,6 +34,7 @@ "unplugin-icons": "^0.20.1" }, "devDependencies": { + "@iconify-json/lucide": "^1.2.14", "@playwright/test": "^1.48.2", "@sveltejs/adapter-static": "^3.0.6", "@sveltejs/kit": "^2.8.1", @@ -935,6 +936,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@iconify-json/lucide": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.14.tgz", + "integrity": "sha512-NDJKk1NQRNymlr5B5uiWBRc3RAZ8r+8A1F4SU+60udy05V0GwILAnoJvlg6oY0WZBhqb/ChaU1LWm1XHmJ4DEg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@iconify/types": "*" + } + }, "node_modules/@iconify-json/mdi": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@iconify-json/mdi/-/mdi-1.2.1.tgz", diff --git a/src/Exceptionless.Web/ClientApp/package.json b/src/Exceptionless.Web/ClientApp/package.json index fdb818ea6c..4e5c1d80e9 100644 --- a/src/Exceptionless.Web/ClientApp/package.json +++ b/src/Exceptionless.Web/ClientApp/package.json @@ -23,6 +23,7 @@ "upgrade": "ncu -i" }, "devDependencies": { + "@iconify-json/lucide": "^1.2.14", "@playwright/test": "^1.48.2", "@sveltejs/adapter-static": "^3.0.6", "@sveltejs/kit": "^2.8.1", @@ -77,4 +78,4 @@ "unplugin-icons": "^0.20.1" }, "type": "module" -} \ No newline at end of file +} diff --git a/src/Exceptionless.Web/ClientApp/src/app.css b/src/Exceptionless.Web/ClientApp/src/app.css index 517ad0e4e3..4770b5c12b 100644 --- a/src/Exceptionless.Web/ClientApp/src/app.css +++ b/src/Exceptionless.Web/ClientApp/src/app.css @@ -35,6 +35,15 @@ --ring: 221 39% 11%; --radius: 0.375rem; + + --sidebar-background: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); } .dark { @@ -66,6 +75,15 @@ --destructive-foreground: 0 0% 100%; --ring: 96 64.1% 45.88%; + + --sidebar-background: var(--background); + --sidebar-foreground: var(--foreground); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: var(--accent); + --sidebar-accent-foreground: var(--accent-foreground); + --sidebar-border: var(--border); + --sidebar-ring: var(--ring); } } diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/ExtendedDataItem.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/ExtendedDataItem.svelte index 3478cbc76b..04ee27573d 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/ExtendedDataItem.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/ExtendedDataItem.svelte @@ -69,9 +69,13 @@ {#if canPromote} {#if !isPromoted} - + {:else} - + {/if} {/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/DarkModeButton.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/DarkModeButton.svelte index bd76f6f0e4..d52c98b717 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/DarkModeButton.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/DarkModeButton.svelte @@ -13,8 +13,8 @@ diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/constants.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/constants.ts new file mode 100644 index 0000000000..4de44351d4 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/constants.ts @@ -0,0 +1,6 @@ +export const SIDEBAR_COOKIE_NAME = "sidebar:state"; +export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +export const SIDEBAR_WIDTH = "16rem"; +export const SIDEBAR_WIDTH_MOBILE = "18rem"; +export const SIDEBAR_WIDTH_ICON = "3rem"; +export const SIDEBAR_KEYBOARD_SHORTCUT = "b"; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/context.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/context.svelte.ts new file mode 100644 index 0000000000..15248adace --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/context.svelte.ts @@ -0,0 +1,81 @@ +import { IsMobile } from "$lib/hooks/is-mobile.svelte.js"; +import { getContext, setContext } from "svelte"; +import { SIDEBAR_KEYBOARD_SHORTCUT } from "./constants.js"; + +type Getter = () => T; + +export type SidebarStateProps = { + /** + * A getter function that returns the current open state of the sidebar. + * We use a getter function here to support `bind:open` on the `Sidebar.Provider` + * component. + */ + open: Getter; + + /** + * A function that sets the open state of the sidebar. To support `bind:open`, we need + * a source of truth for changing the open state to ensure it will be synced throughout + * the sub-components and any `bind:` references. + */ + setOpen: (open: boolean) => void; +}; + +class SidebarState { + readonly props: SidebarStateProps; + open = $derived.by(() => this.props.open()); + openMobile = $state(false); + setOpen: SidebarStateProps["setOpen"]; + #isMobile: IsMobile; + state = $derived.by(() => (this.open ? "expanded" : "collapsed")); + + constructor(props: SidebarStateProps) { + this.setOpen = props.setOpen; + this.#isMobile = new IsMobile(); + this.props = props; + } + + // Convenience getter for checking if the sidebar is mobile + // without this, we would need to use `sidebar.isMobile.current` everywhere + get isMobile() { + return this.#isMobile.current; + } + + // Event handler to apply to the `` + handleShortcutKeydown = (e: KeyboardEvent) => { + if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + this.toggle(); + } + }; + + setOpenMobile = (value: boolean) => { + this.openMobile = value; + }; + + toggle = () => { + return this.#isMobile.current + ? (this.openMobile = !this.openMobile) + : this.setOpen(!this.open); + }; +} + +const SYMBOL_KEY = "scn-sidebar"; + +/** + * Instantiates a new `SidebarState` instance and sets it in the context. + * + * @param props The constructor props for the `SidebarState` class. + * @returns The `SidebarState` instance. + */ +export function setSidebar(props: SidebarStateProps): SidebarState { + return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props)); +} + +/** + * Retrieves the `SidebarState` instance from the context. This is a class instance, + * so you cannot destructure it. + * @returns The `SidebarState` instance. + */ +export function useSidebar(): SidebarState { + return getContext(Symbol.for(SYMBOL_KEY)); +} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/index.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/index.ts new file mode 100644 index 0000000000..318a341785 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/index.ts @@ -0,0 +1,75 @@ +import { useSidebar } from "./context.svelte.js"; +import Content from "./sidebar-content.svelte"; +import Footer from "./sidebar-footer.svelte"; +import GroupAction from "./sidebar-group-action.svelte"; +import GroupContent from "./sidebar-group-content.svelte"; +import GroupLabel from "./sidebar-group-label.svelte"; +import Group from "./sidebar-group.svelte"; +import Header from "./sidebar-header.svelte"; +import Input from "./sidebar-input.svelte"; +import Inset from "./sidebar-inset.svelte"; +import MenuAction from "./sidebar-menu-action.svelte"; +import MenuBadge from "./sidebar-menu-badge.svelte"; +import MenuButton from "./sidebar-menu-button.svelte"; +import MenuItem from "./sidebar-menu-item.svelte"; +import MenuSkeleton from "./sidebar-menu-skeleton.svelte"; +import MenuSubButton from "./sidebar-menu-sub-button.svelte"; +import MenuSubItem from "./sidebar-menu-sub-item.svelte"; +import MenuSub from "./sidebar-menu-sub.svelte"; +import Menu from "./sidebar-menu.svelte"; +import Provider from "./sidebar-provider.svelte"; +import Rail from "./sidebar-rail.svelte"; +import Separator from "./sidebar-separator.svelte"; +import Trigger from "./sidebar-trigger.svelte"; +import Root from "./sidebar.svelte"; + +export { + Content, + Footer, + Group, + GroupAction, + GroupContent, + GroupLabel, + Header, + Input, + Inset, + Menu, + MenuAction, + MenuBadge, + MenuButton, + MenuItem, + MenuSkeleton, + MenuSub, + MenuSubButton, + MenuSubItem, + Provider, + Rail, + Root, + Separator, + // + Root as Sidebar, + Content as SidebarContent, + Footer as SidebarFooter, + Group as SidebarGroup, + GroupAction as SidebarGroupAction, + GroupContent as SidebarGroupContent, + GroupLabel as SidebarGroupLabel, + Header as SidebarHeader, + Input as SidebarInput, + Inset as SidebarInset, + Menu as SidebarMenu, + MenuAction as SidebarMenuAction, + MenuBadge as SidebarMenuBadge, + MenuButton as SidebarMenuButton, + MenuItem as SidebarMenuItem, + MenuSkeleton as SidebarMenuSkeleton, + MenuSub as SidebarMenuSub, + MenuSubButton as SidebarMenuSubButton, + MenuSubItem as SidebarMenuSubItem, + Provider as SidebarProvider, + Rail as SidebarRail, + Separator as SidebarSeparator, + Trigger as SidebarTrigger, + Trigger, + useSidebar, +}; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-content.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-content.svelte new file mode 100644 index 0000000000..c6db009f9b --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-content.svelte @@ -0,0 +1,24 @@ + + +
+ {@render children?.()} +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-footer.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-footer.svelte new file mode 100644 index 0000000000..c62cb410c8 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-footer.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group-action.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group-action.svelte new file mode 100644 index 0000000000..6961e4c0b7 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group-action.svelte @@ -0,0 +1,36 @@ + + +{#if child} + {@render child({ props: propObj })} +{:else} + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group-content.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group-content.svelte new file mode 100644 index 0000000000..f5e623b9a8 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group-content.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group-label.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group-label.svelte new file mode 100644 index 0000000000..77c180fdec --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group-label.svelte @@ -0,0 +1,34 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} +
+ {@render children?.()} +
+{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group.svelte new file mode 100644 index 0000000000..aa8bf31ad3 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-group.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-header.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-header.svelte new file mode 100644 index 0000000000..5656c0bcd9 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-header.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-input.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-input.svelte new file mode 100644 index 0000000000..0ae2db39cf --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-input.svelte @@ -0,0 +1,23 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-inset.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-inset.svelte new file mode 100644 index 0000000000..b9cd447beb --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-inset.svelte @@ -0,0 +1,24 @@ + + +
+ {@render children?.()} +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-action.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-action.svelte new file mode 100644 index 0000000000..5a94f39add --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-action.svelte @@ -0,0 +1,43 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-badge.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-badge.svelte new file mode 100644 index 0000000000..0fd4c5159d --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-badge.svelte @@ -0,0 +1,29 @@ + + +
+ {@render children?.()} +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-button.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-button.svelte new file mode 100644 index 0000000000..1b4674f8f2 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-button.svelte @@ -0,0 +1,97 @@ + + + + +{#snippet Button({ props }: { props?: Record })} + {@const mergedProps = mergeProps(buttonProps, props)} + {#if child} + {@render child({ props: mergedProps })} + {:else} + + {/if} +{/snippet} + +{#if !tooltipContent} + {@render Button({})} +{:else} + + + {#snippet child({ props })} + {@render Button({ props })} + {/snippet} + + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-item.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-item.svelte new file mode 100644 index 0000000000..ee82144bf6 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-item.svelte @@ -0,0 +1,21 @@ + + +
  • + {@render children?.()} +
  • diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-skeleton.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-skeleton.svelte new file mode 100644 index 0000000000..28d56333ec --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-skeleton.svelte @@ -0,0 +1,36 @@ + + +
    + {#if showIcon} + + {/if} + + {@render children?.()} +
    diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-sub-button.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-sub-button.svelte new file mode 100644 index 0000000000..26b2a90b5c --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-sub-button.svelte @@ -0,0 +1,43 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} + + {@render children?.()} + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-sub-item.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-sub-item.svelte new file mode 100644 index 0000000000..6e7346d3a2 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-sub-item.svelte @@ -0,0 +1,14 @@ + + +
  • + {@render children?.()} +
  • diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-sub.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-sub.svelte new file mode 100644 index 0000000000..2a23990568 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu-sub.svelte @@ -0,0 +1,25 @@ + + +
      + {@render children?.()} +
    diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu.svelte new file mode 100644 index 0000000000..72534db3e1 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-menu.svelte @@ -0,0 +1,21 @@ + + +
      + {@render children?.()} +
    diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-provider.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-provider.svelte new file mode 100644 index 0000000000..3fec9c2c69 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-provider.svelte @@ -0,0 +1,59 @@ + + + + + +
    + {@render children?.()} +
    +
    diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-rail.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-rail.svelte new file mode 100644 index 0000000000..ee16fce1ad --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-rail.svelte @@ -0,0 +1,36 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-separator.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-separator.svelte new file mode 100644 index 0000000000..078a50d069 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-separator.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-trigger.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-trigger.svelte new file mode 100644 index 0000000000..8cf93cf723 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar-trigger.svelte @@ -0,0 +1,34 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar.svelte new file mode 100644 index 0000000000..a5237ac299 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/sidebar/sidebar.svelte @@ -0,0 +1,98 @@ + + +{#if collapsible === "none"} +
    + {@render children?.()} +
    +{:else if sidebar.isMobile} + + +
    + {@render children?.()} +
    +
    +
    +{:else} + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/tooltip/index.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/tooltip/index.ts new file mode 100644 index 0000000000..e9e1fd7399 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/tooltip/index.ts @@ -0,0 +1,18 @@ +import { Tooltip as TooltipPrimitive } from "bits-ui"; +import Content from "./tooltip-content.svelte"; + +const Root = TooltipPrimitive.Root; +const Trigger = TooltipPrimitive.Trigger; +const Provider = TooltipPrimitive.Provider; + +export { + Root, + Trigger, + Content, + Provider, + // + Root as Tooltip, + Content as TooltipContent, + Trigger as TooltipTrigger, + Provider as TooltipProvider, +}; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/tooltip/tooltip-content.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/tooltip/tooltip-content.svelte new file mode 100644 index 0000000000..dc7251cee0 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/tooltip/tooltip-content.svelte @@ -0,0 +1,21 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/hooks/is-mobile.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/hooks/is-mobile.svelte.ts new file mode 100644 index 0000000000..76d3b25d8b --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/hooks/is-mobile.svelte.ts @@ -0,0 +1,27 @@ +import { untrack } from 'svelte'; + +const MOBILE_BREAKPOINT = 768; + +export class IsMobile { + #current = $state(false); + + constructor() { + $effect(() => { + return untrack(() => { + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); + const onChange = () => { + this.#current = window.innerWidth < MOBILE_BREAKPOINT; + }; + mql.addEventListener('change', onChange); + onChange(); + return () => { + mql.removeEventListener('change', onChange); + }; + }); + }); + } + + get current() { + return this.#current; + } +} diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Footer.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Footer.svelte index 701660d60b..ae5d3fe726 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Footer.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Footer.svelte @@ -32,16 +32,16 @@ diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Navbar.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Navbar.svelte index 03284833b2..221c7a95fb 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Navbar.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Navbar.svelte @@ -5,49 +5,36 @@ import * as Avatar from '$comp/ui/avatar'; import { Button } from '$comp/ui/button'; import * as DropdownMenu from '$comp/ui/dropdown-menu'; + import * as Sidebar from '$comp/ui/sidebar'; import { getGravatarFromCurrentUser } from '$features/users/gravatar.svelte'; import logoSmall from '$lib/assets/exceptionless-48.png'; import logo from '$lib/assets/logo.svg'; import logoDark from '$lib/assets/logo-dark.svg'; - import IconClose from '~icons/mdi/close'; - import IconMenu from '~icons/mdi/menu'; import IconSearch from '~icons/mdi/search'; + import { MediaQuery } from 'runed'; interface Props { isCommandOpen: boolean; - isMediumScreen?: boolean; - isSidebarOpen: boolean; } - let { isCommandOpen = $bindable(), isMediumScreen, isSidebarOpen = $bindable() }: Props = $props(); - - function onHamburgerClick(): void { - isSidebarOpen = !isSidebarOpen; - } + let { isCommandOpen = $bindable() }: Props = $props(); function onSearchClick(): void { isCommandOpen = true; } const gravatar = getGravatarFromCurrentUser(); + const isMediumScreenQuery = new MediaQuery('(min-width: 768px)');