breadcrumbs update

This commit is contained in:
Nate Kelley 2025-03-19 22:17:04 -06:00
parent f5263c7a76
commit dd1eb5d0f0
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
16 changed files with 120 additions and 112 deletions

View File

@ -4,7 +4,7 @@ import { BusterRoutes } from '@/routes';
import { Dropdown, DropdownItems } from '@/components/ui/dropdown';
import { Button } from '@/components/ui/buttons';
import React, { useMemo } from 'react';
import { DotsVertical, Trash } from '@/components/ui/icons';
import { Dots, DotsVertical, Trash } from '@/components/ui/icons';
export const DatasetIndividualThreeDotMenu: React.FC<{
datasetId?: string;
@ -31,8 +31,8 @@ export const DatasetIndividualThreeDotMenu: React.FC<{
}, [datasetId, onDeleteDataset]);
return (
<Dropdown items={items}>
<Button variant={'ghost'} prefix={<DotsVertical />} />
<Dropdown items={items} side={'bottom'}>
<Button variant={'ghost'} prefix={<Dots />} />
</Dropdown>
);
});

View File

@ -40,7 +40,7 @@ export const DatasetsIndividualHeader: React.FC<{}> = React.memo(({}) => {
return (
<>
<>
<div className="flex items-center space-x-3">
<div className="flex items-center space-x-3 overflow-hidden">
<DatasetBreadcrumb datasetName={datasetName} />
<DatasetsHeaderOptions
@ -51,7 +51,7 @@ export const DatasetsIndividualHeader: React.FC<{}> = React.memo(({}) => {
</div>
<div className="flex items-center">
<div className="flex items-center">
<div className="flex items-center space-x-2">
<DatasetIndividualThreeDotMenu datasetId={datasetId} />
<Separator orientation="vertical" className="h-4!" />

View File

@ -19,7 +19,7 @@ export const OverviewData: React.FC<{
});
return (
<div className="buster-chart h-full max-h-[70vh] w-full overflow-auto rounded border">
<div className="scrollbar-thin h-full max-h-[70vh] w-full overflow-auto rounded border">
{!isFetchedDatasetData ? (
<LoadingState />
) : !isEmpty(data) ? (

View File

@ -1,7 +1,7 @@
'use client';
import React, { useMemo, useRef } from 'react';
import { AppSegmented } from '@/components/ui/segmented';
import { AppSegmented, SegmentedItem } from '@/components/ui/segmented';
import { PermissionApps } from './config';
import { useMemoizedFn, useSet } from '@/hooks';
import { Separator } from '@/components/ui/seperator';
@ -16,61 +16,54 @@ export const PermissionAppSegments: React.FC<{
datasetId: string;
selectedApp: PermissionApps;
}> = React.memo(({ datasetId, selectedApp }) => {
const [prefetchedRoutes, setPrefetchedRoutes] = useSet<string>();
const ref = useRef<HTMLDivElement>(null);
useDatasetListDatasetGroups(prefetchedRoutes.has(PermissionApps.DATASET_GROUPS) ? datasetId : '');
useDatasetListPermissionUsers(prefetchedRoutes.has(PermissionApps.USERS) ? datasetId : '');
useDatasetListPermissionGroups(
prefetchedRoutes.has(PermissionApps.PERMISSION_GROUPS) ? datasetId : ''
);
useDatasetListDatasetGroups(datasetId);
useDatasetListPermissionUsers(datasetId);
useDatasetListPermissionGroups(datasetId);
const onHoverRoute = useMemoizedFn((route: string) => {
setPrefetchedRoutes.add(route);
});
const options = useMemo(
() => [
{
label: 'Overview',
value: PermissionApps.OVERVIEW,
link: createBusterRoute({
route: BusterRoutes.APP_DATASETS_ID_PERMISSIONS_OVERVIEW,
datasetId
})
},
{
label: 'Permission Groups',
value: PermissionApps.PERMISSION_GROUPS,
link: createBusterRoute({
route: BusterRoutes.APP_DATASETS_ID_PERMISSIONS_PERMISSION_GROUPS,
datasetId
})
},
{
label: 'Dataset Groups',
value: PermissionApps.DATASET_GROUPS,
link: createBusterRoute({
route: BusterRoutes.APP_DATASETS_ID_PERMISSIONS_DATASET_GROUPS,
datasetId
})
},
{
label: 'Users',
value: PermissionApps.USERS,
link: createBusterRoute({
route: BusterRoutes.APP_DATASETS_ID_PERMISSIONS_USERS,
datasetId
})
}
],
const options: SegmentedItem<PermissionApps>[] = useMemo(
() =>
[
{
label: 'Overview',
value: PermissionApps.OVERVIEW,
link: createBusterRoute({
route: BusterRoutes.APP_DATASETS_ID_PERMISSIONS_OVERVIEW,
datasetId
})
},
{
label: 'Permission Groups',
value: PermissionApps.PERMISSION_GROUPS,
link: createBusterRoute({
route: BusterRoutes.APP_DATASETS_ID_PERMISSIONS_PERMISSION_GROUPS,
datasetId
})
},
{
label: 'Dataset Groups',
value: PermissionApps.DATASET_GROUPS,
link: createBusterRoute({
route: BusterRoutes.APP_DATASETS_ID_PERMISSIONS_DATASET_GROUPS,
datasetId
})
},
{
label: 'Users',
value: PermissionApps.USERS,
link: createBusterRoute({
route: BusterRoutes.APP_DATASETS_ID_PERMISSIONS_USERS,
datasetId
})
}
] as SegmentedItem<PermissionApps>[],
[datasetId]
);
return (
<div ref={ref} className="flex flex-col justify-center space-y-2 space-x-0">
<div ref={ref} className="border-b pb-3">
<AppSegmented options={options} value={selectedApp} />
<Separator />
</div>
);
});

View File

@ -1,13 +1,11 @@
'use client';
import React from 'react';
import { Title, Text } from '@/components/ui/typography';
export const PermissionTitleCard: React.FC<{}> = React.memo(({}) => {
return (
<div className="flex flex-col gap-1.5">
<div className="flex flex-col space-y-1">
<Title as="h3">Dataset Permissions</Title>
<Text size={'lg'} variant="secondary">
<Text size={'md'} variant="secondary">
Manage who can build dashboards & metrics using this dataset
</Text>
</div>

View File

@ -29,7 +29,7 @@ export const IndividualSharePerson: React.FC<{
</Text>
{isSameEmailName ? null : (
<Text truncate size="sm" variant="tertiary">
<Text truncate size="xs" variant="tertiary">
{email}
</Text>
)}

View File

@ -15,7 +15,7 @@ export const AvatarUserButton = React.forwardRef<
<div
ref={ref}
className="hover:bg-item-hover active:bg-item-active flex w-full cursor-pointer items-center gap-x-2 rounded-md p-2">
<Avatar size={32} image={avatarUrl} name={username} />
<Avatar size={32} fallbackClassName="text-base" image={avatarUrl} name={username} />
<div className="flex flex-col gap-y-0.5">
<Text className="flex-grow">{username}</Text>
<Text size={'sm'} variant={'secondary'}>

View File

@ -23,16 +23,17 @@ export interface BreadcrumbItem {
interface BreadcrumbProps {
items: BreadcrumbItem[];
className?: string;
activeIndex?: number; //default will be the last item
}
export const Breadcrumb = React.memo(
React.forwardRef<HTMLElement, BreadcrumbProps>(({ items, activeIndex }, ref) => {
React.forwardRef<HTMLElement, BreadcrumbProps>(({ items, activeIndex, className }, ref) => {
const chosenIndex = activeIndex ?? items.length - 1;
const lastItemIndex = items.length - 1;
return (
<BreadcrumbBase>
<BreadcrumbBase className={className}>
<BreadcrumbList>
{items.map((item, index) => (
<BreadcrumbItemSelector
@ -64,9 +65,11 @@ const BreadcrumbItemSelector: React.FC<{
return (
<BreadcrumbLink asChild>
{item.route ? (
<Link href={createBusterRoute(item.route)}>{item.label}</Link>
<Link href={createBusterRoute(item.route)} className="truncate">
{item.label}
</Link>
) : (
<span>{item.label}</span>
<span className="truncate">{item.label}</span>
)}
</BreadcrumbLink>
);
@ -74,7 +77,7 @@ const BreadcrumbItemSelector: React.FC<{
return (
<>
<BreadcrumbItem>{ChosenComponent}</BreadcrumbItem>
<BreadcrumbItem className={isLast ? 'truncate' : ''}>{ChosenComponent}</BreadcrumbItem>
{!isLast && <BreadcrumbSeparator />}
</>
);
@ -87,7 +90,11 @@ const BreadcrumbDropdown: React.FC<{
return items.map((item) => {
const route = createBusterRoute(item.route);
return {
label: <Link href={route}>{item.label}</Link>,
label: (
<Link href={route} className="truncate">
{item.label}
</Link>
),
value: route
};
});

View File

@ -10,7 +10,12 @@ const Breadcrumb = React.forwardRef<
separator?: React.ReactNode;
}
>(({ ...props }, ref) => (
<nav ref={ref} aria-label="breadcrumb" {...props} className={cn('flex', props.className)} />
<nav
ref={ref}
aria-label="breadcrumb"
{...props}
className={cn('flex flex-nowrap overflow-hidden', props.className)}
/>
));
Breadcrumb.displayName = 'Breadcrumb';
@ -19,7 +24,7 @@ const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWi
<ol
ref={ref}
className={cn(
'text-gray-dark flex flex-wrap items-center gap-1.5 text-base break-words sm:gap-2.5',
'text-gray-dark flex flex-nowrap items-center gap-1.5 overflow-hidden text-base break-words sm:gap-2.5',
className
)}
{...props}
@ -58,7 +63,7 @@ const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWit
role="link"
aria-disabled="true"
aria-current="page"
className={cn('text-foreground font-normal', className)}
className={cn('text-foreground truncate font-normal', className)}
{...props}
/>
)

View File

@ -8,6 +8,7 @@ import { useEffect, useState, useLayoutEffect, useTransition } from 'react';
import { cva } from 'class-variance-authority';
import { useMemoizedFn, useMergedRefs, useSize, useThrottleFn } from '@/hooks';
import { Tooltip } from '../tooltip/Tooltip';
import Link from 'next/link';
export interface SegmentedItem<T extends string | number = string> {
value: T;
@ -15,6 +16,7 @@ export interface SegmentedItem<T extends string | number = string> {
icon?: React.ReactNode;
disabled?: boolean;
tooltip?: string;
link?: string;
}
export interface AppSegmentedProps<T extends string | number = string> {
@ -28,11 +30,11 @@ export interface AppSegmentedProps<T extends string | number = string> {
disabled?: boolean;
}
const heightVariants = cva('h-[24px]', {
const heightVariants = cva('h-6', {
variants: {
size: {
default: 'h-[24px]',
medium: 'h-[28px]',
default: 'h-6',
medium: 'h-7',
large: 'h-[50px]'
}
}
@ -52,7 +54,7 @@ const segmentedVariants = cva('relative inline-flex items-center rounded', {
});
const triggerVariants = cva(
'relative z-10 flex items-center justify-center gap-x-1.5 gap-y-1 rounded transition-colors ',
'relative z-10 flex items-center h-6 justify-center gap-x-1.5 gap-y-1 rounded transition-colors ',
{
variants: {
size: {
@ -214,28 +216,32 @@ interface SegmentedTriggerProps<T extends string = string> {
function SegmentedTriggerComponent<T extends string = string>(props: SegmentedTriggerProps<T>) {
const { item, selectedValue, size, block, tabRefs } = props;
const { tooltip, label, icon, disabled, value, link } = item;
const LinkDiv = link ? Link : 'div';
return (
<Tooltip title={item.tooltip || ''} sideOffset={10} delayDuration={0.15}>
<Tooltip title={tooltip || ''} sideOffset={10} delayDuration={0.15}>
<Tabs.Trigger
key={item.value}
value={item.value}
disabled={item.disabled}
key={value}
value={value}
disabled={disabled}
asChild
ref={(el) => {
if (el) tabRefs.current.set(item.value, el);
if (el) tabRefs.current.set(value, el);
}}
className={cn(
triggerVariants({
size,
block,
disabled: item.disabled,
selected: selectedValue === item.value
disabled,
selected: selectedValue === value
})
)}>
<>
{item.icon && <span className={cn('flex items-center text-sm')}>{item.icon}</span>}
{item.label && <span className={cn('text-sm')}>{item.label}</span>}
</>
<LinkDiv href={link || ''}>
{icon && <span className={cn('flex items-center text-sm')}>{icon}</span>}
{label && <span className={cn('text-sm')}>{label}</span>}
</LinkDiv>
</Tabs.Trigger>
</Tooltip>
);

View File

@ -13,25 +13,6 @@
--rdg-row-hover-background-color: var(--color-item-hover);
--rdg-header-draggable-background-color: var(--color-item-hover);
--rdg-background-color: var(--color-background);
/* Webkit scrollbar styling */
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--color-border);
opacity: 0.5;
border-radius: 2px;
}
/* Firefox scrollbar styling */
scrollbar-width: thin;
scrollbar-color: color-mix(in srgb, var(--color-border) 50%, transparent) transparent;
}
.dataGrid :global(.rdg-header-row) {

View File

@ -334,7 +334,7 @@ export const AppDataGrid: React.FC<AppDataGridProps> = React.memo(
className
)}>
<DataGrid
className={styles.dataGrid}
className={cn(styles.dataGrid, 'scrollbar-thin')}
columns={reorderedColumns}
rows={sortedRows}
sortColumns={sortColumns}

View File

@ -7,7 +7,6 @@ import {
} from '@/layouts/ChatLayout/ChatLayoutContext';
import { MetricViewComponents } from './config';
import { FileIndeterminateLoader } from '@/components/features/FileIndeterminateLoader';
import { useMount } from '@/hooks';
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
export const MetricController: React.FC<{
@ -24,12 +23,6 @@ export const MetricController: React.FC<{
? MetricViewComponents[selectedFileView as MetricFileView]
: () => <></>;
console.log('here', metricId);
useMount(() => {
console.log('mounted', metricId);
});
return (
<>
{showLoader && <FileIndeterminateLoader />}

View File

@ -1,3 +1,5 @@
'use client';
import React, { useState } from 'react';
import { MetricStylingAppSegments } from './config';
import { MetricStylingAppSegment } from './MetricStylingAppSegment';

View File

@ -32,7 +32,9 @@ export const ChatHeaderTitle: React.FC<{
<EditableTitle
className="w-full"
id={CHAT_HEADER_TITLE_ID}
onChange={(value) => updateChat({ id: chatId, title: value })}>
onChange={(value) =>
value && value !== chatTitle && updateChat({ id: chatId, title: value })
}>
{chatTitle}
</EditableTitle>
</motion.div>

View File

@ -26,3 +26,24 @@
}
}
}
.scrollbar-thin {
/* Webkit scrollbar styling */
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--color-border);
opacity: 0.5;
border-radius: 2px;
}
/* Firefox scrollbar styling */
scrollbar-width: thin;
scrollbar-color: color-mix(in srgb, var(--color-border) 50%, transparent) transparent;
}