@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.
Files changed (90) hide show
  1. package/dist/date_picker_time_selector.css +1 -1
  2. package/dist/form/field/common/field_error.js +16 -9
  3. package/dist/form/field/common/field_error.js.map +1 -1
  4. package/dist/input.css +1 -1
  5. package/dist/navigation/index.d.ts +7 -0
  6. package/dist/navigation/index.d.ts.map +1 -0
  7. package/dist/navigation/index.js +17 -0
  8. package/dist/navigation/index.js.map +1 -0
  9. package/dist/navigation/tabs/primitives/tabs_bar.d.ts +7 -0
  10. package/dist/navigation/tabs/primitives/tabs_bar.d.ts.map +1 -0
  11. package/dist/navigation/tabs/primitives/tabs_bar.js +21 -0
  12. package/dist/navigation/tabs/primitives/tabs_bar.js.map +1 -0
  13. package/dist/navigation/tabs/primitives/tabs_list.d.ts +10 -0
  14. package/dist/navigation/tabs/primitives/tabs_list.d.ts.map +1 -0
  15. package/dist/navigation/tabs/primitives/tabs_list.js +36 -0
  16. package/dist/navigation/tabs/primitives/tabs_list.js.map +1 -0
  17. package/dist/navigation/tabs/state/context.d.ts +21 -0
  18. package/dist/navigation/tabs/state/context.d.ts.map +1 -0
  19. package/dist/navigation/tabs/state/context.js +34 -0
  20. package/dist/navigation/tabs/state/context.js.map +1 -0
  21. package/dist/navigation/tabs/state/link/tab_link.d.ts +9 -0
  22. package/dist/navigation/tabs/state/link/tab_link.d.ts.map +1 -0
  23. package/dist/navigation/tabs/state/link/tab_link.js +36 -0
  24. package/dist/navigation/tabs/state/link/tab_link.js.map +1 -0
  25. package/dist/navigation/tabs/state/link/use_tab_link.d.ts +5 -0
  26. package/dist/navigation/tabs/state/link/use_tab_link.d.ts.map +1 -0
  27. package/dist/navigation/tabs/state/link/use_tab_link.js +13 -0
  28. package/dist/navigation/tabs/state/link/use_tab_link.js.map +1 -0
  29. package/dist/navigation/tabs/state/nav_bar.d.ts +5 -0
  30. package/dist/navigation/tabs/state/nav_bar.d.ts.map +1 -0
  31. package/dist/navigation/tabs/state/nav_bar.js +36 -0
  32. package/dist/navigation/tabs/state/nav_bar.js.map +1 -0
  33. package/dist/navigation/tabs/state/tab.d.ts +6 -0
  34. package/dist/navigation/tabs/state/tab.d.ts.map +1 -0
  35. package/dist/navigation/tabs/state/tab.js +6 -0
  36. package/dist/navigation/tabs/state/tab.js.map +1 -0
  37. package/dist/overlay/portal/portal_platform.d.ts.map +1 -1
  38. package/dist/overlay/portal/portal_platform.js +3 -3
  39. package/dist/overlay/portal/portal_platform.js.map +1 -1
  40. package/dist/tabs_bar.css +1 -0
  41. package/dist/textarea.css +1 -1
  42. package/dist/theme_provider.css +1 -0
  43. package/dist/theme_provider.module-ChZQ5Xsk.js +5 -0
  44. package/dist/theme_provider.module-ChZQ5Xsk.js.map +1 -0
  45. package/dist/themes/stylesheets/reset.css +1 -1
  46. package/dist/themes/stylesheets/reset.js +1 -0
  47. package/dist/themes/stylesheets/reset.js.map +1 -1
  48. package/dist/themes/theme.d.ts +3 -2
  49. package/dist/themes/theme.d.ts.map +1 -1
  50. package/dist/themes/theme.js +20 -10
  51. package/dist/themes/theme.js.map +1 -1
  52. package/dist/themes/themes/ergo/ergo_theme.css +1 -1
  53. package/dist/themes/themes/ergo/ergo_theme.d.ts.map +1 -1
  54. package/dist/themes/themes/ergo/ergo_theme.js +110 -0
  55. package/dist/themes/themes/ergo/ergo_theme.js.map +1 -1
  56. package/dist/themes/themes/windows_98/windows_98.css +1 -1
  57. package/dist/themes/themes/windows_98/windows_98_theme.js +42 -4
  58. package/dist/themes/themes/windows_98/windows_98_theme.js.map +1 -1
  59. package/dist/tokens/bubble/bubble.js +31 -24
  60. package/dist/tokens/bubble/bubble.js.map +1 -1
  61. package/dist/tokens/chip/chip.js +15 -8
  62. package/dist/tokens/chip/chip.js.map +1 -1
  63. package/dist/utils/css_utils.d.ts +9 -0
  64. package/dist/utils/css_utils.d.ts.map +1 -0
  65. package/dist/utils/css_utils.js +45 -0
  66. package/dist/utils/css_utils.js.map +1 -0
  67. package/package.json +8 -1
  68. package/src/inputs/date_picker/date_picker_time_selector.module.css +0 -1
  69. package/src/inputs/input/input.module.css +0 -1
  70. package/src/inputs/textarea/textarea.module.css +0 -1
  71. package/src/navigation/index.ts +18 -0
  72. package/src/navigation/tabs/__stories__/state.stories.tsx +136 -0
  73. package/src/navigation/tabs/__stories__/tabs.stories.tsx +40 -0
  74. package/src/navigation/tabs/primitives/tabs_bar.module.css +13 -0
  75. package/src/navigation/tabs/primitives/tabs_bar.tsx +25 -0
  76. package/src/navigation/tabs/primitives/tabs_list.tsx +42 -0
  77. package/src/navigation/tabs/state/context.tsx +61 -0
  78. package/src/navigation/tabs/state/link/tab_link.tsx +45 -0
  79. package/src/navigation/tabs/state/link/use_tab_link.ts +17 -0
  80. package/src/navigation/tabs/state/nav_bar.tsx +37 -0
  81. package/src/navigation/tabs/state/tab.tsx +12 -0
  82. package/src/overlay/portal/portal_platform.tsx +1 -0
  83. package/src/surfaces/modal/__stories__/modal.stories.tsx +3 -1
  84. package/src/themes/stylesheets/reset.css +1 -0
  85. package/src/themes/theme.tsx +13 -4
  86. package/src/themes/theme_provider.module.css +6 -0
  87. package/src/themes/themes/ergo/ergo_theme.css +109 -0
  88. package/src/themes/themes/ergo/ergo_theme.ts +1 -0
  89. package/src/themes/themes/windows_98/windows_98.css +42 -4
  90. 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,13 @@
