Skip to content

Commit 19b73ab

Browse files
authored
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 huntabyte/shadcn-svelte#1496 * Fixed main content area * Workaround open mobile issue. * Removed dead code * Fixed whitespace
1 parent b5cf043 commit 19b73ab

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1136
-120
lines changed

src/Exceptionless.Web/ClientApp/.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"keyof",
1414
"legos",
1515
"lucene",
16+
"lucide",
1617
"nameof",
1718
"navigatetofirstpage",
1819
"oidc",

src/Exceptionless.Web/ClientApp/package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Exceptionless.Web/ClientApp/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"upgrade": "ncu -i"
2424
},
2525
"devDependencies": {
26+
"@iconify-json/lucide": "^1.2.14",
2627
"@playwright/test": "^1.48.2",
2728
"@sveltejs/adapter-static": "^3.0.6",
2829
"@sveltejs/kit": "^2.8.1",
@@ -77,4 +78,4 @@
7778
"unplugin-icons": "^0.20.1"
7879
},
7980
"type": "module"
80-
}
81+
}

src/Exceptionless.Web/ClientApp/src/app.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@
3535
--ring: 221 39% 11%;
3636

3737
--radius: 0.375rem;
38+
39+
--sidebar-background: var(--background);
40+
--sidebar-foreground: var(--foreground);
41+
--sidebar-primary: var(--primary);
42+
--sidebar-primary-foreground: var(--primary-foreground);
43+
--sidebar-accent: var(--accent);
44+
--sidebar-accent-foreground: var(--accent-foreground);
45+
--sidebar-border: var(--border);
46+
--sidebar-ring: var(--ring);
3847
}
3948

4049
.dark {
@@ -66,6 +75,15 @@
6675
--destructive-foreground: 0 0% 100%;
6776

6877
--ring: 96 64.1% 45.88%;
78+
79+
--sidebar-background: var(--background);
80+
--sidebar-foreground: var(--foreground);
81+
--sidebar-primary: var(--primary);
82+
--sidebar-primary-foreground: var(--primary-foreground);
83+
--sidebar-accent: var(--accent);
84+
--sidebar-accent-foreground: var(--accent-foreground);
85+
--sidebar-border: var(--border);
86+
--sidebar-ring: var(--ring);
6987
}
7088
}
7189

src/Exceptionless.Web/ClientApp/src/lib/features/events/components/ExtendedDataItem.svelte

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,13 @@
6969

