mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
Fix minor UI issues (#1382)
This commit is contained in:
parent
59422a1aba
commit
562dd7ea28
24 changed files with 617 additions and 307 deletions
5
.changeset/fix-minor-ui-issues.md
Normal file
5
.changeset/fix-minor-ui-issues.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@hyperdx/app': patch
|
||||
---
|
||||
|
||||
Fix minor UI issues and enhance styling across various components
|
||||
|
|
@ -16,16 +16,40 @@ import '../styles/app.scss';
|
|||
import { meHandler } from '../src/mocks/handlers';
|
||||
import { ThemeWrapper } from '../src/ThemeWrapper';
|
||||
|
||||
export const parameters = {
|
||||
layout: 'fullscreen',
|
||||
options: {
|
||||
showPanel: false,
|
||||
storySort: (a, b) =>
|
||||
a.title.localeCompare(b.title, undefined, { numeric: true }),
|
||||
},
|
||||
};
|
||||
|
||||
export const globalTypes = {
|
||||
theme: {
|
||||
name: 'Theme',
|
||||
description: 'Mantine color scheme',
|
||||
defaultValue: 'light',
|
||||
toolbar: {
|
||||
icon: 'mirror',
|
||||
items: [
|
||||
{ value: 'light', title: 'Light' },
|
||||
{ value: 'dark', title: 'Dark' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
initialize();
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const preview: Preview = {
|
||||
decorators: [
|
||||
Story => (
|
||||
(Story, context) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<QueryParamProvider adapter={NextAdapter}>
|
||||
<ThemeWrapper>
|
||||
<ThemeWrapper colorScheme={context.globals.theme || 'light'}>
|
||||
<Story />
|
||||
</ThemeWrapper>
|
||||
</QueryParamProvider>
|
||||
|
|
@ -37,6 +61,7 @@ const preview: Preview = {
|
|||
msw: {
|
||||
handlers: [meHandler],
|
||||
},
|
||||
backgrounds: { disable: true },
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@
|
|||
"@storybook/addon-interactions": "^8.1.5",
|
||||
"@storybook/addon-links": "^8.1.5",
|
||||
"@storybook/addon-styling-webpack": "^1.0.0",
|
||||
"@storybook/addon-themes": "^10.0.8",
|
||||
"@storybook/blocks": "^8.1.5",
|
||||
"@storybook/nextjs": "^8.1.5",
|
||||
"@storybook/react": "^8.1.5",
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ export const AppNavLink = ({
|
|||
const testId = `nav-link-${href.replace(/^\//, '').replace(/\//g, '-') || 'home'}`;
|
||||
|
||||
return (
|
||||
<Group justify="space-between" px="md" py="6px">
|
||||
<Group justify="space-between" px="md" py="6px" h="34px">
|
||||
<Link
|
||||
data-testid={testId}
|
||||
href={href}
|
||||
|
|
|
|||
|
|
@ -409,6 +409,7 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) {
|
|||
const isCollapsed = isSmallScreen || isPreferCollapsed;
|
||||
|
||||
const navWidth = isCollapsed ? 50 : 230;
|
||||
const navHeaderStyle = isCollapsed ? undefined : { height: 58 };
|
||||
|
||||
useEffect(() => {
|
||||
HyperDX.addAction('user navigated', {
|
||||
|
|
@ -604,7 +605,10 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) {
|
|||
}}
|
||||
>
|
||||
<div style={{ width: navWidth }}>
|
||||
<div className="p-3 d-flex flex-wrap justify-content-between align-items-center">
|
||||
<div
|
||||
className="p-3 d-flex flex-wrap justify-content-between align-items-center"
|
||||
style={navHeaderStyle}
|
||||
>
|
||||
<Link href="/search" className="text-decoration-none">
|
||||
{isCollapsed ? (
|
||||
<div style={{ marginLeft: '-0.15rem' }}>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export default function LandingHeader({
|
|||
right: 0,
|
||||
background: 'var(--color-bg-body)',
|
||||
backdropFilter: 'blur(12px)',
|
||||
border: '1px solid var(--color-border)',
|
||||
borderBottom: '1px solid var(--color-border)',
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
|
|
@ -47,12 +47,13 @@ export default function LandingHeader({
|
|||
color="white"
|
||||
/>
|
||||
|
||||
<Group gap="md" visibleFrom="lg" style={{ fontSize: 14 }}>
|
||||
<Group gap="lg" visibleFrom="lg">
|
||||
<Anchor
|
||||
href="https://hyperdx.io"
|
||||
c={activeKey === 'cloud' ? 'green' : 'gray'}
|
||||
underline="never"
|
||||
style={{ fontWeight: activeKey === 'cloud' ? 600 : 400 }}
|
||||
size="sm"
|
||||
>
|
||||
HyperDX Cloud
|
||||
</Anchor>
|
||||
|
|
@ -61,6 +62,7 @@ export default function LandingHeader({
|
|||
c={activeKey === 'docs' ? 'green' : 'gray'}
|
||||
underline="never"
|
||||
style={{ fontWeight: activeKey === 'docs' ? 600 : 400 }}
|
||||
size="sm"
|
||||
>
|
||||
Docs
|
||||
</Anchor>
|
||||
|
|
@ -70,6 +72,7 @@ export default function LandingHeader({
|
|||
c={activeKey === '/login' ? 'green' : 'gray'}
|
||||
underline="never"
|
||||
style={{ fontWeight: activeKey === '/login' ? 600 : 400 }}
|
||||
size="sm"
|
||||
>
|
||||
Login
|
||||
</Anchor>
|
||||
|
|
|
|||
|
|
@ -1,263 +1,8 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
MantineProvider,
|
||||
MantineTheme,
|
||||
MantineThemeOverride,
|
||||
rem,
|
||||
Select,
|
||||
Text,
|
||||
} from '@mantine/core';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import { Notifications } from '@mantine/notifications';
|
||||
|
||||
const makeTheme = ({
|
||||
fontFamily = '"IBM Plex Sans", monospace',
|
||||
}: {
|
||||
fontFamily?: string;
|
||||
}): MantineThemeOverride => ({
|
||||
cursorType: 'pointer',
|
||||
fontFamily,
|
||||
primaryColor: 'green',
|
||||
primaryShade: 8,
|
||||
autoContrast: true,
|
||||
white: '#fff',
|
||||
fontSizes: {
|
||||
xxs: '11px',
|
||||
xs: '12px',
|
||||
sm: '13px',
|
||||
md: '15px',
|
||||
lg: '16px',
|
||||
xl: '18px',
|
||||
},
|
||||
spacing: {
|
||||
xxxs: 'calc(0.375rem * var(--mantine-scale))',
|
||||
xxs: 'calc(0.5rem * var(--mantine-scale))',
|
||||
xs: 'calc(0.625rem * var(--mantine-scale))',
|
||||
sm: 'calc(0.75rem * var(--mantine-scale))',
|
||||
md: 'calc(1rem * var(--mantine-scale))',
|
||||
lg: 'calc(1.25rem * var(--mantine-scale))',
|
||||
xl: 'calc(2rem * var(--mantine-scale))',
|
||||
},
|
||||
colors: {
|
||||
// https://uicolors.app/generate/00c28a
|
||||
green: [
|
||||
'#eafff6',
|
||||
'#cdfee7',
|
||||
'#a0fad5',
|
||||
'#63f2bf',
|
||||
'#25e2a5',
|
||||
'#00c28a',
|
||||
'#00a475',
|
||||
'#008362',
|
||||
'#00674e',
|
||||
'#005542',
|
||||
],
|
||||
// https://mantine.dev/colors-generator/?color=A1A1AA
|
||||
// Customized with FAFAFA, D7D8DB, A1A1AA
|
||||
gray: [
|
||||
'#FAFAFA', // Off White
|
||||
'#e6e6ee',
|
||||
'#D7D8DB', // Light Gray
|
||||
'#aeaeb7',
|
||||
'#A1A1AA', // Primary Gray
|
||||
'#868691',
|
||||
'#7e7e8b',
|
||||
'#6c6c79',
|
||||
'#5f5f6e',
|
||||
'#515264',
|
||||
],
|
||||
dark: [
|
||||
'#C1C2C5',
|
||||
'#A6A7AB',
|
||||
'#909296',
|
||||
'#5C5F66',
|
||||
'#373A40',
|
||||
'#2C2E33',
|
||||
'#25262B',
|
||||
'#1A1B1E',
|
||||
'#141517',
|
||||
'#101113',
|
||||
],
|
||||
},
|
||||
headings: {
|
||||
fontFamily,
|
||||
},
|
||||
components: {
|
||||
Modal: {
|
||||
styles: {
|
||||
header: {
|
||||
fontFamily,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
},
|
||||
InputWrapper: {
|
||||
styles: {
|
||||
label: {
|
||||
marginBottom: 4,
|
||||
},
|
||||
description: {
|
||||
marginBottom: 8,
|
||||
lineHeight: 1.3,
|
||||
},
|
||||
},
|
||||
},
|
||||
Select: Select.extend({
|
||||
styles: {
|
||||
input: {
|
||||
border: '1px solid var(--color-border)',
|
||||
},
|
||||
},
|
||||
}),
|
||||
Input: {
|
||||
styles: {
|
||||
input: {
|
||||
backgroundColor: 'var(--color-bg-field)',
|
||||
border: '1px solid var(--color-border)',
|
||||
},
|
||||
},
|
||||
},
|
||||
Card: {
|
||||
styles: (_theme: MantineTheme, props: { variant?: string }) => {
|
||||
if (props.variant === 'muted') {
|
||||
return {
|
||||
root: {
|
||||
backgroundColor: 'var(--color-bg-muted)',
|
||||
border: '1px solid var(--color-border)',
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
root: {
|
||||
backgroundColor: 'var(--color-bg-body)',
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
Divider: {
|
||||
styles: {
|
||||
root: {
|
||||
borderColor: 'var(--color-border)',
|
||||
borderTopColor: 'var(--color-border)',
|
||||
'--divider-color': 'var(--color-border)',
|
||||
'--item-border-color': 'var(--color-border)',
|
||||
},
|
||||
},
|
||||
},
|
||||
Accordion: {
|
||||
styles: {
|
||||
control: {
|
||||
'--item-border-color': 'var(--color-border)',
|
||||
},
|
||||
item: {
|
||||
borderColor: 'var(--color-border)',
|
||||
},
|
||||
},
|
||||
},
|
||||
UnstyledButton: {
|
||||
styles: {
|
||||
root: {
|
||||
'--item-border-color': 'var(--color-border)',
|
||||
},
|
||||
},
|
||||
},
|
||||
Paper: {
|
||||
classNames: (_theme: MantineTheme, props: { variant?: string }) => {
|
||||
if (props.variant === 'muted') {
|
||||
return {
|
||||
root: 'paper-muted',
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
styles: (_theme: MantineTheme, props: { variant?: string }) => {
|
||||
if (props.variant === 'muted') {
|
||||
return {
|
||||
root: {
|
||||
backgroundColor: 'var(--color-bg-muted)',
|
||||
border: '1px solid var(--color-border)',
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
root: {
|
||||
border: '1px solid var(--color-border)',
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
Text: Text.extend({
|
||||
styles: (theme, props) => {
|
||||
if (props.variant === 'danger') {
|
||||
return {
|
||||
root: {
|
||||
color: 'var(--color-text-danger)',
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
}),
|
||||
Button: Button.extend({
|
||||
vars: (theme, props) => {
|
||||
if (props.size === 'xxs') {
|
||||
return {
|
||||
root: {
|
||||
'--button-height': rem(22),
|
||||
'--button-padding-x': rem(4),
|
||||
'--button-fz': rem(12),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { root: {} };
|
||||
},
|
||||
}),
|
||||
ActionIcon: ActionIcon.extend({
|
||||
defaultProps: {
|
||||
variant: 'subtle',
|
||||
color: 'gray',
|
||||
},
|
||||
styles: (theme, props) => {
|
||||
// Subtle variant stays transparent
|
||||
if (props.variant === 'subtle') {
|
||||
return {
|
||||
root: {
|
||||
backgroundColor: 'transparent',
|
||||
color: 'var(--color-text)',
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--color-bg-hover)',
|
||||
},
|
||||
'&:active': {
|
||||
backgroundColor: 'var(--color-bg-muted)',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Default variant
|
||||
if (props.variant === 'default') {
|
||||
return {
|
||||
root: {
|
||||
backgroundColor: 'var(--color-bg-hover)',
|
||||
color: 'var(--color-text)',
|
||||
border: 'none',
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--color-bg-muted)',
|
||||
},
|
||||
'&:active': {
|
||||
backgroundColor: 'var(--color-bg-muted)',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
import { makeTheme, theme as defaultTheme } from './theme/mantineTheme';
|
||||
|
||||
export const ThemeWrapper = ({
|
||||
fontFamily,
|
||||
|
|
@ -268,8 +13,10 @@ export const ThemeWrapper = ({
|
|||
colorScheme?: 'dark' | 'light';
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const theme = React.useMemo(() => makeTheme({ fontFamily }), [fontFamily]);
|
||||
|
||||
const theme = React.useMemo(
|
||||
() => (fontFamily ? makeTheme({ fontFamily }) : defaultTheme),
|
||||
[fontFamily],
|
||||
);
|
||||
return (
|
||||
<MantineProvider forceColorScheme={colorScheme} theme={theme}>
|
||||
<Notifications zIndex={999999} />
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ type TimelineEventT = {
|
|||
color: string;
|
||||
body: React.ReactNode;
|
||||
minWidthPerc?: number;
|
||||
isError?: boolean;
|
||||
};
|
||||
|
||||
const NewTimelineRow = memo(
|
||||
|
|
@ -35,7 +36,9 @@ const NewTimelineRow = memo(
|
|||
height: number;
|
||||
scale: number;
|
||||
offset: number;
|
||||
eventStyles?: any;
|
||||
eventStyles?:
|
||||
| React.CSSProperties
|
||||
| ((event: TimelineEventT) => React.CSSProperties);
|
||||
onEventHover?: Function;
|
||||
onEventClick?: (event: any) => any;
|
||||
}) {
|
||||
|
|
@ -84,7 +87,9 @@ const NewTimelineRow = memo(
|
|||
minWidth: `${percWidth.toFixed(6)}%`,
|
||||
width: `${percWidth.toFixed(6)}%`,
|
||||
marginLeft: `${percMarginLeft.toFixed(6)}%`,
|
||||
...eventStyles,
|
||||
...(typeof eventStyles === 'function'
|
||||
? eventStyles(e)
|
||||
: eventStyles),
|
||||
}}
|
||||
>
|
||||
<div style={{ margin: 'auto' }} className="px-2">
|
||||
|
|
@ -166,7 +171,7 @@ function TimelineXAxis({
|
|||
width: 1,
|
||||
marginRight: -1,
|
||||
marginLeft: i === 0 ? 0 : `${percSpacing.toFixed(6)}%`,
|
||||
background: 'var(--color-bg-surface)',
|
||||
background: 'var(--color-border-muted)',
|
||||
}}
|
||||
>
|
||||
<div className="ms-2 fs-8.5">{renderMs(i * interval)}</div>
|
||||
|
|
@ -179,10 +184,11 @@ function TimelineXAxis({
|
|||
style={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
height: 4,
|
||||
height: 24,
|
||||
paddingTop: 4,
|
||||
zIndex: 200,
|
||||
pointerEvents: 'none',
|
||||
background: 'var(--color-bg-body)',
|
||||
}}
|
||||
>
|
||||
<div className={`${cx('d-flex align-items-center')}`}>
|
||||
|
|
@ -242,8 +248,8 @@ function TimelineCursor({
|
|||
>
|
||||
<div>
|
||||
<span
|
||||
className="p-2 rounded"
|
||||
style={{ background: 'rgba(0,0,0,0.75)' }}
|
||||
className="p-2 rounded border"
|
||||
style={{ background: 'var(--color-bg-surface)' }}
|
||||
>
|
||||
{overlay}
|
||||
</span>
|
||||
|
|
@ -325,7 +331,7 @@ function TimelineMouseCursor({
|
|||
overlay={renderMs(Math.max(cursorTime, 0))}
|
||||
height={height}
|
||||
labelWidth={labelWidth}
|
||||
color="#ffffff88"
|
||||
color="var(--color-bg-neutral)"
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
|
@ -590,13 +596,14 @@ export default function TimelineChart({
|
|||
events={row.events}
|
||||
height={rowHeight}
|
||||
maxVal={maxVal}
|
||||
eventStyles={{
|
||||
eventStyles={(event: TimelineEventT) => ({
|
||||
borderRadius: 2,
|
||||
fontSize: rowHeight * 0.5,
|
||||
border: '1px solid var(--color-border)',
|
||||
backgroundColor: 'var(--color-bg-neutral)',
|
||||
color: 'var(--color-text)',
|
||||
}}
|
||||
backgroundColor: event.isError
|
||||
? 'var(--color-bg-danger)'
|
||||
: 'var(--color-bg-inverted)',
|
||||
color: 'var(--color-text-inverted)',
|
||||
})}
|
||||
scale={scale}
|
||||
offset={offset}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ export function RowOverviewPanel({
|
|||
return (
|
||||
<div className="flex-grow-1 overflow-auto" data-testid={dataTestId}>
|
||||
{!hideHeader && (
|
||||
<Box px="32px" pt="md">
|
||||
<Box px="sm" pt="md">
|
||||
<DBRowSidePanelHeader
|
||||
date={new Date(firstRow?.__hdx_timestamp ?? 0)}
|
||||
mainContent={mainContent}
|
||||
|
|
@ -182,6 +182,7 @@ export function RowOverviewPanel({
|
|||
'topLevelAttributes',
|
||||
]}
|
||||
multiple
|
||||
variant="noPadding"
|
||||
>
|
||||
{isHttpRequest && (
|
||||
<Accordion.Item value="network">
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ export default function DBTracePanel({
|
|||
<Button
|
||||
ms="sm"
|
||||
variant="outline"
|
||||
color="gray"
|
||||
onClick={() => setShowTraceIdInput(false)}
|
||||
size="xs"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -609,17 +609,19 @@ export function DBTraceWaterfallChartContainer({
|
|||
onClick?.({ id, type: type ?? '' });
|
||||
}}
|
||||
>
|
||||
<div className="d-flex">
|
||||
<div className="d-flex align-items-center" style={{ height: 24 }}>
|
||||
{Array.from({ length: result.level }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
borderLeft: '1px solid var(--color-border)',
|
||||
marginLeft: 5,
|
||||
marginLeft: 7,
|
||||
width: 8,
|
||||
minWidth: 8,
|
||||
maxWidth: 8,
|
||||
flexGrow: 1,
|
||||
flexShrink: 0,
|
||||
height: '100%',
|
||||
}}
|
||||
></div>
|
||||
))}
|
||||
|
|
@ -681,8 +683,9 @@ export function DBTraceWaterfallChartContainer({
|
|||
end,
|
||||
tooltip: `${displayText} ${tookMs >= 0 ? `took ${tookMs.toFixed(4)}ms` : ''} ${status ? `| Status: ${status}` : ''}`,
|
||||
color: barColor({ isError, isWarn, isHighlighted }),
|
||||
body: <span style={{ color: '#FFFFFFEE' }}>{displayText}</span>,
|
||||
body: <span>{displayText}</span>,
|
||||
minWidthPerc: 1,
|
||||
isError,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
80
packages/app/src/components/ExceptionSubpanel.stories.tsx
Normal file
80
packages/app/src/components/ExceptionSubpanel.stories.tsx
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import React from 'react';
|
||||
|
||||
import { ExceptionSubpanel } from './ExceptionSubpanel';
|
||||
|
||||
export default {
|
||||
title: 'Components/ExceptionSubpanel',
|
||||
component: ExceptionSubpanel,
|
||||
};
|
||||
|
||||
const mockExceptionValues = [
|
||||
{
|
||||
type: 'TypeError',
|
||||
value: 'Cannot read property "foo" of undefined',
|
||||
mechanism: {
|
||||
type: 'generic',
|
||||
handled: false,
|
||||
data: {
|
||||
function: 'myFunction',
|
||||
handler: 'errorHandler',
|
||||
target: 'window',
|
||||
},
|
||||
},
|
||||
stacktrace: {
|
||||
frames: [
|
||||
{
|
||||
filename: 'App.js',
|
||||
function: 'myFunction',
|
||||
lineno: 42,
|
||||
colno: 13,
|
||||
in_app: true,
|
||||
context_line: 'const foo = bar.foo;',
|
||||
pre_context: ['function myFunction() {'],
|
||||
post_context: ['return foo;'],
|
||||
},
|
||||
{
|
||||
filename: 'index.js',
|
||||
function: 'main',
|
||||
lineno: 10,
|
||||
colno: 5,
|
||||
in_app: false,
|
||||
context_line: 'main();',
|
||||
pre_context: ['import App from "./App";'],
|
||||
post_context: ['console.log("done");'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mockBreadcrumbs = [
|
||||
{
|
||||
category: 'ui.click',
|
||||
message: 'Button clicked',
|
||||
timestamp: 1700000000,
|
||||
},
|
||||
{
|
||||
category: 'fetch',
|
||||
message: 'GET /api/data',
|
||||
data: { method: 'GET', url: '/api/data', status_code: 200 },
|
||||
timestamp: 1700000001,
|
||||
},
|
||||
{
|
||||
category: 'console',
|
||||
message: 'Error: Something went wrong',
|
||||
level: 'error',
|
||||
timestamp: 1700000002,
|
||||
},
|
||||
];
|
||||
|
||||
const mockLogData = {
|
||||
timestamp: 1700000000000,
|
||||
};
|
||||
|
||||
export const Default = () => (
|
||||
<ExceptionSubpanel
|
||||
logData={mockLogData}
|
||||
breadcrumbs={mockBreadcrumbs}
|
||||
exceptionValues={mockExceptionValues}
|
||||
/>
|
||||
);
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
height: 100%;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
padding: 0 4px;
|
||||
padding: var(--mantine-spacing-xs);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
|
|
|||
24
packages/app/src/components/SQLEditor.stories.tsx
Normal file
24
packages/app/src/components/SQLEditor.stories.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import React, { useState } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
import SQLEditor from './SQLEditor';
|
||||
|
||||
const story = {
|
||||
title: 'Components/SQLEditor',
|
||||
component: SQLEditor,
|
||||
};
|
||||
export default story;
|
||||
|
||||
export const Default = () => {
|
||||
const [value, setValue] = useState('SELECT * FROM users');
|
||||
const queryClient = new QueryClient();
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SQLEditor
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
placeholder="Type your SQL query..."
|
||||
/>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
25
packages/app/src/components/SQLInlineEditor.stories.tsx
Normal file
25
packages/app/src/components/SQLInlineEditor.stories.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import React, { useState } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
import SQLInlineEditor from './SQLInlineEditor';
|
||||
|
||||
export default {
|
||||
title: 'Components/SQLInlineEditor',
|
||||
component: SQLInlineEditor,
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const [value, setValue] = useState('SELECT * FROM table');
|
||||
const queryClient = new QueryClient();
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SQLInlineEditor
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
placeholder="Type your SQL query..."
|
||||
label="SQL Query"
|
||||
allowMultiline
|
||||
/>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
|
@ -30,12 +30,13 @@ $horizontalPadding: 12px;
|
|||
}
|
||||
|
||||
th {
|
||||
color: red;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
font-size: 9px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 1px;
|
||||
padding: $normalVerticalPadding $horizontalPadding;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td {
|
||||
|
|
|
|||
39
packages/app/src/theme/SemanticColors.stories.tsx
Normal file
39
packages/app/src/theme/SemanticColors.stories.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react';
|
||||
|
||||
import { semanticColorsGrouped } from './semanticColorsGrouped';
|
||||
|
||||
const story = {
|
||||
title: 'Design Tokens/Semantic Colors',
|
||||
};
|
||||
export default story;
|
||||
|
||||
export const AllSemanticColors = () => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 32 }}>
|
||||
{Object.entries(semanticColorsGrouped).map(([group, tokens]) => (
|
||||
<div key={group} style={{ marginBottom: 24 }}>
|
||||
<h3 style={{ fontSize: 16, marginBottom: 12 }}>
|
||||
{group.charAt(0).toUpperCase() + group.slice(1)}
|
||||
</h3>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 16 }}>
|
||||
{tokens.map(name => (
|
||||
<div key={name} style={{ width: 140 }}>
|
||||
<div
|
||||
style={{
|
||||
background: `var(--${name})`,
|
||||
height: 40,
|
||||
border: '1px solid var(--color-border)',
|
||||
marginBottom: 8,
|
||||
borderRadius: 4,
|
||||
}}
|
||||
/>
|
||||
<div style={{ fontSize: 12 }}>{name}</div>
|
||||
<div
|
||||
style={{ fontSize: 10, color: '#888' }}
|
||||
>{`var(--${name})`}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
280
packages/app/src/theme/mantineTheme.ts
Normal file
280
packages/app/src/theme/mantineTheme.ts
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
MantineTheme,
|
||||
MantineThemeOverride,
|
||||
rem,
|
||||
Select,
|
||||
Text,
|
||||
} from '@mantine/core';
|
||||
|
||||
export const makeTheme = ({
|
||||
fontFamily = '"IBM Plex Sans", monospace',
|
||||
}: {
|
||||
fontFamily?: string;
|
||||
}): MantineThemeOverride => ({
|
||||
cursorType: 'pointer',
|
||||
fontFamily,
|
||||
primaryColor: 'green',
|
||||
primaryShade: 8,
|
||||
autoContrast: true,
|
||||
white: '#fff',
|
||||
fontSizes: {
|
||||
xxs: '11px',
|
||||
xs: '12px',
|
||||
sm: '13px',
|
||||
md: '15px',
|
||||
lg: '16px',
|
||||
xl: '18px',
|
||||
},
|
||||
spacing: {
|
||||
xxxs: 'calc(0.375rem * var(--mantine-scale))',
|
||||
xxs: 'calc(0.5rem * var(--mantine-scale))',
|
||||
xs: 'calc(0.625rem * var(--mantine-scale))',
|
||||
sm: 'calc(0.75rem * var(--mantine-scale))',
|
||||
md: 'calc(1rem * var(--mantine-scale))',
|
||||
lg: 'calc(1.25rem * var(--mantine-scale))',
|
||||
xl: 'calc(2rem * var(--mantine-scale))',
|
||||
},
|
||||
colors: {
|
||||
green: [
|
||||
'#eafff6',
|
||||
'#cdfee7',
|
||||
'#a0fad5',
|
||||
'#63f2bf',
|
||||
'#25e2a5',
|
||||
'#00c28a',
|
||||
'#00a475',
|
||||
'#008362',
|
||||
'#00674e',
|
||||
'#005542',
|
||||
],
|
||||
gray: [
|
||||
'#FAFAFA',
|
||||
'#e6e6ee',
|
||||
'#D7D8DB',
|
||||
'#aeaeb7',
|
||||
'#A1A1AA',
|
||||
'#868691',
|
||||
'#7e7e8b',
|
||||
'#6c6c79',
|
||||
'#5f5f6e',
|
||||
'#515264',
|
||||
],
|
||||
dark: [
|
||||
'#C1C2C5',
|
||||
'#A6A7AB',
|
||||
'#909296',
|
||||
'#5C5F66',
|
||||
'#373A40',
|
||||
'#2C2E33',
|
||||
'#25262B',
|
||||
'#1A1B1E',
|
||||
'#141517',
|
||||
'#101113',
|
||||
],
|
||||
},
|
||||
headings: {
|
||||
fontFamily,
|
||||
},
|
||||
components: {
|
||||
Modal: {
|
||||
styles: {
|
||||
header: {
|
||||
fontFamily,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
},
|
||||
InputWrapper: {
|
||||
styles: {
|
||||
label: {
|
||||
marginBottom: 4,
|
||||
},
|
||||
description: {
|
||||
marginBottom: 8,
|
||||
lineHeight: 1.3,
|
||||
},
|
||||
},
|
||||
},
|
||||
Select: Select.extend({
|
||||
styles: {
|
||||
input: {
|
||||
border: '1px solid var(--color-border)',
|
||||
},
|
||||
},
|
||||
}),
|
||||
Input: {
|
||||
styles: {
|
||||
input: {
|
||||
backgroundColor: 'var(--color-bg-field)',
|
||||
border: '1px solid var(--color-border)',
|
||||
},
|
||||
},
|
||||
},
|
||||
Card: {
|
||||
styles: (_theme: MantineTheme, props: { variant?: string }) => {
|
||||
if (props.variant === 'muted') {
|
||||
return {
|
||||
root: {
|
||||
backgroundColor: 'var(--color-bg-muted)',
|
||||
border: '1px solid var(--color-border)',
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
root: {
|
||||
backgroundColor: 'var(--color-bg-body)',
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
Divider: {
|
||||
styles: {
|
||||
root: {
|
||||
borderColor: 'var(--color-border)',
|
||||
borderTopColor: 'var(--color-border)',
|
||||
'--divider-color': 'var(--color-border)',
|
||||
'--item-border-color': 'var(--color-border)',
|
||||
},
|
||||
},
|
||||
},
|
||||
Accordion: {
|
||||
styles: (_theme: MantineTheme, props: { variant?: string }) => {
|
||||
const base = {
|
||||
control: {
|
||||
'--item-border-color': 'var(--color-border)',
|
||||
},
|
||||
item: {
|
||||
borderColor: 'var(--color-border)',
|
||||
},
|
||||
};
|
||||
if (props.variant === 'noPadding') {
|
||||
return {
|
||||
...base,
|
||||
content: {
|
||||
paddingInline: 0,
|
||||
},
|
||||
control: {
|
||||
paddingInlineStart: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
return base;
|
||||
},
|
||||
},
|
||||
UnstyledButton: {
|
||||
styles: {
|
||||
root: {
|
||||
'--item-border-color': 'var(--color-border)',
|
||||
},
|
||||
},
|
||||
},
|
||||
Paper: {
|
||||
classNames: (_theme: MantineTheme, props: { variant?: string }) => {
|
||||
if (props.variant === 'muted') {
|
||||
return {
|
||||
root: 'paper-muted',
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
styles: (_theme: MantineTheme, props: { variant?: string }) => {
|
||||
if (props.variant === 'muted') {
|
||||
return {
|
||||
root: {
|
||||
backgroundColor: 'var(--color-bg-muted)',
|
||||
border: '1px solid var(--color-border)',
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
root: {
|
||||
border: '1px solid var(--color-border)',
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
Text: Text.extend({
|
||||
styles: (theme, props) => {
|
||||
if (props.variant === 'danger') {
|
||||
return {
|
||||
root: {
|
||||
color: 'var(--color-text-danger)',
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
}),
|
||||
Button: Button.extend({
|
||||
vars: (theme, props) => {
|
||||
if (props.size === 'xxs') {
|
||||
return {
|
||||
root: {
|
||||
'--button-height': rem(22),
|
||||
'--button-padding-x': rem(4),
|
||||
'--button-fz': rem(12),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { root: {} };
|
||||
},
|
||||
}),
|
||||
SegmentedControl: {
|
||||
styles: {
|
||||
root: {
|
||||
background: 'var(--color-bg-field)',
|
||||
},
|
||||
indicator: {
|
||||
background: 'var(--color-bg-field-highlighted)',
|
||||
},
|
||||
},
|
||||
},
|
||||
ActionIcon: ActionIcon.extend({
|
||||
defaultProps: {
|
||||
variant: 'subtle',
|
||||
color: 'gray',
|
||||
},
|
||||
styles: (theme, props) => {
|
||||
// Subtle variant stays transparent
|
||||
if (props.variant === 'subtle') {
|
||||
return {
|
||||
root: {
|
||||
backgroundColor: 'transparent',
|
||||
color: 'var(--color-text)',
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--color-bg-hover)',
|
||||
},
|
||||
'&:active': {
|
||||
backgroundColor: 'var(--color-bg-muted)',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Default variant
|
||||
if (props.variant === 'default') {
|
||||
return {
|
||||
root: {
|
||||
backgroundColor: 'var(--color-bg-hover)',
|
||||
color: 'var(--color-text)',
|
||||
border: 'none',
|
||||
'&:hover': {
|
||||
backgroundColor: 'var(--color-bg-muted)',
|
||||
},
|
||||
'&:active': {
|
||||
backgroundColor: 'var(--color-bg-muted)',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
export const theme = makeTheme({});
|
||||
47
packages/app/src/theme/semanticColorsGrouped.ts
Normal file
47
packages/app/src/theme/semanticColorsGrouped.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
export const semanticColorsGrouped = {
|
||||
backgrounds: [
|
||||
'color-bg-body',
|
||||
'color-bg-surface',
|
||||
'color-bg-muted',
|
||||
'color-bg-highlighted',
|
||||
'color-bg-sidebar',
|
||||
'color-bg-header',
|
||||
'color-bg-hover',
|
||||
'color-bg-active',
|
||||
'color-bg-field',
|
||||
'color-bg-field-highlighted',
|
||||
'color-bg-neutral',
|
||||
'color-bg-success',
|
||||
'color-bg-danger',
|
||||
'color-bg-warning',
|
||||
'color-bg-brand',
|
||||
'color-bg-modal',
|
||||
'color-bg-code',
|
||||
'color-bg-kbd',
|
||||
],
|
||||
borders: ['color-border', 'color-border-code', 'color-border-muted'],
|
||||
text: [
|
||||
'color-text',
|
||||
'color-text-inverted',
|
||||
'color-text-primary',
|
||||
'color-text-primary-hover',
|
||||
'color-text-secondary',
|
||||
'color-text-muted',
|
||||
'color-text-muted-hover',
|
||||
'color-text-success',
|
||||
'color-text-success-hover',
|
||||
'color-text-danger',
|
||||
],
|
||||
icons: ['color-icon-primary', 'color-icon-muted'],
|
||||
overlay: ['color-overlay', 'color-backdrop'],
|
||||
states: ['color-state-hover', 'color-state-selected', 'color-state-focus'],
|
||||
json: [
|
||||
'color-json-string',
|
||||
'color-json-number',
|
||||
'color-json-boolean',
|
||||
'color-json-key',
|
||||
'color-json-object',
|
||||
'color-json-array',
|
||||
'color-json-punctuation',
|
||||
],
|
||||
};
|
||||
|
|
@ -97,7 +97,7 @@
|
|||
|
||||
.lineContext {
|
||||
color: var(--color-text-secondary);
|
||||
background-color: var(--color-bg-hover);
|
||||
background-color: var(--color-bg-highlighted);
|
||||
padding: 8px 0;
|
||||
border-radius: 4px;
|
||||
margin-top: 8px;
|
||||
|
|
@ -149,7 +149,7 @@
|
|||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg-hover);
|
||||
background-color: var(--color-bg-muted);
|
||||
}
|
||||
|
||||
&:active {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--color-bg-muted);
|
||||
background-color: var(--color-bg-sidebar);
|
||||
word-break: break-all;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
|
@ -134,12 +134,12 @@
|
|||
|
||||
@keyframes highlight {
|
||||
0% {
|
||||
background-color: var(--color-bg-body);
|
||||
background-color: var(--color-bg-highlighted);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: var(--color-bg-body);
|
||||
background-color: var(--color-bg-highlighted);
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg-muted);
|
||||
background-color: var(--color-bg-highlighted);
|
||||
}
|
||||
}
|
||||
|
||||
.timelineRowActive {
|
||||
background-color: var(--color-bg-surface);
|
||||
background-color: var(--color-bg-highlighted);
|
||||
border-radius: var(--mantine-radius-xs);
|
||||
}
|
||||
|
||||
.labelContainer {
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
/* Dark Mode (default) */
|
||||
[data-mantine-color-scheme='dark'] {
|
||||
/* Backgrounds */
|
||||
--color-bg-body: var(--mantine-color-dark-8);
|
||||
--color-bg-body: var(--mantine-color-dark-9);
|
||||
--color-bg-surface: var(--mantine-color-dark-5);
|
||||
--color-bg-inverted: var(--mantine-color-dark-1);
|
||||
--color-bg-muted: var(--mantine-color-dark-7);
|
||||
--color-bg-highlighted: var(--mantine-color-dark-5);
|
||||
--color-bg-sidebar: var(--mantine-color-dark-8);
|
||||
--color-bg-header: var(--mantine-color-dark-8);
|
||||
--color-bg-highlighted: rgb(55 58 64 / 70%);
|
||||
--color-bg-sidebar: var(--mantine-color-dark-9);
|
||||
--color-bg-header: var(--mantine-color-dark-9);
|
||||
--color-bg-hover: var(--mantine-color-dark-6);
|
||||
--color-bg-active: var(--mantine-color-dark-5);
|
||||
--color-bg-field: var(--mantine-color-dark-6);
|
||||
--color-bg-field-highlighted: var(--mantine-color-dark-4);
|
||||
--color-bg-field-highlighted: var(--mantine-color-dark-3);
|
||||
--color-bg-neutral: var(--mantine-color-dark-4);
|
||||
--color-bg-success: var(--mantine-color-green-5);
|
||||
--color-bg-danger: var(--mantine-color-red-5);
|
||||
--color-bg-danger: var(--mantine-color-red-4);
|
||||
--color-bg-warning: var(--mantine-color-orange-5);
|
||||
--color-bg-brand: #4ffa7a;
|
||||
|
||||
/* Borders & Dividers */
|
||||
--color-border: var(--mantine-color-dark-5);
|
||||
--color-border-muted: rgb(255 255 255 / 8%);
|
||||
|
||||
/* Text */
|
||||
--color-text: var(--mantine-color-dark-0);
|
||||
|
|
@ -30,7 +32,7 @@
|
|||
--color-text-muted-hover: var(--mantine-color-dark-1);
|
||||
--color-text-success: var(--mantine-color-green-4);
|
||||
--color-text-success-hover: var(--mantine-color-green-3);
|
||||
--color-text-danger: var(--mantine-color-red-4);
|
||||
--color-text-danger: var(--mantine-color-red-3);
|
||||
|
||||
/* Icons */
|
||||
--color-icon-primary: var(--mantine-color-dark-1);
|
||||
|
|
@ -51,12 +53,12 @@
|
|||
--color-bg-kbd: var(--mantine-color-dark-9);
|
||||
|
||||
/* JSON Syntax Highlighting */
|
||||
--color-json-string: var(--mantine-color-green-4);
|
||||
--color-json-number: var(--mantine-color-orange-4);
|
||||
--color-json-boolean: var(--mantine-color-orange-4);
|
||||
--color-json-key: var(--mantine-color-violet-4);
|
||||
--color-json-object: var(--mantine-color-green-4);
|
||||
--color-json-array: var(--mantine-color-green-4);
|
||||
--color-json-string: var(--mantine-color-green-3);
|
||||
--color-json-number: var(--mantine-color-orange-3);
|
||||
--color-json-boolean: var(--mantine-color-orange-3);
|
||||
--color-json-key: var(--mantine-color-violet-3);
|
||||
--color-json-object: var(--mantine-color-green-3);
|
||||
--color-json-array: var(--mantine-color-green-3);
|
||||
--color-json-punctuation: var(--mantine-color-dark-4);
|
||||
|
||||
/* Mantine Overrides */
|
||||
|
|
@ -71,15 +73,16 @@
|
|||
/* Backgrounds - inverted (dark becomes light) */
|
||||
--color-bg-body: var(--mantine-color-white);
|
||||
--color-bg-surface: var(--mantine-color-white);
|
||||
--color-bg-inverted: var(--mantine-color-dark-3);
|
||||
--color-bg-muted: #f6f6fa;
|
||||
--color-bg-highlighted: #e6e8ed;
|
||||
--color-bg-highlighted: rgb(230 232 237 / 70%);
|
||||
--color-bg-sidebar: var(--mantine-color-white);
|
||||
--color-bg-header: var(--mantine-color-gray-1);
|
||||
--color-bg-modal: var(--mantine-color-white);
|
||||
--color-bg-hover: var(--mantine-color-gray-3);
|
||||
--color-bg-active: var(--mantine-color-gray-4);
|
||||
--color-bg-field: #ececf5;
|
||||
--color-bg-field-highlighted: var(--mantine-color-gray-1);
|
||||
--color-bg-field-highlighted: var(--mantine-color-white);
|
||||
--color-bg-neutral: var(--mantine-color-gray-5);
|
||||
--color-bg-success: var(--mantine-color-green-8);
|
||||
--color-bg-danger: var(--mantine-color-red-8);
|
||||
|
|
@ -87,7 +90,8 @@
|
|||
--color-bg-brand: var(--mantine-color-green-8);
|
||||
|
||||
/* Borders & Dividers - inverted */
|
||||
--color-border: var(--mantine-color-gray-2);
|
||||
--color-border: var(--mantine-color-gray-1);
|
||||
--color-border-muted: rgb(0 0 0 / 5%);
|
||||
|
||||
/* Text - inverted */
|
||||
--color-text: var(--mantine-color-dark-9);
|
||||
|
|
@ -99,7 +103,7 @@
|
|||
--color-text-muted-hover: var(--mantine-color-dark-9);
|
||||
--color-text-success: var(--mantine-color-green-8);
|
||||
--color-text-success-hover: var(--mantine-color-green-9);
|
||||
--color-text-danger: var(--mantine-color-red-7);
|
||||
--color-text-danger: var(--mantine-color-red-8);
|
||||
|
||||
/* Icons - inverted */
|
||||
--color-icon-primary: var(--mantine-color-dark-8);
|
||||
|
|
|
|||
12
yarn.lock
12
yarn.lock
|
|
@ -4288,6 +4288,7 @@ __metadata:
|
|||
"@storybook/addon-interactions": "npm:^8.1.5"
|
||||
"@storybook/addon-links": "npm:^8.1.5"
|
||||
"@storybook/addon-styling-webpack": "npm:^1.0.0"
|
||||
"@storybook/addon-themes": "npm:^10.0.8"
|
||||
"@storybook/blocks": "npm:^8.1.5"
|
||||
"@storybook/nextjs": "npm:^8.1.5"
|
||||
"@storybook/react": "npm:^8.1.5"
|
||||
|
|
@ -8267,6 +8268,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/addon-themes@npm:^10.0.8":
|
||||
version: 10.0.8
|
||||
resolution: "@storybook/addon-themes@npm:10.0.8"
|
||||
dependencies:
|
||||
ts-dedent: "npm:^2.0.0"
|
||||
peerDependencies:
|
||||
storybook: ^10.0.8
|
||||
checksum: 10c0/5ab4c0a6cb2d1fed75cc8fb0183cef837127b01245da3dd2e5bbcb401a052aac7dd6fb33adad52d93cfdf7ded1ce2901cdee003fc680aa785c25c9139dca1069
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@storybook/addon-toolbars@npm:8.1.5":
|
||||
version: 8.1.5
|
||||
resolution: "@storybook/addon-toolbars@npm:8.1.5"
|
||||
|
|
|
|||
Loading…
Reference in a new issue