Skip to content

Commit

Permalink
feat(dashboard): side navigation (#6608)
Browse files Browse the repository at this point in the history
  • Loading branch information
LetItRock authored Oct 4, 2024
1 parent f60aa5b commit e900c02
Show file tree
Hide file tree
Showing 17 changed files with 1,238 additions and 614 deletions.
2 changes: 2 additions & 0 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@segment/analytics-next": "^1.73.0",
"@tanstack/react-query": "^4.20.4",
Expand All @@ -38,6 +39,7 @@
"react-dom": "^18.3.1",
"react-helmet-async": "^1.3.0",
"react-hook-form": "7.43.9",
"react-icons": "^5.0.1",
"react-router-dom": "6.26.2",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
Expand Down
32 changes: 18 additions & 14 deletions apps/dashboard/src/components/dashboard-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import { ReactNode } from 'react';
import { UserProfile } from '@/components/user-profile';
import { InboxButton } from '@/components/inbox-button';
import { SideNavigation } from './side-navigation';

export const DashboardLayout = ({ children }: { children: ReactNode }) => {
return (
<div className="relative min-h-dvh">
<div className="fixed left-0 top-0 flex h-16 w-full items-center justify-between bg-green-200 p-4">
<a
href="/legacy/integrations"
target="_self"
className="text-blue-600 visited:text-purple-600 hover:border-b hover:border-current"
>
Integrations
</a>
<div className="flex gap-4">
<InboxButton />
<UserProfile />
<div className="relative flex h-screen w-full">
<SideNavigation />
<div className="flex min-h-screen flex-1 flex-col overflow-y-auto overflow-x-hidden">
<div className="bg-background flex h-16 w-full items-center justify-between border-b p-4">
<a
href="/legacy/integrations"
target="_self"
className="text-blue-600 visited:text-purple-600 hover:border-b hover:border-current"
>
Integrations
</a>
<div className="flex gap-4">
<InboxButton />
<UserProfile />
</div>
</div>
</div>

<div className="pt-16">{children}</div>
<div className="overflow-y-auto overflow-x-hidden">{children}</div>
</div>
</div>
);
};
13 changes: 9 additions & 4 deletions apps/dashboard/src/components/primitives/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,31 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/utils/ui';

const badgeVariants = cva(
'inline-flex items-center rounded-md border px-2 py-1 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
'inline-flex items-center h-5 border px-2 py-1 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {
default: 'border-transparent bg-secondary-alpha-100 text-secondary-500',
default: 'border-transparent bg-secondary-alpha-100 text-secondary-300',
destructive: 'border-transparent bg-destructive/10 text-destructive',
success: 'border-transparent bg-success/10 text-success',
warning: 'border-transparent bg-warning/10 text-warning',
},
kind: {
default: 'rounded-md',
pill: 'rounded-full',
},
},
defaultVariants: {
variant: 'default',
kind: 'default',
},
}
);

export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
function Badge({ className, variant, kind, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant, kind }), className)} {...props} />;
}

export { Badge, badgeVariants };
145 changes: 145 additions & 0 deletions apps/dashboard/src/components/primitives/select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import * as React from 'react';
import { CaretSortIcon, CheckIcon, ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons';
import * as SelectPrimitive from '@radix-ui/react-select';

import { cn } from '@/utils/ui';

const Select = SelectPrimitive.Root;

const SelectGroup = SelectPrimitive.Group;

const SelectValue = SelectPrimitive.Value;

const SelectIcon = SelectPrimitive.Icon;

const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
'border-input ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border bg-transparent px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-1 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<CaretSortIcon className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;

const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn('flex cursor-default items-center justify-center py-1', className)}
{...props}
>
<ChevronUpIcon />
</SelectPrimitive.ScrollUpButton>
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;

const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn('flex cursor-default items-center justify-center py-1', className)}
{...props}
>
<ChevronDownIcon />
</SelectPrimitive.ScrollDownButton>
));
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;

const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = 'popper', ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
'bg-background text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border shadow-md',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
'p-1',
position === 'popper' &&
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;