1
+ @layer tcn-system {
2
+ .tabs-bar {
3
+ width: auto;
4
+ }
5
+
6
+ .tabs-bar[data-variant="inline"] {
7
+ width: auto;
8
+ }
9
+
10
+ .tabs-bar[data-variant="default"] {
11
+ width: 100%;
12
+ }
13
+ }
@@ -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
- <button onClick={toggle}>{isOpen ? 'Close' : 'Open'}</button>
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>
@@ -80,6 +80,7 @@
80
80
  time,
81
81
  mark,
82
82
  audio,
83
+ button,
83
84
  video {
84
85
  margin: 0;
85
86
  padding: 0;
@@ -1,13 +1,18 @@
1
1
  import React, { useLayoutEffect } from 'react';
2
- import layers from '../css/layers.css?raw';
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 children;
35
+ return (
36
+ <Box className={clsx('tcn-theme-root', styles['tcn-theme-provider'])} {...boxProps}>
37
+ {children}
38
+ </Box>
39
+ );
31
40
  }
@@ -0,0 +1,6 @@
1
+ @layer tcn-system {
2
+ .tcn-theme-provider {
3
+ width: 100%;
4
+ height: 100%;
5
+ }
6
+ }
@@ -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));
@@ -1,4 +1,5 @@
1
1
  import css from './ergo_theme.css?raw';
2
+ import '@fontsource/lato';
2
3
 
3
4
  export const ergoStyleSheet = new CSSStyleSheet();
4
5
  ergoStyleSheet.replaceSync(css);
@@ -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
+ }