@tcn/ui 0.1.1 → 0.2.0

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 (107) hide show
  1. package/README.md +38 -3
  2. package/dist/draggable.css +1 -0
  3. package/dist/feedback/progress/progress_bar.js +1 -1
  4. package/dist/form/field/common/field_description.js +1 -1
  5. package/dist/form/field/common/field_error.js +1 -1
  6. package/dist/form/field/common/field_label.js +1 -1
  7. package/dist/inputs/date_picker/date_picker_date.js +1 -1
  8. package/dist/inputs/date_picker/date_picker_day.js +1 -1
  9. package/dist/inputs/date_picker/date_picker_time_selector.js +1 -1
  10. package/dist/inputs/date_picker/date_picker_year_selector.js +1 -1
  11. package/dist/inputs/phone_number_input/phone_number_input.d.ts +2 -0
  12. package/dist/inputs/phone_number_input/phone_number_input.d.ts.map +1 -1
  13. package/dist/inputs/phone_number_input/phone_number_input.js +159 -153
  14. package/dist/inputs/phone_number_input/phone_number_input.js.map +1 -1
  15. package/dist/inputs/suggestions/suggestion_list.js +1 -1
  16. package/dist/inputs/textarea/textarea.d.ts +2 -2
  17. package/dist/inputs/textarea/textarea.d.ts.map +1 -1
  18. package/dist/inputs/textarea/textarea.js.map +1 -1
  19. package/dist/layouts/header/header.d.ts.map +1 -1
  20. package/dist/layouts/header/header.js.map +1 -1
  21. package/dist/overlay/frame/frame.d.ts +11 -0
  22. package/dist/overlay/frame/frame.d.ts.map +1 -0
  23. package/dist/overlay/frame/frame.js +18 -0
  24. package/dist/overlay/frame/frame.js.map +1 -0
  25. package/dist/overlay/index.d.ts +1 -0
  26. package/dist/overlay/index.d.ts.map +1 -1
  27. package/dist/overlay/index.js +4 -2
  28. package/dist/overlay/index.js.map +1 -1
  29. package/dist/stacks/box/box.d.ts +1 -1
  30. package/dist/stacks/box/box.d.ts.map +1 -1
  31. package/dist/stacks/box/box.js.map +1 -1
  32. package/dist/surfaces/index.d.ts +2 -2
  33. package/dist/surfaces/index.d.ts.map +1 -1
  34. package/dist/surfaces/index.js +22 -22
  35. package/dist/surfaces/modal/modal.d.ts +3 -2
  36. package/dist/surfaces/modal/modal.d.ts.map +1 -1
  37. package/dist/surfaces/modal/modal.js +14 -13
  38. package/dist/surfaces/modal/modal.js.map +1 -1
  39. package/dist/surfaces/window/window.d.ts +3 -2
  40. package/dist/surfaces/window/window.d.ts.map +1 -1
  41. package/dist/surfaces/window/window.js +17 -7
  42. package/dist/surfaces/window/window.js.map +1 -1
  43. package/dist/themes/themes/ergo/ergo_theme.js +69 -0
  44. package/dist/themes/themes/ergo/ergo_theme.js.map +1 -1
  45. package/dist/typography/title/title.d.ts +2 -1
  46. package/dist/typography/title/title.d.ts.map +1 -1
  47. package/dist/typography/title/title.js +23 -22
  48. package/dist/typography/title/title.js.map +1 -1
  49. package/dist/utils/dnd/context.d.ts +4 -0
  50. package/dist/utils/dnd/context.d.ts.map +1 -0
  51. package/dist/utils/dnd/context.js +20 -0
  52. package/dist/utils/dnd/context.js.map +1 -0
  53. package/dist/utils/dnd/draggable/draggable.d.ts +7 -0
  54. package/dist/utils/dnd/draggable/draggable.d.ts.map +1 -0
  55. package/dist/utils/dnd/draggable/draggable.js +27 -0
  56. package/dist/utils/dnd/draggable/draggable.js.map +1 -0
  57. package/dist/utils/dnd/handle.d.ts +6 -0
  58. package/dist/utils/dnd/handle.d.ts.map +1 -0
  59. package/dist/utils/dnd/handle.js +22 -0
  60. package/dist/utils/dnd/handle.js.map +1 -0
  61. package/dist/utils/dnd/hooks/use_drag_container.d.ts +7 -0
  62. package/dist/utils/dnd/hooks/use_drag_container.d.ts.map +1 -0
  63. package/dist/utils/dnd/hooks/use_drag_container.js +30 -0
  64. package/dist/utils/dnd/hooks/use_drag_container.js.map +1 -0
  65. package/dist/utils/{hooks → dnd/hooks}/use_draggable.d.ts +3 -3
  66. package/dist/utils/dnd/hooks/use_draggable.d.ts.map +1 -0
  67. package/dist/utils/dnd/hooks/use_draggable.js +41 -0
  68. package/dist/utils/dnd/hooks/use_draggable.js.map +1 -0
  69. package/dist/utils/dnd/types.d.ts +10 -0
  70. package/dist/utils/dnd/types.d.ts.map +1 -0
  71. package/dist/utils/dnd/types.js +2 -0
  72. package/dist/utils/dnd/types.js.map +1 -0
  73. package/dist/utils/index.d.ts +1 -1
  74. package/dist/utils/index.d.ts.map +1 -1
  75. package/dist/utils/index.js +1 -1
  76. package/package.json +9 -3
  77. package/src/inputs/phone_number_input/phone_number_input.tsx +8 -0
  78. package/src/inputs/textarea/textarea.tsx +2 -2
  79. package/src/layouts/header/header.tsx +0 -1
  80. package/src/overlay/frame/frame.stories.tsx +40 -0
  81. package/src/overlay/frame/frame.tsx +34 -0
  82. package/src/overlay/frame/frame_stories.module.css +14 -0
  83. package/src/overlay/index.ts +1 -0
  84. package/src/stacks/box/box.tsx +8 -2
  85. package/src/surfaces/index.ts +2 -2
  86. package/src/surfaces/modal/__stories__/modal.stories.tsx +19 -27
  87. package/src/surfaces/modal/modal.tsx +13 -10
  88. package/src/surfaces/window/window.stories.tsx +37 -4
  89. package/src/surfaces/window/window.tsx +14 -6
  90. package/src/themes/themes/ergo/ergo_theme.css +69 -0
  91. package/src/typography/title/title.tsx +22 -18
  92. package/src/utils/dnd/__stories__/draggable.stories.tsx +48 -0
  93. package/src/utils/dnd/__stories__/draggable_stories.module.css +21 -0
  94. package/src/utils/{__stories__ → dnd/__stories__}/use_draggable.stories.tsx +15 -10
  95. package/src/utils/dnd/context.ts +24 -0
  96. package/src/utils/dnd/draggable/draggable.module.css +8 -0
  97. package/src/utils/dnd/draggable/draggable.tsx +42 -0
  98. package/src/utils/dnd/handle.tsx +32 -0
  99. package/src/utils/dnd/hooks/use_drag_container.ts +42 -0
  100. package/src/utils/{hooks → dnd/hooks}/use_draggable.ts +23 -17
  101. package/src/utils/dnd/types.ts +6 -0
  102. package/src/utils/index.ts +1 -1
  103. package/dist/title.module-B16de2jd.js +0 -5
  104. package/dist/title.module-B16de2jd.js.map +0 -1
  105. package/dist/utils/hooks/use_draggable.d.ts.map +0 -1
  106. package/dist/utils/hooks/use_draggable.js +0 -30
  107. package/dist/utils/hooks/use_draggable.js.map +0 -1