const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label ref={ref} className={cn('px-2 py-1.5 text-sm font-semibold', className)} {...props} />
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;

const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;

const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator ref={ref} className={cn('bg-muted -mx-1 my-1 h-px', className)} {...props} />
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;

export {
Select,
SelectGroup,
SelectValue,
SelectIcon,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
};
86 changes: 86 additions & 0 deletions apps/dashboard/src/components/side-navigation/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
RiBarChartBoxLine,
RiGroup2Line,
RiKey2Line,
RiPaintBrushLine,
RiRouteFill,
RiSettings4Line,
RiStore3Line,
RiUserAddLine,
} from 'react-icons/ri';
import { NavItemsGroup } from './types';
import { LEGACY_ROUTES, ROUTES } from '@/utils/routes';

export const navigationItems: NavItemsGroup[] = [
{
items: [
{
label: 'Workflows',
icon: RiRouteFill,
to: ROUTES.WORKFLOWS,
},
{
label: 'Subscribers',
icon: RiGroup2Line,
isExternal: true,
to: 'https://docs.novu.co/api-reference/subscribers/get-subscribers',
disabled: true,
},
],
},
{
label: 'Monitor',
items: [
{
label: 'Activity Feed',
icon: RiBarChartBoxLine,
to: LEGACY_ROUTES.ACTIVITY_FEED,
isExternal: true,
},
],
},
{
label: 'Developer',
items: [
{
label: 'Integration Store',
icon: RiStore3Line,
to: LEGACY_ROUTES.INTEGRATIONS,
isExternal: true,
},
{
label: 'API Keys',
icon: RiKey2Line,
to: LEGACY_ROUTES.API_KEYS,
isExternal: true,
},
],
},
{
label: 'Application',
items: [
{
label: 'Branding',
icon: RiPaintBrushLine,
to: LEGACY_ROUTES.BRANDING,
isExternal: true,
},
{
label: 'Settings',
icon: RiSettings4Line,
to: LEGACY_ROUTES.SETTINGS,
isExternal: true,
},
],
},
{
items: [
{
label: 'Invite teammates',
icon: RiUserAddLine,
to: LEGACY_ROUTES.INVITE_TEAM_MEMBERS,
isExternal: true,
},
],
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { cva } from 'class-variance-authority';
import { RiExpandUpDownLine, RiGitBranchLine } from 'react-icons/ri';
import { Select, SelectContent, SelectIcon, SelectItem, SelectTrigger, SelectValue } from '../primitives/select';

const logoVariants = cva(`size-6 rounded-[6px] border-[1px] border-solid p-1 `, {
variants: {
variant: {
default: 'bg-warning/10 border-warning text-warning',
production: 'bg-feature/10 border-feature text-feature',
},
},
defaultVariants: {
variant: 'default',
},
});

type EnvironmentDropdownProps = {
value?: string;
data?: string[];
onChange?: (value: string) => void;
};

export const EnvironmentDropdown = ({ value, data, onChange }: EnvironmentDropdownProps) => {
return (
<Select value={value} onValueChange={onChange}>
<SelectTrigger className="group p-1.5 shadow-sm last:[&>svg]:hidden">
<SelectValue asChild>
<div className="flex items-center gap-2">
<div
className={logoVariants({
variant: value?.toLocaleLowerCase() === 'production' ? 'production' : 'default',
})}
>
<RiGitBranchLine className="size-4" />
</div>
<span className="text-foreground text-sm">{value}</span>
</div>
</SelectValue>
<SelectIcon asChild>
<RiExpandUpDownLine className="ml-auto size-4 opacity-0 transition duration-300 ease-out group-focus-within:opacity-100 group-hover:opacity-100" />
</SelectIcon>
</SelectTrigger>
<SelectContent>
{data?.map((item) => (
<SelectItem key={item} value={item}>
{item}
</SelectItem>
))}
</SelectContent>
</Select>
);
};
1 change: 1 addition & 0 deletions apps/dashboard/src/components/side-navigation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './side-navigation';
Loading

0 comments on commit e900c02

Please sign in to comment.