fix scroll area border

This commit is contained in:
Nate Kelley 2025-03-24 11:25:34 -06:00
parent d4962aa406
commit ed93eb1c19
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
11 changed files with 166 additions and 61 deletions

View File

@ -1,3 +1,2 @@
export * from './interfaces';
export * from './requests';
export * from './queryRequests';

View File

@ -1,27 +0,0 @@
import type { BusterChartConfigProps } from '@/api/asset_interfaces/metric';
import type { VerificationStatus } from '@/api/asset_interfaces/share';
export interface GetMetricParams {
id: string;
password?: string;
version_number?: number; //api will default to latest if not provided
}
/**
* Request payload for updating metric properties
*/
export type UpdateMetricParams = {
/** The unique identifier of the metric to update */
id: string;
/** New title for the metric */
name?: string;
/** SQL query associated with the metric */
sql?: string;
chart_config?: BusterChartConfigProps;
/** Flag to save the current draft state */
save_draft?: boolean;
/** Admin only: verification status update */
status?: VerificationStatus;
/** file in yaml format to update */
file?: string;
};

View File

@ -14,7 +14,6 @@ import {
unshareMetric,
updateMetricShare
} from './requests';
import type { GetMetricParams } from './interfaces';
import { prepareMetricUpdateMetric, upgradeMetricToIMetric } from '@/lib/metrics';
import { metricsQueryKeys } from '@/api/query_keys/metric';
import { collectionQueryKeys } from '@/api/query_keys/collection';
@ -60,7 +59,10 @@ export const useGetMetric = <TData = IBusterMetric>(
});
};
export const prefetchGetMetric = async (params: GetMetricParams, queryClientProp?: QueryClient) => {
export const prefetchGetMetric = async (
params: Parameters<typeof getMetric_server>[0],
queryClientProp?: QueryClient
) => {
const queryClient = queryClientProp || new QueryClient();
await queryClient.prefetchQuery({
...metricsQueryKeys.metricsGetMetric(params.id),

View File

@ -1,7 +1,7 @@
import { mainApi } from '../instances';
import { serverFetch } from '@/api/createServerInstance';
import type { GetMetricParams, UpdateMetricParams } from './interfaces';
import type {
BusterChartConfigProps,
BusterMetric,
BusterMetricData,
BusterMetricListItem
@ -13,7 +13,15 @@ import type {
} from '@/api/asset_interfaces/shared_interfaces';
import { VerificationStatus } from '@/api/asset_interfaces/share';
export const getMetric = async ({ id, password, version_number }: GetMetricParams) => {
export const getMetric = async ({
id,
password,
version_number
}: {
id: string;
password?: string;
version_number?: number; //api will default to latest if not provided
}) => {
return mainApi
.get<BusterMetric>(`/metrics/${id}`, {
params: { password, version_number }
@ -21,7 +29,7 @@ export const getMetric = async ({ id, password, version_number }: GetMetricParam
.then((res) => res.data);
};
export const getMetric_server = async ({ id, password }: GetMetricParams) => {
export const getMetric_server = async ({ id, password }: Parameters<typeof getMetric>[0]) => {
return await serverFetch<BusterMetric>(`/metrics/${id}`, {
params: { ...(password && { password }) }
});
@ -54,7 +62,21 @@ export const listMetrics_server = async (params: Parameters<typeof listMetrics>[
return await serverFetch<BusterMetricListItem[]>('/metrics', { params });
};
export const updateMetric = async (params: UpdateMetricParams) => {
export const updateMetric = async (params: {
/** The unique identifier of the metric to update */
id: string;
/** New title for the metric */
name?: string;
/** SQL query associated with the metric */
sql?: string;
chart_config?: BusterChartConfigProps;
/** Flag to save the current draft state */
save_draft?: boolean;
/** Admin only: verification status update */
status?: VerificationStatus;
/** file in yaml format to update */
file?: string;
}) => {
return mainApi.put<BusterMetric>(`/metrics/${params.id}`, params).then((res) => res.data);
};

View File

@ -11,29 +11,17 @@ import { cn } from '@/lib/classMerge';
type DropdownValue = ShareRole | 'remove' | 'notShared';
export const AccessDropdown: React.FC<{
groupShare?: boolean;
className?: string;
showRemove?: boolean;
shareLevel?: ShareRole | null;
onChangeShareLevel?: (level: ShareRole | null) => void;
}> = ({
shareLevel,
showRemove = true,
groupShare = false,
className = '',
onChangeShareLevel
}) => {
}> = ({ shareLevel, showRemove = true, className = '', onChangeShareLevel }) => {
const disabled = useMemo(() => canEdit(shareLevel), [shareLevel]);
const items = useMemo(() => {
const baseItems: DropdownItem<DropdownValue>[] = standardItems;
const baseItems: DropdownItem<DropdownValue>[] = [...standardItems];
if (groupShare) {
baseItems.push({
label: <DropdownLabel title="Not shared" subtitle="Does not have access." />,
value: 'notShared'
});
} else if (showRemove) {
if (showRemove) {
baseItems.push({
label: 'Remove',
value: 'remove'
@ -44,7 +32,7 @@ export const AccessDropdown: React.FC<{
...item,
selected: item.value === shareLevel
}));
}, [groupShare, showRemove, shareLevel]);
}, [showRemove, shareLevel]);
const selectedLabel = useMemo(() => {
const selectedItem = items.find((item) => item.selected) || OWNER_ITEM;

View File

@ -174,7 +174,6 @@ const ShareMenuContentShare: React.FC<ShareMenuContentBodyProps> = React.memo(
{inputValue && (
<AccessDropdown
showRemove={false}
groupShare={false}
className="absolute top-[50%] right-[10px] -translate-y-1/2"
shareLevel={defaultPermissionLevel}
onChangeShareLevel={onChangeAccessDropdown}

View File

@ -45,7 +45,9 @@ export const AppPageLayout: React.FC<
</AppPageLayoutHeader>
)}
<AppPageLayoutContent className="scroll-shadow-container" scrollable={scrollable}>
<AppPageLayoutContent
className={cn(headerBorderVariant === 'ghost' && 'scroll-shadow-container')}
scrollable={scrollable}>
{header && scrollable && headerBorderVariant === 'ghost' && (
<div className="scroll-header"></div>
)}

View File

@ -3,7 +3,7 @@ import React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
const headerVariants = cva(
'bg-page-background flex max-h-[38px] min-h-[38px] items-center justify-between gap-x-2.5 relative',
'bg-page-background flex max-h-[38px] min-h-[38px] items-center z-10 justify-between gap-x-2.5 relative',
{
variants: {
sizeVariant: {

View File

@ -0,0 +1,119 @@
import type { Meta, StoryObj } from '@storybook/react';
import { ScrollArea } from './ScrollArea';
const meta = {
title: 'UI/ScrollArea/ScrollArea',
component: ScrollArea,
parameters: {
layout: 'centered'
},
tags: ['autodocs']
} satisfies Meta<typeof ScrollArea>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => (
<ScrollArea className="h-[200px] w-[350px] rounded-md border p-4">
<div>
<p className="mb-4">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam auctor, nisl eget
ultricies tincidunt, nisl nisl aliquam nisl, eget ultricies nisl nisl eget nisl.
</p>
<p className="mb-4">
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
architecto beatae vitae dicta sunt explicabo.
</p>
<p className="mb-4">
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia
consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
</p>
<p className="mb-4">
Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci
velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam
quaerat voluptatem.
</p>
<p>
Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam,
nisi ut aliquid ex ea commodi consequatur.
</p>
</div>
</ScrollArea>
)
};
export const TallContent: Story = {
render: () => (
<ScrollArea className="h-[200px] w-[350px] rounded-md border p-4">
<div>
{Array.from({ length: 50 }).map((_, i) => (
<div key={i} className="mb-2">
Row {i + 1}
</div>
))}
</div>
</ScrollArea>
)
};
export const WideContent: Story = {
render: () => (
<ScrollArea className="h-[200px] w-[350px] rounded-md border p-4">
<div className="w-[500px]">
<p className="mb-4">
This content is intentionally wider than the container to demonstrate horizontal
scrolling. The ScrollArea component will automatically add scrollbars as needed.
</p>
<div className="flex space-x-4">
{Array.from({ length: 10 }).map((_, i) => (
<div
key={i}
className="flex h-20 w-20 flex-shrink-0 items-center justify-center rounded-md bg-gray-200">
{i + 1}
</div>
))}
</div>
</div>
</ScrollArea>
)
};
export const CustomHeight: Story = {
render: () => (
<ScrollArea className="h-[400px] w-[350px] rounded-md border p-4">
<div>
<p className="mb-4 text-lg font-semibold">Taller Scroll Area</p>
{Array.from({ length: 20 }).map((_, i) => (
<p key={i} className="mb-4">
Paragraph {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>
))}
</div>
</ScrollArea>
)
};
export const NestedContent: Story = {
render: () => (
<ScrollArea className="h-[300px] w-[400px] rounded-md border p-4">
<div>
<h3 className="mb-4 text-lg font-semibold">Nested Content Example</h3>
<div className="mb-4">
<p className="mb-2">Main content area with some text.</p>
<div className="rounded-md border p-2">
<h4 className="mb-2 font-medium">Nested Section</h4>
<p>This is nested content inside the scroll area.</p>
</div>
</div>
{Array.from({ length: 10 }).map((_, i) => (
<div key={i} className="mb-4">
<h4 className="mb-1 font-medium">Section {i + 1}</h4>
<p>Additional content for demonstrating scrolling behavior.</p>
</div>
))}
</div>
</ScrollArea>
)
};

View File

@ -16,7 +16,7 @@ import type {
PieChartAxis,
ScatterAxis
} from '@/api/asset_interfaces/metric/charts';
import { UpdateMetricParams } from '@/api/buster_rest/metrics';
import { type updateMetric } from '@/api/buster_rest/metrics';
const DEFAULT_COLUMN_SETTINGS_ENTRIES = Object.entries(DEFAULT_COLUMN_SETTINGS);
const DEFAULT_COLUMN_LABEL_FORMATS_ENTRIES = Object.entries(DEFAULT_COLUMN_LABEL_FORMAT);
@ -120,11 +120,11 @@ const getChangesFromDefaultChartConfig = (newMetric: IBusterMetric) => {
export const prepareMetricUpdateMetric = (
newMetric: IBusterMetric,
oldMetric: IBusterMetric
): UpdateMetricParams | null => {
): Parameters<typeof updateMetric>[0] | null => {
const changedTopLevelValues = getChangedTopLevelMessageValues(
newMetric,
oldMetric
) as unknown as UpdateMetricParams;
) as unknown as Parameters<typeof updateMetric>[0];
const changedChartConfig = getChangesFromDefaultChartConfig(newMetric);

View File

@ -7,22 +7,23 @@
}
.scroll-shadow-container {
@apply relative;
@apply relative overflow-y-auto;
scroll-timeline-name: --scrollShadowContainer;
.scroll-header {
@apply fixed top-[30px] right-0 left-0 h-2 w-full;
animation: shadowAnimation linear;
animation-range: 0px 125px;
animation-timeline: scroll(block start);
animation-range: 0px 100px;
animation-timeline: --scrollShadowContainer;
animation-fill-mode: forwards;
}
@keyframes shadowAnimation {
from {
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
box-shadow: 0px 0px 0px 0px var(--color-border) !important;
}
to {
box-shadow: 0px 1px 8px 0px #00000029;
box-shadow: 0px 0px 0px 0.5px var(--color-border) !important;
}
}
}