mirror of https://github.com/buster-so/buster.git
breadcrumbs update
This commit is contained in:
parent
f5263c7a76
commit
dd1eb5d0f0
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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!" />
|
||||
|
|
|
@ -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) ? (
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -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'}>
|
||||
|
|
|
@ -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
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 />}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { MetricStylingAppSegments } from './config';
|
||||
import { MetricStylingAppSegment } from './MetricStylingAppSegment';
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue