@tcn/ui 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/date_picker_time_selector.css +1 -1
- package/dist/form/field/common/field_error.js +16 -9
- package/dist/form/field/common/field_error.js.map +1 -1
- package/dist/input.css +1 -1
- package/dist/navigation/index.d.ts +7 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +17 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/navigation/tabs/primitives/tabs_bar.d.ts +7 -0
- package/dist/navigation/tabs/primitives/tabs_bar.d.ts.map +1 -0
- package/dist/navigation/tabs/primitives/tabs_bar.js +21 -0
- package/dist/navigation/tabs/primitives/tabs_bar.js.map +1 -0
- package/dist/navigation/tabs/primitives/tabs_list.d.ts +10 -0
- package/dist/navigation/tabs/primitives/tabs_list.d.ts.map +1 -0
- package/dist/navigation/tabs/primitives/tabs_list.js +36 -0
- package/dist/navigation/tabs/primitives/tabs_list.js.map +1 -0
- package/dist/navigation/tabs/state/context.d.ts +21 -0
- package/dist/navigation/tabs/state/context.d.ts.map +1 -0
- package/dist/navigation/tabs/state/context.js +34 -0
- package/dist/navigation/tabs/state/context.js.map +1 -0
- package/dist/navigation/tabs/state/link/tab_link.d.ts +9 -0
- package/dist/navigation/tabs/state/link/tab_link.d.ts.map +1 -0
- package/dist/navigation/tabs/state/link/tab_link.js +36 -0
- package/dist/navigation/tabs/state/link/tab_link.js.map +1 -0
- package/dist/navigation/tabs/state/link/use_tab_link.d.ts +5 -0
- package/dist/navigation/tabs/state/link/use_tab_link.d.ts.map +1 -0
- package/dist/navigation/tabs/state/link/use_tab_link.js +13 -0
- package/dist/navigation/tabs/state/link/use_tab_link.js.map +1 -0
- package/dist/navigation/tabs/state/nav_bar.d.ts +5 -0
- package/dist/navigation/tabs/state/nav_bar.d.ts.map +1 -0
- package/dist/navigation/tabs/state/nav_bar.js +36 -0
- package/dist/navigation/tabs/state/nav_bar.js.map +1 -0
- package/dist/navigation/tabs/state/tab.d.ts +6 -0
- package/dist/navigation/tabs/state/tab.d.ts.map +1 -0
- package/dist/navigation/tabs/state/tab.js +6 -0
- package/dist/navigation/tabs/state/tab.js.map +1 -0
- package/dist/overlay/portal/portal_platform.d.ts.map +1 -1
- package/dist/overlay/portal/portal_platform.js +3 -3
- package/dist/overlay/portal/portal_platform.js.map +1 -1
- package/dist/tabs_bar.css +1 -0
- package/dist/textarea.css +1 -1
- package/dist/theme_provider.css +1 -0
- package/dist/theme_provider.module-ChZQ5Xsk.js +5 -0
- package/dist/theme_provider.module-ChZQ5Xsk.js.map +1 -0
- package/dist/themes/stylesheets/reset.css +1 -1
- package/dist/themes/stylesheets/reset.js +1 -0
- package/dist/themes/stylesheets/reset.js.map +1 -1
- package/dist/themes/theme.d.ts +3 -2
- package/dist/themes/theme.d.ts.map +1 -1
- package/dist/themes/theme.js +20 -10
- package/dist/themes/theme.js.map +1 -1
- package/dist/themes/themes/ergo/ergo_theme.css +1 -1
- package/dist/themes/themes/ergo/ergo_theme.d.ts.map +1 -1
- package/dist/themes/themes/ergo/ergo_theme.js +110 -0
- package/dist/themes/themes/ergo/ergo_theme.js.map +1 -1
- package/dist/themes/themes/windows_98/windows_98.css +1 -1
- package/dist/themes/themes/windows_98/windows_98_theme.js +42 -4
- package/dist/themes/themes/windows_98/windows_98_theme.js.map +1 -1
- package/dist/tokens/bubble/bubble.js +31 -24
- package/dist/tokens/bubble/bubble.js.map +1 -1
- package/dist/tokens/chip/chip.js +15 -8
- package/dist/tokens/chip/chip.js.map +1 -1
- package/dist/utils/css_utils.d.ts +9 -0
- package/dist/utils/css_utils.d.ts.map +1 -0
- package/dist/utils/css_utils.js +45 -0
- package/dist/utils/css_utils.js.map +1 -0
- package/package.json +8 -1
- package/src/inputs/date_picker/date_picker_time_selector.module.css +0 -1
- package/src/inputs/input/input.module.css +0 -1
- package/src/inputs/textarea/textarea.module.css +0 -1
- package/src/navigation/index.ts +18 -0
- package/src/navigation/tabs/__stories__/state.stories.tsx +136 -0
- package/src/navigation/tabs/__stories__/tabs.stories.tsx +40 -0
- package/src/navigation/tabs/primitives/tabs_bar.module.css +13 -0
- package/src/navigation/tabs/primitives/tabs_bar.tsx +25 -0
- package/src/navigation/tabs/primitives/tabs_list.tsx +42 -0
- package/src/navigation/tabs/state/context.tsx +61 -0
- package/src/navigation/tabs/state/link/tab_link.tsx +45 -0
- package/src/navigation/tabs/state/link/use_tab_link.ts +17 -0
- package/src/navigation/tabs/state/nav_bar.tsx +37 -0
- package/src/navigation/tabs/state/tab.tsx +12 -0
- package/src/overlay/portal/portal_platform.tsx +1 -0
- package/src/surfaces/modal/__stories__/modal.stories.tsx +3 -1
- package/src/themes/stylesheets/reset.css +1 -0
- package/src/themes/theme.tsx +13 -4
- package/src/themes/theme_provider.module.css +6 -0
- package/src/themes/themes/ergo/ergo_theme.css +109 -0
- package/src/themes/themes/ergo/ergo_theme.ts +1 -0
- package/src/themes/themes/windows_98/windows_98.css +42 -4
- package/src/utils/css_utils.ts +64 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { TabsList } from '../primitives/tabs_list.js';
|
|
3
|
+
import { TabItem } from '../primitives/tabs_list.js';
|
|
4
|
+
import { TabsBar } from '../primitives/tabs_bar.js';
|
|
5
|
+
import { Spacer } from '../../../stacks/spacer.js';
|
|
6
|
+
import { Bubble } from '../../../tokens/bubble/bubble.js';
|
|
7
|
+
|
|
8
|
+
const meta: Meta = {
|
|
9
|
+
title: 'Navigation/Tabs/Component',
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj;
|
|
16
|
+
|
|
17
|
+
const TabDemo = () => {
|
|
18
|
+
return (
|
|
19
|
+
<TabsBar>
|
|
20
|
+
<TabsList>
|
|
21
|
+
<TabItem width={100}>Tab One</TabItem>
|
|
22
|
+
<TabItem width={100} selected>
|
|
23
|
+
Tab Two
|
|
24
|
+
</TabItem>
|
|
25
|
+
<TabItem width={100}>Tab Three</TabItem>
|
|
26
|
+
<TabItem width={100}>
|
|
27
|
+
Custom Label
|
|
28
|
+
<Spacer width="4px" />
|
|
29
|
+
<Bubble size="sm" backgroundColor="#e0383e" textColor="#fff">
|
|
30
|
+
10
|
|
31
|
+
</Bubble>
|
|
32
|
+
</TabItem>
|
|
33
|
+
</TabsList>
|
|
34
|
+
</TabsBar>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Default: Story = {
|
|
39
|
+
render: () => <TabDemo />,
|
|
40
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type FC, type PropsWithChildren } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { HStack, type HStackProps } from '../../../stacks/h_stack.js';
|
|
4
|
+
import styles from './tabs_bar.module.css';
|
|
5
|
+
|
|
6
|
+
export interface TabsBarProps extends HStackProps {
|
|
7
|
+
variant?: 'default' | 'inline';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const TabsBar: FC<PropsWithChildren<TabsBarProps>> = ({
|
|
11
|
+
children,
|
|
12
|
+
className,
|
|
13
|
+
variant = 'default',
|
|
14
|
+
...props
|
|
15
|
+
}) => {
|
|
16
|
+
return (
|
|
17
|
+
<HStack
|
|
18
|
+
data-variant={variant}
|
|
19
|
+
className={clsx('tcn-tabs-bar', styles['tabs-bar'], className)}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</HStack>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { forwardRef, type FC, type PropsWithChildren } from 'react';
|
|
2
|
+
import { BaseButton, type BaseButtonProps } from '../../../actions/index.js';
|
|
3
|
+
import { HStack, type HStackProps } from '../../../stacks/h_stack.js';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
|
|
6
|
+
export type TabsListProps = HStackProps;
|
|
7
|
+
|
|
8
|
+
export const TabsList: FC<PropsWithChildren<TabsListProps>> = ({
|
|
9
|
+
children,
|
|
10
|
+
className,
|
|
11
|
+
role = 'tablist',
|
|
12
|
+
as = 'menu',
|
|
13
|
+
...props
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<HStack as={as} role={role} className={clsx('tcn-tabs-list', className)} {...props}>
|
|
17
|
+
{children}
|
|
18
|
+
</HStack>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export interface TabItemProps extends Omit<BaseButtonProps, 'hierarchy'> {
|
|
23
|
+
selected?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const TabItem = forwardRef<HTMLButtonElement, PropsWithChildren<TabItemProps>>(
|
|
27
|
+
({ children, className, role = 'tab', selected = false, ...props }, ref) => {
|
|
28
|
+
return (
|
|
29
|
+
<BaseButton
|
|
30
|
+
ref={ref}
|
|
31
|
+
role={role}
|
|
32
|
+
className={clsx(className, 'tcn-interactive', 'tcn-tab-item')}
|
|
33
|
+
hierarchy="tertiary"
|
|
34
|
+
data-is-selected={selected}
|
|
35
|
+
aria-selected={selected}
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
{children}
|
|
39
|
+
</BaseButton>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useState,
|
|
4
|
+
useContext,
|
|
5
|
+
useMemo,
|
|
6
|
+
type FC,
|
|
7
|
+
type PropsWithChildren,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import type { Rectangle } from '../../../utils/index.js';
|
|
10
|
+
|
|
11
|
+
export interface TabsState {
|
|
12
|
+
value: string;
|
|
13
|
+
onChange?: (value: string) => void;
|
|
14
|
+
minItemWidth?: number;
|
|
15
|
+
maxItemWidth?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TabsContextValue extends TabsState {
|
|
19
|
+
activeTrigger: Rectangle | null;
|
|
20
|
+
setActiveTrigger: (trigger: Rectangle) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const TabsContext = createContext<TabsContextValue | null>(null);
|
|
24
|
+
|
|
25
|
+
export function useTabs(): TabsContextValue {
|
|
26
|
+
const context = useContext(TabsContext);
|
|
27
|
+
if (!context) {
|
|
28
|
+
throw new Error('useTabs must be used within a Tabs provider');
|
|
29
|
+
}
|
|
30
|
+
return context;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TabsProps {
|
|
34
|
+
value: string;
|
|
35
|
+
onChange?: (value: string) => void;
|
|
36
|
+
minItemWidth?: number;
|
|
37
|
+
maxItemWidth?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const Tabs: FC<PropsWithChildren<TabsProps>> = ({
|
|
41
|
+
value,
|
|
42
|
+
onChange,
|
|
43
|
+
children,
|
|
44
|
+
minItemWidth,
|
|
45
|
+
maxItemWidth,
|
|
46
|
+
}) => {
|
|
47
|
+
const [activeTrigger, setActiveTrigger] = useState<Rectangle | null>(null);
|
|
48
|
+
const contextValue = useMemo(
|
|
49
|
+
() => ({
|
|
50
|
+
value,
|
|
51
|
+
onChange,
|
|
52
|
+
activeTrigger,
|
|
53
|
+
setActiveTrigger,
|
|
54
|
+
minItemWidth,
|
|
55
|
+
maxItemWidth,
|
|
56
|
+
}),
|
|
57
|
+
[value, onChange, activeTrigger, minItemWidth, maxItemWidth]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return <TabsContext.Provider value={contextValue}>{children}</TabsContext.Provider>;
|
|
61
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { forwardRef, useCallback, type PropsWithChildren } from 'react';
|
|
2
|
+
import { TabItem, type TabItemProps } from '../../primitives/tabs_list.js';
|
|
3
|
+
import { useForkRef } from '../../../../utils/index.js';
|
|
4
|
+
import { useTabs } from '../context.js';
|
|
5
|
+
import { useTabLink } from './use_tab_link.js';
|
|
6
|
+
|
|
7
|
+
export interface TabLinkOwnProps {
|
|
8
|
+
value: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TabLinkProps
|
|
12
|
+
extends Omit<TabItemProps, 'selected' | 'value'>,
|
|
13
|
+
TabLinkOwnProps {}
|
|
14
|
+
|
|
15
|
+
export const TabLink = forwardRef<HTMLButtonElement, PropsWithChildren<TabLinkProps>>(
|
|
16
|
+
({ children, value, onClick, minWidth, maxWidth, ...props }, forwardedRef) => {
|
|
17
|
+
const { ref: internalRef, isMatch } = useTabLink(value);
|
|
18
|
+
const state = useTabs();
|
|
19
|
+
const ref = useForkRef(internalRef, forwardedRef);
|
|
20
|
+
|
|
21
|
+
const handleClick = useCallback(
|
|
22
|
+
(event: React.MouseEvent<HTMLButtonElement>) => {
|
|
23
|
+
state.onChange?.(value);
|
|
24
|
+
onClick?.(event);
|
|
25
|
+
},
|
|
26
|
+
[state, value, onClick]
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const pickMinWidth = state.minItemWidth ?? minWidth;
|
|
30
|
+
const pickMaxWidth = state.maxItemWidth ?? maxWidth;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<TabItem
|
|
34
|
+
ref={ref}
|
|
35
|
+
selected={isMatch}
|
|
36
|
+
onClick={handleClick}
|
|
37
|
+
minWidth={pickMinWidth}
|
|
38
|
+
maxWidth={pickMaxWidth}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
{children}
|
|
42
|
+
</TabItem>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
|
2
|
+
import { useTabs } from '../context.js';
|
|
3
|
+
import { useTrackActiveItemRectangle } from '../../../../utils/css_utils.js';
|
|
4
|
+
|
|
5
|
+
export function useTabLink(value: string) {
|
|
6
|
+
const state = useTabs();
|
|
7
|
+
const isMatch = state.value === value;
|
|
8
|
+
const { ref, rectangle } = useTrackActiveItemRectangle(isMatch);
|
|
9
|
+
|
|
10
|
+
useLayoutEffect(() => {
|
|
11
|
+
if (rectangle) {
|
|
12
|
+
state.setActiveTrigger(rectangle);
|
|
13
|
+
}
|
|
14
|
+
}, [rectangle, state.setActiveTrigger]);
|
|
15
|
+
|
|
16
|
+
return { ref, isMatch };
|
|
17
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type FC, type PropsWithChildren } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { TabsBar, type TabsBarProps } from '../primitives/tabs_bar.js';
|
|
4
|
+
import { TabsList } from '../primitives/tabs_list.js';
|
|
5
|
+
import { useTabs } from './context.js';
|
|
6
|
+
import { convertRectangleToCssVariables } from '../../../utils/css_utils.js';
|
|
7
|
+
|
|
8
|
+
export type TabsNavbarProps = TabsBarProps;
|
|
9
|
+
|
|
10
|
+
export const TabsNavbar: FC<PropsWithChildren<TabsNavbarProps>> = ({
|
|
11
|
+
children,
|
|
12
|
+
className,
|
|
13
|
+
variant = 'default',
|
|
14
|
+
style,
|
|
15
|
+
...props
|
|
16
|
+
}) => {
|
|
17
|
+
const state = useTabs();
|
|
18
|
+
const cssVariables = convertRectangleToCssVariables(
|
|
19
|
+
'tabs',
|
|
20
|
+
'active',
|
|
21
|
+
state.activeTrigger
|
|
22
|
+
);
|
|
23
|
+
const finalStyle = {
|
|
24
|
+
...cssVariables,
|
|
25
|
+
...style,
|
|
26
|
+
};
|
|
27
|
+
return (
|
|
28
|
+
<TabsBar
|
|
29
|
+
style={finalStyle}
|
|
30
|
+
className={clsx('tcn-tabs-navbar', className)}
|
|
31
|
+
variant={variant}
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
<TabsList>{children}</TabsList>
|
|
35
|
+
</TabsBar>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FC, PropsWithChildren } from 'react';
|
|
2
|
+
import { useTabs } from './context.js';
|
|
3
|
+
|
|
4
|
+
export interface TabProps {
|
|
5
|
+
value: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const Tab: FC<PropsWithChildren<TabProps>> = ({ value, children }) => {
|
|
9
|
+
const state = useTabs();
|
|
10
|
+
if (state.value !== value) return null;
|
|
11
|
+
return children;
|
|
12
|
+
};
|
|
@@ -51,6 +51,7 @@ export class PortalPlatform {
|
|
|
51
51
|
const portalPlatform = this._window.document.createElement('div');
|
|
52
52
|
portalPlatform.id = 'tcn-portal-platform';
|
|
53
53
|
portalPlatform.classList.add('tcn-portal-platform');
|
|
54
|
+
portalPlatform.classList.add('tcn-theme-root');
|
|
54
55
|
root.appendChild(portalPlatform);
|
|
55
56
|
root.classList.add('tcn-platform-root');
|
|
56
57
|
|
|
@@ -23,7 +23,9 @@ export const ModalStory = () => {
|
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<ZStack height="100%" width="100%" minHeight="600px">
|
|
26
|
-
<
|
|
26
|
+
<Button hierarchy="secondary" onClick={toggle}>
|
|
27
|
+
{isOpen ? 'Close' : 'Open'}
|
|
28
|
+
</Button>
|
|
27
29
|
|
|
28
30
|
<Modal isOpen={isOpen} width="400px" height="500px">
|
|
29
31
|
<Header>
|
package/src/themes/theme.tsx
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import React, { useLayoutEffect } from 'react';
|
|
2
|
-
import
|
|
2
|
+
import { Box, type BoxProps } from '../stacks/index.js';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
|
|
5
|
+
// Styles
|
|
3
6
|
import './stylesheets/reset.css';
|
|
7
|
+
import styles from './theme_provider.module.css';
|
|
8
|
+
import layers from '../css/layers.css?raw';
|
|
4
9
|
|
|
5
|
-
export interface ThemeProps {
|
|
10
|
+
export interface ThemeProps extends BoxProps {
|
|
6
11
|
styleSheets: CSSStyleSheet[];
|
|
7
12
|
children: React.ReactNode;
|
|
8
13
|
}
|
|
9
14
|
|
|
10
|
-
export function Theme({ styleSheets, children }: ThemeProps) {
|
|
15
|
+
export function Theme({ styleSheets, children, ...boxProps }: ThemeProps) {
|
|
11
16
|
useLayoutEffect(() => {
|
|
12
17
|
document.adoptedStyleSheets = styleSheets;
|
|
13
18
|
}, [styleSheets]);
|
|
@@ -27,5 +32,9 @@ export function Theme({ styleSheets, children }: ThemeProps) {
|
|
|
27
32
|
};
|
|
28
33
|
}, []);
|
|
29
34
|
|
|
30
|
-
return
|
|
35
|
+
return (
|
|
36
|
+
<Box className={clsx('tcn-theme-root', styles['tcn-theme-provider'])} {...boxProps}>
|
|
37
|
+
{children}
|
|
38
|
+
</Box>
|
|
39
|
+
);
|
|
31
40
|
}
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
--foreground-color-quaternary: #222222;
|
|
71
71
|
|
|
72
72
|
--material-disabled: #d3d3d3;
|
|
73
|
+
--material-line: #aaaaaa;
|
|
73
74
|
--material-secondary-dark: 197 29.1% 40.4%;
|
|
74
75
|
--material-tan: 33, 22%, 84%;
|
|
75
76
|
|
|
@@ -87,6 +88,12 @@
|
|
|
87
88
|
--action-encouraged: 120, 90%, 40%;
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
.tcn-theme-root {
|
|
92
|
+
font-family: var(--font-family);
|
|
93
|
+
color: var(--font-color);
|
|
94
|
+
background-color: var(--background-color-primary);
|
|
95
|
+
}
|
|
96
|
+
|
|
90
97
|
/* ===== Actions ===== */
|
|
91
98
|
|
|
92
99
|
.tcn-interactive {
|
|
@@ -105,6 +112,7 @@
|
|
|
105
112
|
--act-faint: hsla(var(--action), 0.2);
|
|
106
113
|
--on-mat: hsl(var(--on-material));
|
|
107
114
|
--on-mat-faint: hsla(var(--on-material), 0.2);
|
|
115
|
+
--on-mat-down: color-mix(in srgb, var(--on-mat), black 8%);
|
|
108
116
|
--mat: hsl(var(--material));
|
|
109
117
|
--mat-down: color-mix(in srgb, var(--mat), black 8%);
|
|
110
118
|
--mat-raised: color-mix(in srgb, var(--mat), white 8%);
|
|
@@ -368,6 +376,107 @@
|
|
|
368
376
|
}
|
|
369
377
|
}
|
|
370
378
|
|
|
379
|
+
/* ===== Tabs ===== */
|
|
380
|
+
.tcn-tabs-bar {
|
|
381
|
+
.tcn-tabs-list {
|
|
382
|
+
.tcn-tab-item {
|
|
383
|
+
min-height: 24px;
|
|
384
|
+
padding: 0px var(--padding-medium);
|
|
385
|
+
text-decoration: none;
|
|
386
|
+
text-overflow: ellipsis;
|
|
387
|
+
overflow: hidden;
|
|
388
|
+
white-space: nowrap;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/* Default */
|
|
394
|
+
.tcn-tabs-bar[data-variant="default"] {
|
|
395
|
+
.tcn-tabs-list {
|
|
396
|
+
.tcn-tab-item {
|
|
397
|
+
box-sizing: border-box;
|
|
398
|
+
border: none;
|
|
399
|
+
padding: 0px var(--padding-medium);
|
|
400
|
+
}
|
|
401
|
+
.tcn-tab-item[data-is-selected="true"] {
|
|
402
|
+
--on-material: var(--action);
|
|
403
|
+
}
|
|
404
|
+
.tcn-tab-item[data-is-selected="false"]:hover::after {
|
|
405
|
+
content: "";
|
|
406
|
+
display: block;
|
|
407
|
+
position: absolute;
|
|
408
|
+
left: 0;
|
|
409
|
+
right: 0;
|
|
410
|
+
bottom: 0px;
|
|
411
|
+
height: 1px;
|
|
412
|
+
background: var(--on-mat);
|
|
413
|
+
pointer-events: none;
|
|
414
|
+
z-index: 2;
|
|
415
|
+
width: 100%;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
&::after {
|
|
419
|
+
content: "";
|
|
420
|
+
position: absolute;
|
|
421
|
+
bottom: -1px;
|
|
422
|
+
left: 0;
|
|
423
|
+
min-height: 2px;
|
|
424
|
+
transform: translateX(var(--tabs-active-rectangle-position-x, 0));
|
|
425
|
+
width: var(--tabs-active-rectangle-width, 0);
|
|
426
|
+
background: hsl(var(--action));
|
|
427
|
+
pointer-events: none;
|
|
428
|
+
border-bottom-left-radius: 2px;
|
|
429
|
+
border-bottom-right-radius: 2px;
|
|
430
|
+
transition:
|
|
431
|
+
transform 300ms ease-in-out,
|
|
432
|
+
width 300ms ease-in-out;
|
|
433
|
+
will-change: transform, width;
|
|
434
|
+
z-index: 2;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
&::before {
|
|
439
|
+
content: "";
|
|
440
|
+
position: absolute;
|
|
441
|
+
bottom: 0;
|
|
442
|
+
left: 0;
|
|
443
|
+
width: 100%;
|
|
444
|
+
height: 1px;
|
|
445
|
+
background: var(--material-line);
|
|
446
|
+
pointer-events: none;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/* Inline */
|
|
451
|
+
.tcn-tabs-bar[data-variant="inline"] {
|
|
452
|
+
min-width: min-content;
|
|
453
|
+
width: auto;
|
|
454
|
+
flex-grow: 0;
|
|
455
|
+
border-radius: var(--shape-radius-medium);
|
|
456
|
+
border: 1px solid hsl(var(--on-material));
|
|
457
|
+
--action: var(--on-material);
|
|
458
|
+
|
|
459
|
+
.tcn-tabs-list {
|
|
460
|
+
gap: var(--gap-small);
|
|
461
|
+
padding: var(--padding-small);
|
|
462
|
+
.tcn-tab-item {
|
|
463
|
+
border-radius: var(--shape-radius-medium);
|
|
464
|
+
}
|
|
465
|
+
.tcn-tab-item[data-is-selected="true"] {
|
|
466
|
+
background: var(--on-mat);
|
|
467
|
+
color: var(--mat);
|
|
468
|
+
}
|
|
469
|
+
.tcn-tab-item[data-is-selected="true"]:hover {
|
|
470
|
+
background: color-mix(in srgb, var(--on-mat), white 8%);
|
|
471
|
+
}
|
|
472
|
+
.tcn-tab-item[data-is-selected="true"]:active {
|
|
473
|
+
background: color-mix(in srgb, var(--on-mat), black 8%);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/* ===== MATERIAL ===== */
|
|
479
|
+
|
|
371
480
|
.material {
|
|
372
481
|
background-color: hsl(var(--material));
|
|
373
482
|
color: hsl(var(--on-material));
|
|
@@ -36,10 +36,6 @@
|
|
|
36
36
|
--quaternary-color: #000080;
|
|
37
37
|
--quaternary-color-strong: #000080;
|
|
38
38
|
|
|
39
|
-
--font-color: #222222;
|
|
40
|
-
--font-family: "Pixelated MS Sans Serif", Arial;
|
|
41
|
-
--font-size: 12px;
|
|
42
|
-
|
|
43
39
|
--background-color-primary: #c0c0c0;
|
|
44
40
|
--background-color-secondary: #c0c0c0;
|
|
45
41
|
--background-color-tertiary: #c0c0c0;
|
|
@@ -51,6 +47,17 @@
|
|
|
51
47
|
--foreground-color-quaternary: #222222;
|
|
52
48
|
|
|
53
49
|
--accent-color: #000080;
|
|
50
|
+
|
|
51
|
+
--font-color: #222222;
|
|
52
|
+
--font-family: "Microsoft Sans Serif", "Arial", sans-serif;
|
|
53
|
+
--font-size: 12px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.tcn-theme-root {
|
|
57
|
+
font-family: var(--font-family);
|
|
58
|
+
color: var(--font-color);
|
|
59
|
+
-webkit-font-smoothing: none;
|
|
60
|
+
background-color: var(--background-color-primary);
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
/* ===== TYPOGRAPHY ===== */
|
|
@@ -82,6 +89,7 @@
|
|
|
82
89
|
|
|
83
90
|
/* ===== BUTTONS ===== */
|
|
84
91
|
/* Base button styles */
|
|
92
|
+
.tcn-base-button,
|
|
85
93
|
.tcn-button,
|
|
86
94
|
.tcn-button[data-hierarchy="primary"],
|
|
87
95
|
.tcn-button[data-hierarchy="secondary"],
|
|
@@ -1029,3 +1037,33 @@
|
|
|
1029
1037
|
border-left: 1px solid #808080;
|
|
1030
1038
|
}
|
|
1031
1039
|
}
|
|
1040
|
+
|
|
1041
|
+
/* ===== TABS ===== */
|
|
1042
|
+
|
|
1043
|
+
.tcn-tabs-list {
|
|
1044
|
+
display: flex;
|
|
1045
|
+
list-style-type: none;
|
|
1046
|
+
margin: 0 0 -2px;
|
|
1047
|
+
padding-left: 3px;
|
|
1048
|
+
position: relative;
|
|
1049
|
+
text-indent: 0;
|
|
1050
|
+
|
|
1051
|
+
.tcn-tab-item {
|
|
1052
|
+
border-top-left-radius: 3px;
|
|
1053
|
+
border-top-right-radius: 3px;
|
|
1054
|
+
box-shadow:
|
|
1055
|
+
inset -1px 0 #0a0a0a,
|
|
1056
|
+
inset 1px 1px #dfdfdf,
|
|
1057
|
+
inset -2px 0 grey,
|
|
1058
|
+
inset 2px 2px #fff;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
.tcn-tab-item[data-is-selected="true"] {
|
|
1062
|
+
background-color: silver;
|
|
1063
|
+
margin-left: -3px;
|
|
1064
|
+
margin-top: -2px;
|
|
1065
|
+
padding-bottom: 2px;
|
|
1066
|
+
position: relative;
|
|
1067
|
+
z-index: 8;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Rectangle } from './types/dimensions.js';
|
|
2
|
+
import { useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
export const convertRectangleToCssVariables = (
|
|
5
|
+
componentName: string,
|
|
6
|
+
stateName: string,
|
|
7
|
+
rectangle?: Rectangle | null
|
|
8
|
+
) => {
|
|
9
|
+
if (!rectangle) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
const prefix = `--${componentName}-${stateName}-rectangle-`;
|
|
13
|
+
return {
|
|
14
|
+
[`${prefix}position-x`]: rectangle.position.x + 'px',
|
|
15
|
+
[`${prefix}position-y`]: rectangle.position.y + 'px',
|
|
16
|
+
[`${prefix}width`]: rectangle.dimensions.width + 'px',
|
|
17
|
+
[`${prefix}height`]: rectangle.dimensions.height + 'px',
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function getElementRect(element: HTMLElement | null): Rectangle | null {
|
|
22
|
+
if (!element) return null;
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
dimensions: {
|
|
26
|
+
width: element.offsetWidth,
|
|
27
|
+
height: element.offsetHeight,
|
|
28
|
+
},
|
|
29
|
+
position: {
|
|
30
|
+
x: element.offsetLeft,
|
|
31
|
+
y: element.offsetTop,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useTrackActiveItemRectangle(isActive: boolean) {
|
|
37
|
+
const ref = useRef<HTMLElement>(null);
|
|
38
|
+
const [rectangle, setRectangle] = useState<Rectangle | null>(null);
|
|
39
|
+
|
|
40
|
+
useLayoutEffect(() => {
|
|
41
|
+
const element = ref.current;
|
|
42
|
+
if (!element) return;
|
|
43
|
+
|
|
44
|
+
const update = () => {
|
|
45
|
+
const rect = getElementRect(element);
|
|
46
|
+
if (isActive && rect) {
|
|
47
|
+
setRectangle(rect);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
update();
|
|
52
|
+
|
|
53
|
+
const resizeObserver = new ResizeObserver(update);
|
|
54
|
+
resizeObserver.observe(element);
|
|
55
|
+
window.addEventListener('resize', update);
|
|
56
|
+
|
|
57
|
+
return () => {
|
|
58
|
+
resizeObserver.disconnect();
|
|
59
|
+
window.removeEventListener('resize', update);
|
|
60
|
+
};
|
|
61
|
+
}, [isActive]);
|
|
62
|
+
|
|
63
|
+
return { ref, rectangle };
|
|
64
|
+
}
|