7070
{#if canPromote}
7171
{#if !isPromoted}
72-
<Button onclick={async () => await promote(title)} size="icon" title="Promote to Tab"><ArrowUpIcon /></Button>
72+
<Button onclick={async () => await promote(title)} size="icon" title="Promote to Tab"
73+
><ArrowUpIcon /><span class="sr-only">Promote to Tab</span></Button
74+
>
7375
{:else}
74-
<Button onclick={async () => await demote(title)} size="icon" title="Demote Tab"><ArrowDownIcon /></Button>
76+
<Button onclick={async () => await demote(title)} size="icon" title="Demote Tab"
77+
><ArrowDownIcon /><span class="sr-only">Demote Tab</span></Button
78+
>
7579
{/if}
7680
{/if}
7781
</div>

src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/DarkModeButton.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
<ContextMenu.Root>
1414
<ContextMenu.Trigger>
1515
<Button onclick={toggleMode} size="icon" title="Toggle dark mode" variant="outline">
16-
<IconWhiteBalanceSunny class="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
17-
<IconMoonWaningCrescent class="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
16+
<IconWhiteBalanceSunny class="rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
17+
<IconMoonWaningCrescent class="absolute ml-1 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
1818
<span class="sr-only">Toggle theme</span>
1919
</Button>
2020
</ContextMenu.Trigger>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const SIDEBAR_COOKIE_NAME = "sidebar:state";
2+
export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
3+
export const SIDEBAR_WIDTH = "16rem";
4+
export const SIDEBAR_WIDTH_MOBILE = "18rem";
5+
export const SIDEBAR_WIDTH_ICON = "3rem";
6+
export const SIDEBAR_KEYBOARD_SHORTCUT = "b";
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { IsMobile } from "$lib/hooks/is-mobile.svelte.js";
2+
import { getContext, setContext } from "svelte";
3+
import { SIDEBAR_KEYBOARD_SHORTCUT } from "./constants.js";
4+
5+
type Getter<T> = () => T;
6+
7+
export type SidebarStateProps = {
8+
/**
9+
* A getter function that returns the current open state of the sidebar.
10+
* We use a getter function here to support `bind:open` on the `Sidebar.Provider`
11+
* component.
12+
*/
13+
open: Getter<boolean>;
14+
15+
/**
16+
* A function that sets the open state of the sidebar. To support `bind:open`, we need
17+
* a source of truth for changing the open state to ensure it will be synced throughout
18+
* the sub-components and any `bind:` references.
19+
*/
20+
setOpen: (open: boolean) => void;
21+
};
22+
23+
class SidebarState {
24+
readonly props: SidebarStateProps;
25+
open = $derived.by(() => this.props.open());
26+
openMobile = $state(false);
27+
setOpen: SidebarStateProps["setOpen"];
28+
#isMobile: IsMobile;
29+
state = $derived.by(() => (this.open ? "expanded" : "collapsed"));
30+
31+
constructor(props: SidebarStateProps) {
32+
this.setOpen = props.setOpen;
33+
this.#isMobile = new IsMobile();
34+
this.props = props;
35+
}
36+
37+
// Convenience getter for checking if the sidebar is mobile
38+
// without this, we would need to use `sidebar.isMobile.current` everywhere
39+
get isMobile() {
40+
return this.#isMobile.current;
41+
}
42+
43+
// Event handler to apply to the `<svelte:window>`
44+
handleShortcutKeydown = (e: KeyboardEvent) => {
45+
if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) {
46+
e.preventDefault();
47+
this.toggle();
48+
}
49+
};
50+
51+
setOpenMobile = (value: boolean) => {
52+
this.openMobile = value;
53+
};
54+
55+
toggle = () => {
56+
return this.#isMobile.current
57+
? (this.openMobile = !this.openMobile)
58+
: this.setOpen(!this.open);
59+
};
60+
}
61+
62+
const SYMBOL_KEY = "scn-sidebar";
63+
64+
/**
65+
* Instantiates a new `SidebarState` instance and sets it in the context.
66+
*
67+
* @param props The constructor props for the `SidebarState` class.
68+
* @returns The `SidebarState` instance.
69+
*/
70+
export function setSidebar(props: SidebarStateProps): SidebarState {
71+
return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props));
72+
}
73+
74+
/**
75+
* Retrieves the `SidebarState` instance from the context. This is a class instance,
76+
* so you cannot destructure it.
77+
* @returns The `SidebarState` instance.
78+
*/
79+
export function useSidebar(): SidebarState {
80+
return getContext(Symbol.for(SYMBOL_KEY));
81+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { useSidebar } from "./context.svelte.js";
2+
import Content from "./sidebar-content.svelte";
3+
import Footer from "./sidebar-footer.svelte";
4+
import GroupAction from "./sidebar-group-action.svelte";
5+
import GroupContent from "./sidebar-group-content.svelte";
6+
import GroupLabel from "./sidebar-group-label.svelte";
7+
import Group from "./sidebar-group.svelte";
8+
import Header from "./sidebar-header.svelte";
9+
import Input from "./sidebar-input.svelte";
10+
import Inset from "./sidebar-inset.svelte";
11+
import MenuAction from "./sidebar-menu-action.svelte";
12+
import MenuBadge from "./sidebar-menu-badge.svelte";
13+
import MenuButton from "./sidebar-menu-button.svelte";
14+
import MenuItem from "./sidebar-menu-item.svelte";
15+
import MenuSkeleton from "./sidebar-menu-skeleton.svelte";
16+
import MenuSubButton from "./sidebar-menu-sub-button.svelte";
17+
import MenuSubItem from "./sidebar-menu-sub-item.svelte";
18+
import MenuSub from "./sidebar-menu-sub.svelte";
19+
import Menu from "./sidebar-menu.svelte";
20+
import Provider from "./sidebar-provider.svelte";
21+
import Rail from "./sidebar-rail.svelte";
22+
import Separator from "./sidebar-separator.svelte";
23+
import Trigger from "./sidebar-trigger.svelte";
24+
import Root from "./sidebar.svelte";
25+
26+
export {
27+
Content,
28+
Footer,
29+
Group,
30+
GroupAction,
31+
GroupContent,
32+
GroupLabel,
33+
Header,
34+
Input,
35+
Inset,
36+
Menu,
37+
MenuAction,
38+
MenuBadge,
39+
MenuButton,
40+
MenuItem,
41+
MenuSkeleton,
42+
MenuSub,
43+
MenuSubButton,
44+
MenuSubItem,
45+
Provider,
46+
Rail,
47+
Root,
48+
Separator,
49+
//
50+
Root as Sidebar,
51+
Content as SidebarContent,
52+
Footer as SidebarFooter,
53+
Group as SidebarGroup,
54+
GroupAction as SidebarGroupAction,
55+
GroupContent as SidebarGroupContent,
56+
GroupLabel as SidebarGroupLabel,
57+
Header as SidebarHeader,
58+
Input as SidebarInput,
59+
Inset as SidebarInset,
60+
Menu as SidebarMenu,
61+
MenuAction as SidebarMenuAction,
62+
MenuBadge as SidebarMenuBadge,
63+
MenuButton as SidebarMenuButton,
64+
MenuItem as SidebarMenuItem,
65+
MenuSkeleton as SidebarMenuSkeleton,
66+
MenuSub as SidebarMenuSub,
67+
MenuSubButton as SidebarMenuSubButton,
68+
MenuSubItem as SidebarMenuSubItem,
69+
Provider as SidebarProvider,
70+
Rail as SidebarRail,
71+
Separator as SidebarSeparator,
72+
Trigger as SidebarTrigger,
73+
Trigger,
74+
useSidebar,
75+
};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script lang="ts">
2+
import type { HTMLAttributes } from "svelte/elements";
3+
import type { WithElementRef } from "bits-ui";
4+
import { cn } from "$lib/utils.js";
5+
6+
let {
7+
ref = $bindable(null),
8+
class: className,
9+
children,
10+
...restProps
11+
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
12+
</script>
13+
14+
<div
15+
bind:this={ref}
16+
data-sidebar="content"
17+
class={cn(
18+
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
19+
className
20+
)}
21+
{...restProps}
22+
>
23+
{@render children?.()}
24+
</div>

0 commit comments

Comments
 (0)