@@ -118,6 +118,8 @@ function getCountryCodeFromValue(
118
118
  export interface PhoneNumberInputProps
119
119
  extends Omit<HStackProps, 'onChange' | 'children'> {
120
120
  value?: string;
121
+ name?: string;
122
+ autoComplete?: string;
121
123
  defaultCountry?: string;
122
124
  /**
123
125
  * Callback fired when the phone number value changes.
@@ -135,6 +137,8 @@ export interface PhoneNumberInputProps
135
137
  export const PhoneNumberInput = React.forwardRef(function PhoneNumberInput(
136
138
  {
137
139
  value = '',
140
+ name,
141
+ autoComplete,
138
142
  defaultCountry = 'US',
139
143
  onChange,
140
144
  countrySelectRef: countryRef,
@@ -345,6 +349,8 @@ export const PhoneNumberInput = React.forwardRef(function PhoneNumberInput(
345
349
  {obfuscateValue ? (
346
350
  <MaskInput
347
351
  key="obfuscated"
352
+ name={name}
353
+ autoComplete={autoComplete}
348
354
  ref={forkedInputRef}
349
355
  value=""
350
356
  mask={createObfuscatedMasks(currentMasks)}
@@ -365,6 +371,8 @@ export const PhoneNumberInput = React.forwardRef(function PhoneNumberInput(
365
371
  ) : (
366
372
  <MaskInput
367
373
  key="normal"
374
+ name={name}
375
+ autoComplete={autoComplete}
368
376
  ref={forkedInputRef}
369
377
  value={phoneNumber}
370
378
  mask={currentMasks}
@@ -1,10 +1,10 @@
1
1
  import { clsx } from 'clsx';
2
2
  import React from 'react';
3
- import { HTMLAttributes } from 'react';
3
+ import { TextareaHTMLAttributes } from 'react';
4
4
  import styles from './textarea.module.css';
5
5
 
6
6
  export interface TextareaProps
7
- extends Omit<HTMLAttributes<HTMLTextAreaElement>, 'onChange'> {
7
+ extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChange'> {
8
8
  value?: string;
9
9
  width?: string;
10
10
  height?: string;
@@ -4,7 +4,6 @@ import { HStack, type HStackProps } from '../../stacks/h_stack.js';
4
4
  import type { Hierarchy, Size } from '../../utils/index.js';
5
5
  import styles from './header.module.css';
6
6
 
7
- // UtilityBar
8
7
  export interface HeaderProps extends Omit<HStackProps, 'as'> {
9
8
  hierarchy?: Hierarchy;
10
9
  size?: Size;
@@ -0,0 +1,40 @@
1
+ import { ZStack } from '../../stacks/z_stack.js';
2
+ import { Frame, type FrameOwnProps } from './frame.js';
3
+ import { DragHandle } from '../../utils/dnd/handle.js';
4
+ import { Title } from '../../typography/title/title.js';
5
+ import { BodyText } from '../../typography/index.js';
6
+ import { Header } from '../../layouts/index.js';
7
+ import styles from './frame_stories.module.css';
8
+ export default {
9
+ title: 'Overlay/Floating/Frame',
10
+ component: Frame,
11
+ tags: ['autodocs'],
12
+
13
+ args: {
14
+ isOpen: true,
15
+ draggable: true,
16
+ veil: false,
17
+ },
18
+ };
19
+
20
+ export const FrameStory = (args: Omit<FrameOwnProps, 'children'>) => {
21
+ return (
22
+ <ZStack height="100%" width="100%" minHeight="600px">
23
+ <Frame
24
+ width="300px"
25
+ height="300px"
26
+ className={styles['sb-frame-container']}
27
+ {...args}
28
+ >
29
+ <Header className={styles['sb-frame-header']}>
30
+ <Title> This is a frame</Title>
31
+ </Header>
32
+ <DragHandle>
33
+ <Header className={styles['sb-frame-header']}>
34
+ <BodyText> You can drag here.</BodyText>
35
+ </Header>
36
+ </DragHandle>
37
+ </Frame>
38
+ </ZStack>
39
+ );
40
+ };
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import { ZStack, type ZStackProps } from '../../stacks/index.js';
3
+ import { Portal } from '../portal/portal.js';
4
+ import { Draggable } from '../../utils/dnd/draggable/draggable.js';
5
+
6
+ export interface FrameOwnProps {
7
+ isOpen?: boolean;
8
+ children?: React.ReactNode;
9
+ draggable?: boolean;
10
+ veil?: boolean;
11
+ }
12
+
13
+ export type FrameProps = ZStackProps & FrameOwnProps;
14
+
15
+ export const Frame = React.forwardRef<HTMLDialogElement, FrameProps>(function Frame(
16
+ { children, isOpen = false, draggable = true, veil = false, ...rest }: FrameProps,
17
+ ref
18
+ ) {
19
+ if (!isOpen) {
20
+ return null;
21
+ }
22
+
23
+ return (
24
+ <Portal>
25
+ <ZStack width="100%" height="100%" data-is-veil={veil} className="tcn-frame">
26
+ <Draggable draggable={draggable}>
27
+ <ZStack ref={ref} {...rest}>
28
+ {children}
29
+ </ZStack>
30
+ </Draggable>
31
+ </ZStack>
32
+ </Portal>
33
+ );
34
+ });
@@ -0,0 +1,14 @@
1
+ .sb-frame-header {
2
+ background: var(--accent-color);
3
+ color: white;
4
+ font-weight: bold;
5
+ border: 2px solid black;
6
+ padding: var(--padding-medium);
7
+ border-radius: var(--shape-radius-medium);
8
+ }
9
+
10
+ .sb-frame-container {
11
+ background: #f7e9fe;
12
+ padding: var(--padding-medium);
13
+ border-radius: var(--shape-radius-large);
14
+ }
@@ -3,3 +3,4 @@ export * from './context_menu/context_menu.js';
3
3
  export * from './menu/menu.js';
4
4
  export * from './tooltip/tooltip.js';
5
5
  export * from './popper/popper.js';
6
+ export { Frame, type FrameOwnProps, type FrameProps } from './frame/frame.js';
@@ -45,8 +45,14 @@ export interface BoxProps extends HTMLAttributes<HTMLElement> {
45
45
  enableResizeOnRight?: boolean;
46
46
  horizontalHandleProps?: HandleProps;
47
47
  verticalHandleProps?: HandleProps;
48
- onWidthResize?: (width: number) => void;
49
- onHeightResize?: (width: number) => void;
48
+ onWidthResize?: (
49
+ width: number
50
+ // origin: 'left' | 'right'
51
+ ) => void;
52
+ onHeightResize?: (
53
+ height: number
54
+ // origin: 'top' | 'bottom'
55
+ ) => void;
50
56
  onWidthResizeEnd?: (width: number) => void;
51
57
  onHeightResizeEnd?: (width: number) => void;
52
58
  }
@@ -1,14 +1,14 @@
1
1
  export * from './alert/alert.js';
2
2
  export * from './card/card.js';
3
3
  export * from './confirm/confirm.js';
4
- export * from './modal/modal.js';
5
4
  export * from './page/h_page.js';
6
5
  export * from './page/v_page.js';
7
6
  export * from './panel/h_panel.js';
8
7
  export * from './panel/v_panel.js';
9
8
  export * from './popover/popover.js';
10
- export * from './window/window.js';
11
9
  export * from './drawers/drawer_bottom/drawer_bottom.js';
12
10
  export * from './drawers/drawer_top/drawer_top.js';
13
11
  export * from './drawers/drawer_start/drawer_start.js';
14
12
  export * from './drawers/drawer_end/drawer_end.js';
13
+ export { Window, type WindowProps } from './window/window.js';
14
+ export { Modal, type ModalProps } from './modal/modal.js';
@@ -1,11 +1,9 @@
1
1
  import { useState } from 'react';
2
2
  import { Button, SlimButton } from '../../../actions/index.js';
3
3
  import { Footer, Header, VBody } from '../../../layouts/index.js';
4
- import { Portal } from '../../../overlay/index.js';
5
4
  import { ZStack } from '../../../stacks/z_stack.js';
6
5
  import { BodyText, Title } from '../../../typography/index.js';
7
6
  import { Modal } from '../modal.js';
8
- import { ClickAwayListener } from '../../../utils/index.js';
9
7
  import { Spacer } from '../../../stacks/index.js';
10
8
  import { CrossIcon } from '@tcn/icons/cross_icon.js';
11
9
 
@@ -21,34 +19,28 @@ export const ModalStory = () => {
21
19
  function toggle() {
22
20
  setIsOpen(!isOpen);
23
21
  }
22
+
24
23
  return (
25
24
  <ZStack height="100%" width="100%" minHeight="600px">
26
25
  <button onClick={toggle}>{isOpen ? 'Close' : 'Open'}</button>
27
- {isOpen && (
28
- <Portal>
29
- <ZStack width="100%" height="100%" className="tcn-veil">
30
- <ClickAwayListener onClickAway={toggle}>
31
- <Modal width="400px" height="500px">
32
- <Header>
33
- <Title>Modal Title</Title>
34
- <Spacer />
35
- <SlimButton hierarchy="tertiary" size="md" onClick={toggle}>
36
- <CrossIcon />
37
- </SlimButton>
38
- </Header>
39
- <VBody>
40
- <BodyText>This is a modal</BodyText>
41
- </VBody>
42
- <Footer>
43
- <Spacer />
44
- <Button hierarchy="secondary">Cancel</Button>
45
- <Button hierarchy="primary">Save</Button>
46
- </Footer>
47
- </Modal>
48
- </ClickAwayListener>
49
- </ZStack>
50
- </Portal>
51
- )}
26
+
27
+ <Modal isOpen={isOpen} width="400px" height="500px">
28
+ <Header>
29
+ <Title>Modal Title</Title>
30
+ <Spacer />
31
+ <SlimButton hierarchy="tertiary" size="md" onClick={toggle}>
32
+ <CrossIcon />
33
+ </SlimButton>
34
+ </Header>
35
+ <VBody>
36
+ <BodyText>This is a modal</BodyText>
37
+ </VBody>
38
+ <Footer>
39
+ <Spacer />
40
+ <Button hierarchy="secondary">Cancel</Button>
41
+ <Button hierarchy="primary">Save</Button>
42
+ </Footer>
43
+ </Modal>
52
44
  </ZStack>
53
45
  );
54
46
  };
@@ -2,21 +2,24 @@ import { VStack, type VStackProps } from '../../stacks/v_stack.js';
2
2
  import { clsx } from 'clsx';
3
3
  import React from 'react';
4
4
  import styles from './modal.module.css';
5
+ import { Frame, type FrameOwnProps } from '../../overlay/frame/frame.js';
5
6
 
6
- export type ModalProps = Omit<VStackProps<HTMLDialogElement>, 'as'>;
7
+ export type ModalProps = FrameOwnProps & Omit<VStackProps<HTMLDialogElement>, 'as'>;
7
8
 
8
9
  export const Modal = React.forwardRef<HTMLDialogElement, ModalProps>(function Modal(
9
- { children, className, ...props }: ModalProps,
10
+ { children, className, isOpen, draggable = false, veil = true, ...props }: ModalProps,
10
11
  ref
11
12
  ) {
12
13
  return (
13
- <VStack
14
- ref={ref}
15
- className={clsx(styles['modal'], 'tcn-modal', className)}
16
- as="dialog"
17
- {...props}
18
- >
19
- {children}
20
- </VStack>
14
+ <Frame isOpen={isOpen} draggable={draggable} veil={veil}>
15
+ <VStack
16
+ ref={ref}
17
+ className={clsx(styles['modal'], 'tcn-modal', className)}
18
+ as="dialog"
19
+ {...props}
20
+ >
21
+ {children}
22
+ </VStack>
23
+ </Frame>
21
24
  );
22
25
  });
@@ -1,4 +1,12 @@
1
+ import { useState } from 'react';
1
2
  import { Window } from './window.js';
3
+ import { Footer, Header, VBody } from '../../layouts/index.js';
4
+ import { BodyText, Title } from '../../typography/index.js';
5
+ import { Spacer } from '../../stacks/spacer.js';
6
+ import { Button, SlimButton } from '../../actions/index.js';
7
+ import { CrossIcon } from '@tcn/icons/cross_icon.js';
8
+ import { ZStack } from '../../stacks/z_stack.js';
9
+ import { DragHandle } from '../../utils/dnd/handle.js';
2
10
 
3
11
  export default {
4
12
  title: 'Surfaces/Window',
@@ -6,10 +14,35 @@ export default {
6
14
  tags: ['autodocs'],
7
15
  };
8
16
 
9
- export const ModalStory = () => {
17
+ export const WindowStory = () => {
18
+ const [isOpen, setIsOpen] = useState(false);
19
+
20
+ function toggle() {
21
+ setIsOpen(!isOpen);
22
+ }
23
+
10
24
  return (
11
- <Window>
12
- <>window</>
13
- </Window>
25
+ <ZStack height="100%" width="100%" minHeight="600px">
26
+ <button onClick={toggle}>{isOpen ? 'Close' : 'Open'}</button>
27
+ <Window isOpen={isOpen} width="400px" height="500px">
28
+ <DragHandle>
29
+ <Header>
30
+ <Title>Window Title</Title>
31
+ <Spacer />
32
+ <SlimButton hierarchy="tertiary" size="md" onClick={toggle}>
33
+ <CrossIcon />
34
+ </SlimButton>
35
+ </Header>
36
+ </DragHandle>
37
+ <VBody>
38
+ <BodyText>This is a window</BodyText>
39
+ </VBody>
40
+ <Footer>
41
+ <Spacer />
42
+ <Button hierarchy="secondary">Cancel</Button>
43
+ <Button hierarchy="primary">Save</Button>
44
+ </Footer>
45
+ </Window>
46
+ </ZStack>
14
47
  );
15
48
  };
@@ -2,16 +2,24 @@ import { VStack, type VStackProps } from '../../stacks/v_stack.js';
2
2
  import { clsx } from 'clsx';
3
3
  import React from 'react';
4
4
  import styles from './window.module.css';
5
+ import { Frame, type FrameOwnProps } from '../../overlay/frame/frame.js';
5
6
 
6
- export type WindowProps = Omit<VStackProps, 'as'>;
7
+ export type WindowProps = FrameOwnProps & Omit<VStackProps<HTMLDialogElement>, 'as'>;
7
8
 
8
- export const Window = React.forwardRef<HTMLDivElement, WindowProps>(function Window(
9
- { children, className, ...props }: WindowProps,
9
+ export const Window = React.forwardRef<HTMLDialogElement, WindowProps>(function Window(
10
+ { children, className, isOpen, draggable = true, veil = false, ...props }: WindowProps,
10
11
  ref
11
12
  ) {
12
13
  return (
13
- <VStack ref={ref} className={clsx(styles.window, className, 'window')} {...props}>
14
- {children}
15
- </VStack>
14
+ <Frame isOpen={isOpen} draggable={draggable} veil={veil}>
15
+ <VStack
16
+ ref={ref}
17
+ className={clsx(styles['window'], 'tcn-window', className)}
18
+ as="dialog"
19
+ {...props}
20
+ >
21
+ {children}
22
+ </VStack>
23
+ </Frame>
16
24
  );
17
25
  });
@@ -445,6 +445,16 @@ legend {
445
445
  }
446
446
 
447
447
  /* ===== SURFACES ===== */
448
+ .tcn-draggable[data-is-draggable="true"] {
449
+ .tcn-drag-handle {
450
+ cursor: move;
451
+ }
452
+ }
453
+
454
+ .tcn-frame[data-is-veil="true"] {
455
+ background-color: rgba(0, 0, 0, 0.5);
456
+ }
457
+
448
458
  .tcn-list {
449
459
  gap: var(--gap-medium);
450
460
  .tcn-item {
@@ -518,6 +528,65 @@ legend {
518
528
  }
519
529
  }
520
530
 
531
+ /* WINDOW: */
532
+ .tcn-window {
533
+ --v-inset: var(--padding-large);
534
+ background-color: var(--background-color-primary);
535
+ border-radius: var(--shape-radius-medium);
536
+ /* TODO: This should be a variable */
537
+ border: 1px solid rgba(170, 170, 170, 1);
538
+ overflow: hidden;
539
+
540
+ :where(.tcn-typography) {
541
+ color: inherit;
542
+ }
543
+
544
+ :where(.tcn-header) {
545
+ --material: var(--material-secondary-dark);
546
+ --on-material: 0, 0%, 100%;
547
+ --action: var(--material-tan);
548
+ --on-action: 0, 0%, 100%;
549
+ background-color: hsl(var(--material));
550
+ color: hsl(var(--on-material));
551
+ min-height: 40px;
552
+ padding: 0 var(--v-inset);
553
+ gap: var(--gap-medium);
554
+
555
+ :where(.tcn-divider) {
556
+ padding: 4px 0;
557
+ :where(.tcn-divider-line) {
558
+ width: 1.5px;
559
+ min-height: 18px;
560
+ height: auto;
561
+ }
562
+ }
563
+ }
564
+
565
+ :where(.tcn-utility-bar) {
566
+ min-height: 32px;
567
+ border-bottom: 1px solid var(--foreground-color-primary);
568
+ padding: 0 var(--v-inset);
569
+
570
+ :where(.tcn-button) {
571
+ padding: 0;
572
+ min-width: 18px;
573
+ min-height: 18px;
574
+ }
575
+ }
576
+
577
+ :where(.tcn-body) {
578
+ padding: 0 var(--v-inset);
579
+ gap: var(--gap-medium);
580
+ }
581
+
582
+ :where(.tcn-footer) {
583
+ gap: var(--gap-medium);
584
+ min-height: 40px;
585
+ border-top: 1px solid var(--foreground-color-primary);
586
+ padding: 0 var(--v-inset);
587
+ }
588
+ }
589
+
521
590
  /* PANEL */
522
591
  .tcn-panel {
523
592
  --v-inset: var(--padding-large);
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { forwardRef } from 'react';
2
2
  import { clsx } from 'clsx';
3
3
  import type { WithDetailedHTMLProps } from '../../stacks/types/as.js';
4
4
  import type { Emphasis, Hierarchy, Size } from '../../utils/index.js';
@@ -21,22 +21,25 @@ export interface TitleOwnProps {
21
21
 
22
22
  export type TitleProps = WithDetailedHTMLProps<TitleOwnProps, 'h1' | 'h2' | 'h3'>;
23
23
 
24
- export function Title({
25
- size = 'md',
26
- emphasis = 'normal',
27
- hierarchy = 'primary',
28
- color,
29
- children,
30
- className,
31
- style = {},
32
- padStart,
33
- padEnd,
34
- padBottom,
35
- padTop,
36
- pad,
37
- selectable = true,
38
- as,
39
- }: TitleProps) {
24
+ export const Title = forwardRef<HTMLHeadingElement, TitleProps>(function Title(
25
+ {
26
+ size = 'md',
27
+ emphasis = 'normal',
28
+ hierarchy = 'primary',
29
+ color,
30
+ children,
31
+ className,
32
+ style = {},
33
+ padStart,
34
+ padEnd,
35
+ padBottom,
36
+ padTop,
37
+ pad,
38
+ selectable = true,
39
+ as,
40
+ },
41
+ ref
42
+ ) {
40
43
  let As: React.ElementType = as as React.ElementType;
41
44
 
42
45
  if (as == null) {
@@ -75,6 +78,7 @@ export function Title({
75
78
 
76
79
  return (
77
80
  <As
81
+ ref={ref}
78
82
  data-hierarchy={hierarchy}
79
83
  data-emphasis={emphasis}
80
84
  data-selectable={selectable}
@@ -85,4 +89,4 @@ export function Title({
85
89
  {children}
86
90
  </As>
87
91
  );
88
- }
92
+ });
@@ -0,0 +1,48 @@
1
+ import { Draggable } from '../draggable/draggable.js';
2
+ import { VStack } from '../../../stacks/v_stack.js';
3
+ import { DragHandle } from '../handle.js';
4
+ import { Box } from '../../../stacks/box/box.js';
5
+ import { BodyText } from '../../../typography/index.js';
6
+ import styles from './draggable_stories.module.css';
7
+
8
+ export default {
9
+ title: 'Utils/Draggable',
10
+ component: Draggable,
11
+ tags: ['autodocs'],
12
+ };
13
+
14
+ export const DraggableStory = () => {
15
+ return (
16
+ <VStack minHeight="600px" height="100%" width="100%">
17
+ <Draggable>
18
+ <Box width="400px" height="300px" className={styles['handle-container']}>
19
+ <DragHandle>
20
+ <Box
21
+ className={styles.handle}
22
+ width="100px"
23
+ style={{
24
+ top: 50,
25
+ left: 100,
26
+ }}
27
+ >
28
+ <BodyText>Drag Handle</BodyText>
29
+ </Box>
30
+ </DragHandle>
31
+
32
+ <DragHandle>
33
+ <Box
34
+ className={styles.handle}
35
+ width="150px"
36
+ style={{
37
+ top: 200,
38
+ left: 50,
39
+ }}
40
+ >
41
+ <BodyText>Another Drag Handle</BodyText>
42
+ </Box>
43
+ </DragHandle>
44
+ </Box>
45
+ </Draggable>
46
+ </VStack>
47
+ );
48
+ };
@@ -0,0 +1,21 @@
1
+ .handle {
2
+ position: relative;
3
+ background: var(--accent-color);
4
+ color: white;
5
+ font-weight: bold;
6
+ border: 2px solid black;
7
+ padding: var(--padding-medium);
8
+ border-radius: var(--shape-radius-medium);
9
+ }
10
+
11
+ .handle-container {
12
+ background: #f7e9fe;
13
+ padding: var(--padding-medium);
14
+ border-radius: var(--shape-radius-large);
15
+ }
16
+
17
+ .handle-container[data-is-dragging="true"] {
18
+ box-shadow:
19
+ 0 4px 16px 0 rgba(25, 118, 210, 0.2),
20
+ 0 1.5px 4px 0 rgba(21, 101, 192, 0.15);
21
+ }
@@ -1,9 +1,9 @@
1
1
  import React, { useState, useRef } from 'react';
2
2
  import { useDraggable } from '../hooks/use_draggable';
3
- import { Box } from '../../stacks/box/box.js';
3
+ import { Box } from '../../../stacks/box/box.js';
4
4
 
5
5
  export default {
6
- title: 'Hooks/useDraggable',
6
+ title: 'Utils/useDraggable',
7
7
  component: <></>,
8
8
  args: {
9
9
  startX: 200,
@@ -16,8 +16,9 @@ export const BasicDraggable = {
16
16
  const [position, setPosition] = useState({ x: args.startX, y: args.startY });
17
17
  const [isDragging, setIsDragging] = useState(false);
18
18
  const elementStartPosition = useRef({ x: args.startX, y: args.startY });
19
-
20
- const dragRef = useDraggable({
19
+ const dragRef = useRef<HTMLDivElement>(null);
20
+ useDraggable({
21
+ handles: dragRef,
21
22
  startDragCallback: () => {
22
23
  setIsDragging(true);
23
24
  elementStartPosition.current = { x: position.x, y: position.y };
@@ -76,8 +77,10 @@ export const MultipleDraggables = {
76
77
 
77
78
  const createDragHandlers = (
78
79
  id: number,
79
- elementStartPosition: React.RefObject<{ x: number; y: number }>
80
+ elementStartPosition: React.RefObject<{ x: number; y: number }>,
81
+ handleRef: React.RefObject<HTMLDivElement>
80
82
  ) => ({
83
+ handles: handleRef,
81
84
  startDragCallback: () => {
82
85
  const box = boxes.find(b => b.id === id);
83
86
  if (box && elementStartPosition.current) {
@@ -120,11 +123,12 @@ export const MultipleDraggables = {
120
123
  </p>
121
124
  {boxes.map(box => {
122
125
  const elementStartPosition = useRef({ x: box.x, y: box.y });
123
- const dragRef = useDraggable(createDragHandlers(box.id, elementStartPosition));
126
+ const handleRef = useRef<HTMLDivElement>(null);
127
+ useDraggable(createDragHandlers(box.id, elementStartPosition, handleRef));
124
128
  return (
125
129
  <Box
126
130
  key={box.id}
127
- ref={dragRef as React.RefObject<HTMLDivElement>}
131
+ ref={handleRef}
128
132
  style={{
129
133
  width: '90px',
130
134
  height: '100px',
@@ -155,8 +159,9 @@ export const CustomDragHandle = {
155
159
  const [position, setPosition] = useState({ x: args.startX, y: args.startY });
156
160
  const [isDragging, setIsDragging] = useState(false);
157
161
  const elementStartPosition = useRef({ x: args.startX, y: args.startY });
158
-
159
- const dragRef = useDraggable({
162
+ const handleRef = useRef<HTMLDivElement>(null);
163
+ useDraggable({
164
+ handles: handleRef,
160
165
  startDragCallback: () => {
161
166
  setIsDragging(true);
162
167
  elementStartPosition.current = { x: position.x, y: position.y };
@@ -189,7 +194,7 @@ export const CustomDragHandle = {
189
194
  }}
190
195
  >
191
196
  <Box
192
- ref={dragRef as React.RefObject<HTMLDivElement>}
197
+ ref={handleRef}
193
198
  style={{
194
199
  height: '30px',
195
200
  backgroundColor: '#2196F3',