@tcn/ui 0.9.0 → 0.11.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 (298) hide show
  1. package/dist/bar.css +1 -0
  2. package/dist/body.css +1 -1
  3. package/dist/body.module-BbFZ7KNP.js +5 -0
  4. package/dist/body.module-BbFZ7KNP.js.map +1 -0
  5. package/dist/divider.css +1 -1
  6. package/dist/footer.css +1 -1
  7. package/dist/form/field/h_field/h_field.d.ts.map +1 -1
  8. package/dist/form/field/h_field/h_field.js +33 -35
  9. package/dist/form/field/h_field/h_field.js.map +1 -1
  10. package/dist/form/field/v_field/v_field.d.ts.map +1 -1
  11. package/dist/form/field/v_field/v_field.js +34 -36
  12. package/dist/form/field/v_field/v_field.js.map +1 -1
  13. package/dist/frame.css +1 -1
  14. package/dist/inputs/color_input/color_input.d.ts.map +1 -1
  15. package/dist/inputs/color_input/color_input.js +47 -46
  16. package/dist/inputs/color_input/color_input.js.map +1 -1
  17. package/dist/inputs/combo_box/combo_box.d.ts.map +1 -1
  18. package/dist/inputs/combo_box/combo_box.js +61 -58
  19. package/dist/inputs/combo_box/combo_box.js.map +1 -1
  20. package/dist/inputs/index.d.ts +1 -0
  21. package/dist/inputs/index.d.ts.map +1 -1
  22. package/dist/inputs/index.js +34 -31
  23. package/dist/inputs/index.js.map +1 -1
  24. package/dist/inputs/input/input.js +9 -9
  25. package/dist/inputs/input/input.js.map +1 -1
  26. package/dist/inputs/input_group/input_group.d.ts +5 -0
  27. package/dist/inputs/input_group/input_group.d.ts.map +1 -0
  28. package/dist/inputs/input_group/input_group.js +20 -0
  29. package/dist/inputs/input_group/input_group.js.map +1 -0
  30. package/dist/inputs/phone_number_input/countries_phone_information.d.ts +2 -2
  31. package/dist/inputs/phone_number_input/countries_phone_information.d.ts.map +1 -1
  32. package/dist/inputs/phone_number_input/countries_phone_information.js +5 -353
  33. package/dist/inputs/phone_number_input/countries_phone_information.js.map +1 -1
  34. package/dist/inputs/phone_number_input/phone_number_context.d.ts +24 -0
  35. package/dist/inputs/phone_number_input/phone_number_context.d.ts.map +1 -0
  36. package/dist/inputs/phone_number_input/phone_number_context.js +23 -0
  37. package/dist/inputs/phone_number_input/phone_number_context.js.map +1 -0
  38. package/dist/inputs/phone_number_input/phone_number_country_select_adapter.d.ts +19 -0
  39. package/dist/inputs/phone_number_input/phone_number_country_select_adapter.d.ts.map +1 -0
  40. package/dist/inputs/phone_number_input/phone_number_country_select_adapter.js +77 -0
  41. package/dist/inputs/phone_number_input/phone_number_country_select_adapter.js.map +1 -0
  42. package/dist/inputs/phone_number_input/phone_number_input.d.ts +16 -14
  43. package/dist/inputs/phone_number_input/phone_number_input.d.ts.map +1 -1
  44. package/dist/inputs/phone_number_input/phone_number_input.js +104 -274
  45. package/dist/inputs/phone_number_input/phone_number_input.js.map +1 -1
  46. package/dist/inputs/phone_number_input/phone_number_input_adapter.d.ts +6 -0
  47. package/dist/inputs/phone_number_input/phone_number_input_adapter.d.ts.map +1 -0
  48. package/dist/inputs/phone_number_input/phone_number_input_adapter.js +95 -0
  49. package/dist/inputs/phone_number_input/phone_number_input_adapter.js.map +1 -0
  50. package/dist/inputs/phone_number_input/sip_input.d.ts +12 -0
  51. package/dist/inputs/phone_number_input/sip_input.d.ts.map +1 -0
  52. package/dist/inputs/phone_number_input/sip_input.js +111 -0
  53. package/dist/inputs/phone_number_input/sip_input.js.map +1 -0
  54. package/dist/inputs/select/select.d.ts.map +1 -1
  55. package/dist/inputs/select/select.js +3 -2
  56. package/dist/inputs/select/select.js.map +1 -1
  57. package/dist/inputs/suggestions/suggestion_list.d.ts +4 -1
  58. package/dist/inputs/suggestions/suggestion_list.d.ts.map +1 -1
  59. package/dist/inputs/suggestions/suggestion_list.js +120 -111
  60. package/dist/inputs/suggestions/suggestion_list.js.map +1 -1
  61. package/dist/inputs/textarea/textarea.js +8 -8
  62. package/dist/inputs/textarea/textarea.js.map +1 -1
  63. package/dist/inputs/unit_input/unit_input.d.ts.map +1 -1
  64. package/dist/inputs/unit_input/unit_input.js +39 -39
  65. package/dist/inputs/unit_input/unit_input.js.map +1 -1
  66. package/dist/layouts/bar/bar.d.ts +5 -0
  67. package/dist/layouts/bar/bar.d.ts.map +1 -0
  68. package/dist/layouts/bar/bar.js +20 -0
  69. package/dist/layouts/bar/bar.js.map +1 -0
  70. package/dist/layouts/body/body.d.ts +2 -2
  71. package/dist/layouts/body/body.d.ts.map +1 -1
  72. package/dist/layouts/body/body.js +12 -12
  73. package/dist/layouts/body/body.js.map +1 -1
  74. package/dist/layouts/body/h_body.d.ts.map +1 -1
  75. package/dist/layouts/body/h_body.js +18 -12
  76. package/dist/layouts/body/h_body.js.map +1 -1
  77. package/dist/layouts/body/v_body.d.ts.map +1 -1
  78. package/dist/layouts/body/v_body.js +16 -10
  79. package/dist/layouts/body/v_body.js.map +1 -1
  80. package/dist/layouts/footer/footer.d.ts +2 -3
  81. package/dist/layouts/footer/footer.d.ts.map +1 -1
  82. package/dist/layouts/footer/footer.js +7 -7
  83. package/dist/layouts/footer/footer.js.map +1 -1
  84. package/dist/layouts/header/header.d.ts +2 -2
  85. package/dist/layouts/header/header.d.ts.map +1 -1
  86. package/dist/layouts/header/header.js +13 -21
  87. package/dist/layouts/header/header.js.map +1 -1
  88. package/dist/layouts/index.d.ts +0 -1
  89. package/dist/layouts/index.d.ts.map +1 -1
  90. package/dist/layouts/index.js +17 -19
  91. package/dist/layouts/index.js.map +1 -1
  92. package/dist/layouts/rail/rail.js +41 -41
  93. package/dist/layouts/rail/rail.js.map +1 -1
  94. package/dist/layouts/rail/side/side.d.ts.map +1 -1
  95. package/dist/layouts/rail/side/side.js +1 -1
  96. package/dist/layouts/rail/side/side.js.map +1 -1
  97. package/dist/layouts/rail/utility_strip/utility_strip.d.ts.map +1 -1
  98. package/dist/layouts/rail/utility_strip/utility_strip.js +21 -17
  99. package/dist/layouts/rail/utility_strip/utility_strip.js.map +1 -1
  100. package/dist/layouts/scaffold/scaffold.js +32 -32
  101. package/dist/layouts/scaffold/scaffold.js.map +1 -1
  102. package/dist/layouts/utility_bar/utility_bar.d.ts +2 -2
  103. package/dist/layouts/utility_bar/utility_bar.d.ts.map +1 -1
  104. package/dist/layouts/utility_bar/utility_bar.js +17 -19
  105. package/dist/layouts/utility_bar/utility_bar.js.map +1 -1
  106. package/dist/overlay/frame/frame.d.ts +8 -4
  107. package/dist/overlay/frame/frame.d.ts.map +1 -1
  108. package/dist/overlay/frame/frame.js +87 -23
  109. package/dist/overlay/frame/frame.js.map +1 -1
  110. package/dist/overlay/popper/base/dismissal_decorator.js.map +1 -1
  111. package/dist/overlay/popper/legacy/popper.d.ts.map +1 -1
  112. package/dist/overlay/popper/legacy/popper.js +52 -50
  113. package/dist/overlay/popper/legacy/popper.js.map +1 -1
  114. package/dist/panel.css +1 -0
  115. package/dist/phone_number_input.css +1 -1
  116. package/dist/rail.css +1 -1
  117. package/dist/scaffold.css +1 -1
  118. package/dist/side.css +1 -1
  119. package/dist/stacks/box/bottom_resize_handle.d.ts +2 -2
  120. package/dist/stacks/box/bottom_resize_handle.d.ts.map +1 -1
  121. package/dist/stacks/box/bottom_resize_handle.js +12 -10
  122. package/dist/stacks/box/bottom_resize_handle.js.map +1 -1
  123. package/dist/stacks/box/box.d.ts +4 -4
  124. package/dist/stacks/box/box.d.ts.map +1 -1
  125. package/dist/stacks/box/box.js +26 -26
  126. package/dist/stacks/box/box.js.map +1 -1
  127. package/dist/stacks/box/end_resize_handle.d.ts +2 -2
  128. package/dist/stacks/box/end_resize_handle.d.ts.map +1 -1
  129. package/dist/stacks/box/end_resize_handle.js +6 -5
  130. package/dist/stacks/box/end_resize_handle.js.map +1 -1
  131. package/dist/stacks/box/left_resize_handle.d.ts +2 -2
  132. package/dist/stacks/box/left_resize_handle.d.ts.map +1 -1
  133. package/dist/stacks/box/left_resize_handle.js +10 -9
  134. package/dist/stacks/box/left_resize_handle.js.map +1 -1
  135. package/dist/stacks/box/resize_handlers.d.ts +2 -2
  136. package/dist/stacks/box/resize_handlers.d.ts.map +1 -1
  137. package/dist/stacks/box/resize_handlers.js +32 -32
  138. package/dist/stacks/box/resize_handlers.js.map +1 -1
  139. package/dist/stacks/box/right_resize_handle.d.ts +2 -2
  140. package/dist/stacks/box/right_resize_handle.d.ts.map +1 -1
  141. package/dist/stacks/box/right_resize_handle.js +6 -5
  142. package/dist/stacks/box/right_resize_handle.js.map +1 -1
  143. package/dist/stacks/box/start_resize_handle.d.ts +2 -2
  144. package/dist/stacks/box/start_resize_handle.d.ts.map +1 -1
  145. package/dist/stacks/box/start_resize_handle.js +6 -6
  146. package/dist/stacks/box/start_resize_handle.js.map +1 -1
  147. package/dist/stacks/box/top_resize_handle.d.ts +2 -2
  148. package/dist/stacks/box/top_resize_handle.d.ts.map +1 -1
  149. package/dist/stacks/box/top_resize_handle.js +6 -5
  150. package/dist/stacks/box/top_resize_handle.js.map +1 -1
  151. package/dist/stacks/h_collapsible_box.d.ts.map +1 -1
  152. package/dist/stacks/h_collapsible_box.js +25 -25
  153. package/dist/stacks/h_collapsible_box.js.map +1 -1
  154. package/dist/stacks/v_collapsible_box.js +25 -25
  155. package/dist/stacks/v_collapsible_box.js.map +1 -1
  156. package/dist/suggestion_list.css +1 -1
  157. package/dist/surfaces/index.d.ts +1 -2
  158. package/dist/surfaces/index.d.ts.map +1 -1
  159. package/dist/surfaces/index.js +18 -20
  160. package/dist/surfaces/index.js.map +1 -1
  161. package/dist/surfaces/panel/panel.d.ts +5 -0
  162. package/dist/surfaces/panel/panel.d.ts.map +1 -0
  163. package/dist/surfaces/panel/panel.js +19 -0
  164. package/dist/surfaces/panel/panel.js.map +1 -0
  165. package/dist/surfaces/pop_confirm/pop_confirm.js +2 -3
  166. package/dist/surfaces/pop_confirm/pop_confirm.js.map +1 -1
  167. package/dist/surfaces/window/window.d.ts +1 -1
  168. package/dist/surfaces/window/window.d.ts.map +1 -1
  169. package/dist/surfaces/window/window.js +20 -10
  170. package/dist/surfaces/window/window.js.map +1 -1
  171. package/dist/themes/stylesheets/reset.css +1 -1
  172. package/dist/themes/stylesheets/reset.js +8 -1
  173. package/dist/themes/stylesheets/reset.js.map +1 -1
  174. package/dist/themes/theme.d.ts +2 -1
  175. package/dist/themes/theme.d.ts.map +1 -1
  176. package/dist/themes/theme.js +16 -9
  177. package/dist/themes/theme.js.map +1 -1
  178. package/dist/themes/themes/ergo/ergo_theme.css +1 -1
  179. package/dist/themes/themes/ergo/ergo_theme.js +210 -18
  180. package/dist/themes/themes/ergo/ergo_theme.js.map +1 -1
  181. package/dist/typography/body_text/body_text.d.ts.map +1 -1
  182. package/dist/typography/body_text/body_text.js +12 -10
  183. package/dist/typography/body_text/body_text.js.map +1 -1
  184. package/dist/utils/dnd/hooks/use_drag_container.d.ts.map +1 -1
  185. package/dist/utils/dnd/hooks/use_drag_container.js +22 -19
  186. package/dist/utils/dnd/hooks/use_drag_container.js.map +1 -1
  187. package/package.json +4 -2
  188. package/src/form/field/h_field/h_field.tsx +0 -4
  189. package/src/form/field/v_field/v_field.stories.tsx +8 -0
  190. package/src/form/field/v_field/v_field.tsx +1 -4
  191. package/src/form/field_set/field_set.stories.tsx +2 -1
  192. package/src/inputs/__docs__/inputs.mdx +81 -0
  193. package/src/inputs/__docs__/inputs.stories.tsx +268 -0
  194. package/src/inputs/color_input/color_input.tsx +17 -17
  195. package/src/inputs/combo_box/combo_box.tsx +17 -13
  196. package/src/inputs/index.ts +2 -0
  197. package/src/inputs/input/input.tsx +1 -1
  198. package/src/inputs/input_group/input_group.tsx +26 -0
  199. package/src/inputs/phone_number_input/countries_phone_information.ts +6 -353
  200. package/src/inputs/phone_number_input/phone_number_context.tsx +32 -0
  201. package/src/inputs/phone_number_input/phone_number_country_select_adapter.tsx +126 -0
  202. package/src/inputs/phone_number_input/phone_number_input.module.css +5 -63
  203. package/src/inputs/phone_number_input/phone_number_input.stories.tsx +180 -150
  204. package/src/inputs/phone_number_input/phone_number_input.tsx +133 -400
  205. package/src/inputs/phone_number_input/phone_number_input_adapter.tsx +123 -0
  206. package/src/inputs/phone_number_input/sip_input.tsx +147 -0
  207. package/src/inputs/select/select.tsx +13 -14
  208. package/src/inputs/suggestions/suggestion_list.module.css +1 -0
  209. package/src/inputs/suggestions/suggestion_list.stories.tsx +12 -8
  210. package/src/inputs/suggestions/suggestion_list.tsx +24 -3
  211. package/src/inputs/textarea/textarea.tsx +1 -1
  212. package/src/inputs/unit_input/unit_input.tsx +17 -17
  213. package/src/layouts/__stories__/composed.stories.tsx +0 -55
  214. package/src/layouts/__stories__/rail.stories.tsx +78 -0
  215. package/src/layouts/__stories__/scaffold.stories.tsx +90 -0
  216. package/src/layouts/__stories__/utils/content.tsx +27 -0
  217. package/src/layouts/__stories__/utils/layout_theme.css +88 -0
  218. package/src/layouts/__stories__/utils/layout_theme_provider.tsx +11 -0
  219. package/src/layouts/__stories__/utils.tsx +6 -6
  220. package/src/layouts/{utility_bar/utility_bar.module.css → bar/bar.module.css} +2 -1
  221. package/src/layouts/bar/bar.tsx +23 -0
  222. package/src/layouts/body/body.module.css +9 -4
  223. package/src/layouts/body/body.tsx +7 -6
  224. package/src/layouts/body/h_body.module.css +1 -2
  225. package/src/layouts/body/h_body.tsx +9 -4
  226. package/src/layouts/body/v_body.tsx +9 -4
  227. package/src/layouts/divider/divider.module.css +1 -1
  228. package/src/layouts/footer/footer.module.css +0 -3
  229. package/src/layouts/footer/footer.tsx +5 -6
  230. package/src/layouts/header/header.tsx +6 -15
  231. package/src/layouts/index.ts +0 -1
  232. package/src/layouts/rail/rail.module.css +9 -5
  233. package/src/layouts/rail/rail.tsx +1 -1
  234. package/src/layouts/rail/side/side.module.css +0 -1
  235. package/src/layouts/rail/side/side.tsx +1 -2
  236. package/src/layouts/rail/utility_strip/utility_strip.module.css +5 -0
  237. package/src/layouts/rail/utility_strip/utility_strip.tsx +4 -1
  238. package/src/layouts/scaffold/scaffold.module.css +10 -7
  239. package/src/layouts/scaffold/scaffold.tsx +1 -1
  240. package/src/layouts/utility_bar/utility_bar.tsx +6 -9
  241. package/src/overlay/frame/frame.module.css +2 -4
  242. package/src/overlay/frame/frame.stories.tsx +13 -10
  243. package/src/overlay/frame/frame.tsx +124 -16
  244. package/src/overlay/popper/base/dismissal_decorator.tsx +1 -1
  245. package/src/overlay/popper/legacy/popper.tsx +5 -1
  246. package/src/stacks/box/bottom_resize_handle.tsx +12 -5
  247. package/src/stacks/box/box.tsx +18 -14
  248. package/src/stacks/box/end_resize_handle.tsx +11 -6
  249. package/src/stacks/box/left_resize_handle.tsx +9 -3
  250. package/src/stacks/box/resize_handlers.ts +27 -13
  251. package/src/stacks/box/right_resize_handle.tsx +9 -3
  252. package/src/stacks/box/start_resize_handle.tsx +9 -4
  253. package/src/stacks/box/top_resize_handle.tsx +10 -4
  254. package/src/stacks/collapsible_box.stories.tsx +11 -11
  255. package/src/stacks/h_collapsible_box.tsx +5 -5
  256. package/src/stacks/v_collapsible_box.tsx +4 -4
  257. package/src/surfaces/index.ts +1 -2
  258. package/src/surfaces/panel/__stories__/panel.stories.tsx +12 -12
  259. package/src/surfaces/panel/__stories__/panel_stories.module.css +3 -3
  260. package/src/surfaces/panel/panel.module.css +1 -6
  261. package/src/surfaces/panel/panel.tsx +22 -0
  262. package/src/surfaces/window/window.tsx +14 -4
  263. package/src/themes/stories/controls_fieldset.tsx +1 -1
  264. package/src/themes/stylesheets/reset.css +8 -1
  265. package/src/themes/theme.tsx +6 -2
  266. package/src/themes/themes/ergo/__stories__/material.stories.tsx +15 -16
  267. package/src/themes/themes/ergo/ergo_theme.css +210 -18
  268. package/src/typography/body_text/body_text.tsx +2 -0
  269. package/src/utils/dnd/__stories__/draggable.stories.tsx +14 -8
  270. package/src/utils/dnd/hooks/use_drag_container.ts +13 -3
  271. package/dist/h_body.css +0 -1
  272. package/dist/h_panel.css +0 -1
  273. package/dist/header.css +0 -1
  274. package/dist/layouts/rail/main/main.d.ts +0 -6
  275. package/dist/layouts/rail/main/main.d.ts.map +0 -1
  276. package/dist/layouts/rail/main/main.js +0 -21
  277. package/dist/layouts/rail/main/main.js.map +0 -1
  278. package/dist/main.css +0 -1
  279. package/dist/surfaces/panel/h_panel.d.ts +0 -9
  280. package/dist/surfaces/panel/h_panel.d.ts.map +0 -1
  281. package/dist/surfaces/panel/h_panel.js +0 -60
  282. package/dist/surfaces/panel/h_panel.js.map +0 -1
  283. package/dist/surfaces/panel/v_panel.d.ts +0 -5
  284. package/dist/surfaces/panel/v_panel.d.ts.map +0 -1
  285. package/dist/surfaces/panel/v_panel.js +0 -19
  286. package/dist/surfaces/panel/v_panel.js.map +0 -1
  287. package/dist/utility_bar.css +0 -1
  288. package/dist/v_body.css +0 -1
  289. package/src/inputs/phone_number_input/__tests__/utils.test.ts +0 -52
  290. package/src/layouts/header/header.module.css +0 -8
  291. package/src/layouts/rail/__stories__/rail.stories.tsx +0 -64
  292. package/src/layouts/rail/__stories__/rail_stories.module.css +0 -25
  293. package/src/layouts/rail/main/main.module.css +0 -7
  294. package/src/layouts/rail/main/main.tsx +0 -26
  295. package/src/layouts/scaffold/__stories__/scaffold.stories.tsx +0 -53
  296. package/src/layouts/scaffold/__stories__/scaffold_stories.module.css +0 -31
  297. package/src/surfaces/panel/h_panel.tsx +0 -65
  298. package/src/surfaces/panel/v_panel.tsx +0 -20
@@ -0,0 +1,147 @@
1
+ import React, { useLayoutEffect } from 'react';
2
+ import { NotebookIcon } from '@tcn/icons/notebook_icon.js';
3
+ import { Input } from '../input/input.js';
4
+ import { Button } from '../../actions/index.js';
5
+ import { InputGroup } from '../input_group/input_group.js';
6
+ import clsx from 'clsx';
7
+ import { usePhoneContext } from './phone_number_context.js';
8
+ import type { CountryCode } from 'libphonenumber-js';
9
+ import {
10
+ PhoneNumberCountrySelectAdapter,
11
+ type CountryOption,
12
+ } from './phone_number_country_select_adapter.js';
13
+ import { SuggestionList } from '../suggestions/suggestion_list.js';
14
+
15
+ export interface SipInputProps {
16
+ onChange: (value: string) => void;
17
+ countries?: CountryCode[];
18
+ disabled?: boolean;
19
+ name?: string;
20
+ 'aria-label'?: string;
21
+ autoFocus?: boolean;
22
+ placeholder?: string;
23
+ }
24
+
25
+ export function SipInput({
26
+ disabled,
27
+ countries,
28
+ name,
29
+ 'aria-label': ariaLabel,
30
+ autoFocus,
31
+ placeholder,
32
+ }: SipInputProps) {
33
+ const inputRef = React.useRef<HTMLInputElement>(null);
34
+ const [phoneBookElement, setPhoneBookElement] =
35
+ React.useState<HTMLButtonElement | null>(null);
36
+ const isPhoneBookOpen = phoneBookElement != null;
37
+
38
+ const {
39
+ phoneBook: phoneBookOptions,
40
+ setValue,
41
+ setCountry,
42
+ ariaPhoneBookButtonLabel,
43
+ sipAddress,
44
+ setSipAddress,
45
+ focusNumberInput,
46
+ setFocusNumberInput,
47
+ } = usePhoneContext();
48
+
49
+ const showPhoneBook = phoneBookOptions.length > 0;
50
+
51
+ function togglePhoneBook(e: React.MouseEvent<HTMLButtonElement>) {
52
+ if (isPhoneBookOpen) {
53
+ setPhoneBookElement(null);
54
+ } else {
55
+ setPhoneBookElement(e.currentTarget);
56
+ }
57
+ }
58
+
59
+ function closePhoneBook() {
60
+ setPhoneBookElement(null);
61
+ }
62
+
63
+ function handlePhoneBookOptionSelect(value: string) {
64
+ closePhoneBook();
65
+ setFocusNumberInput(true);
66
+ setValue(value);
67
+ }
68
+
69
+ const options: CountryOption[] =
70
+ countries?.map(country => ({
71
+ value: country,
72
+ label: country,
73
+ })) || [];
74
+
75
+ function selectCountry(countryCode?: string) {
76
+ if (countryCode !== 'SIP') {
77
+ setSipAddress(sipAddress);
78
+ setFocusNumberInput(true);
79
+ setValue('');
80
+ setCountry((countryCode as CountryCode) || 'US');
81
+ }
82
+ }
83
+
84
+ function updateSipValue(value: string) {
85
+ setSipAddress(value);
86
+ setValue(`sip:${value}`);
87
+ }
88
+
89
+ useLayoutEffect(() => {
90
+ const input = inputRef.current;
91
+
92
+ if (input == null || !focusNumberInput) {
93
+ return;
94
+ }
95
+
96
+ requestAnimationFrame(() => {
97
+ if (input.value.length > 0) {
98
+ input.select();
99
+ } else {
100
+ input.focus();
101
+ }
102
+ });
103
+ }, [focusNumberInput]);
104
+
105
+ return (
106
+ <>
107
+ <InputGroup>
108
+ <PhoneNumberCountrySelectAdapter
109
+ value="SIP"
110
+ onChange={selectCountry}
111
+ options={options}
112
+ disabled={disabled}
113
+ />
114
+ <Input
115
+ ref={inputRef}
116
+ value={sipAddress}
117
+ disabled={disabled}
118
+ onChange={updateSipValue}
119
+ name={name}
120
+ aria-label={ariaLabel}
121
+ autoFocus={autoFocus}
122
+ placeholder={placeholder}
123
+ />
124
+ {showPhoneBook && (
125
+ <Button
126
+ disabled={disabled}
127
+ className={clsx('tcn-input-group-slot', 'tcn-phone-number-phone-book')}
128
+ aria-label={ariaPhoneBookButtonLabel}
129
+ onClick={togglePhoneBook}
130
+ size="md"
131
+ >
132
+ <NotebookIcon size="md" />
133
+ </Button>
134
+ )}
135
+ </InputGroup>
136
+ <SuggestionList
137
+ open={isPhoneBookOpen}
138
+ anchorElement={phoneBookElement}
139
+ onOptionSelect={handlePhoneBookOptionSelect}
140
+ onClose={closePhoneBook}
141
+ noSuggestionMessage="No phone numbers found"
142
+ >
143
+ {phoneBookOptions}
144
+ </SuggestionList>
145
+ </>
146
+ );
147
+ }
@@ -130,7 +130,7 @@ export const Select = React.forwardRef(function Select(
130
130
  <>
131
131
  <Button
132
132
  ref={forkedRef}
133
- className={clsx(className, 'tcn-select', styles.select)}
133
+ className={clsx(className, 'tcn-select', 'tcn-control', styles.select)}
134
134
  width="100%"
135
135
  {...props}
136
136
  hAlign="start"
@@ -144,19 +144,18 @@ export const Select = React.forwardRef(function Select(
144
144
  {selectedLabel}
145
145
  </span>
146
146
  </Button>
147
- {isSuggestionsOpen && (
148
- <SuggestionList
149
- anchorElement={anchorElement}
150
- onClose={handleClose}
151
- value={''}
152
- scrollToValue={value || ''}
153
- trimCustomInput={trimCustomInput}
154
- onOptionSelect={handleSelection}
155
- noSuggestionMessage={noOptionMatchedMessage}
156
- >
157
- {children}
158
- </SuggestionList>
159
- )}
147
+ <SuggestionList
148
+ open={isSuggestionsOpen}
149
+ anchorElement={anchorElement}
150
+ onClose={handleClose}
151
+ value={''}
152
+ scrollToValue={value || ''}
153
+ trimCustomInput={trimCustomInput}
154
+ onOptionSelect={handleSelection}
155
+ noSuggestionMessage={noOptionMatchedMessage}
156
+ >
157
+ {children}
158
+ </SuggestionList>
160
159
  </>
161
160
  );
162
161
  });
@@ -6,6 +6,7 @@
6
6
  overflow-y: auto;
7
7
  overflow-x: hidden;
8
8
  background-color: #fff;
9
+ overscroll-behavior: none;
9
10
  }
10
11
 
11
12
  :where(.input) {
@@ -34,13 +34,14 @@ export const SuggestionList: Story = {
34
34
  ref={setAnchorElement}
35
35
  value={value}
36
36
  onChange={e => setValue(e.target.value)}
37
- onFocus={() => setIsOpen(true)}
37
+ onClick={() => setIsOpen(true)}
38
38
  placeholder="Search fruits..."
39
39
  style={{ width: '100%', padding: '8px' }}
40
40
  />
41
- {isOpen && anchorElement && (
41
+ {anchorElement && (
42
42
  <SuggestionListComponent
43
43
  value={value}
44
+ open={isOpen}
44
45
  anchorElement={anchorElement}
45
46
  onChange={setValue}
46
47
  onOptionSelect={selectedValue => {
@@ -95,13 +96,14 @@ export const WithCustomContent: Story = {
95
96
  ref={setAnchorElement}
96
97
  value={label}
97
98
  onChange={e => setLabel(e.target.value)}
98
- onFocus={() => setIsOpen(true)}
99
+ onClick={() => setIsOpen(true)}
99
100
  placeholder="Search cities..."
100
101
  style={{ width: '100%', padding: '8px' }}
101
102
  />
102
- {isOpen && anchorElement && (
103
+ {anchorElement && (
103
104
  <SuggestionListComponent
104
105
  value={label}
106
+ open={isOpen}
105
107
  anchorElement={anchorElement}
106
108
  onChange={setLabel}
107
109
  onOptionSelect={(_, label) => {
@@ -160,11 +162,11 @@ export const WithDisabledOptions: Story = {
160
162
  ref={setAnchorElement}
161
163
  value={value}
162
164
  onChange={e => setValue(e.target.value)}
163
- onFocus={() => setIsOpen(true)}
165
+ onClick={() => setIsOpen(true)}
164
166
  placeholder="Search fruits..."
165
167
  style={{ width: '100%', padding: '8px' }}
166
168
  />
167
- {isOpen && anchorElement && (
169
+ {anchorElement && (
168
170
  <SuggestionListComponent
169
171
  value={value}
170
172
  anchorElement={anchorElement}
@@ -173,6 +175,7 @@ export const WithDisabledOptions: Story = {
173
175
  setValue(selectedValue);
174
176
  setIsOpen(false);
175
177
  }}
178
+ open={isOpen}
176
179
  onClose={() => setIsOpen(false)}
177
180
  >
178
181
  <Option value="apple" label="Apple" keywords={['fruit', 'red', 'sweet']}>
@@ -333,13 +336,14 @@ export const PerformanceTest: Story = {
333
336
  ref={setAnchorElement}
334
337
  value={label}
335
338
  onChange={e => setLabel(e.target.value)}
336
- onFocus={() => setIsOpen(true)}
339
+ onClick={() => setIsOpen(true)}
337
340
  placeholder="Search 1000 random items..."
338
341
  style={{ width: '100%', padding: '8px' }}
339
342
  />
340
- {isOpen && anchorElement && (
343
+ {anchorElement && (
341
344
  <SuggestionListComponent
342
345
  value={label}
346
+ open={isOpen}
343
347
  anchorElement={anchorElement}
344
348
  onChange={setLabel}
345
349
  onOptionSelect={(_, label) => {
@@ -18,8 +18,10 @@ const BATCH_SIZE = 50;
18
18
  export interface SuggestionListProps
19
19
  extends Omit<React.HTMLAttributes<HTMLInputElement>, 'onChange'> {
20
20
  value?: string;
21
+ initialSearchValue?: string;
21
22
  scrollToValue?: string;
22
23
  anchorElement: HTMLElement | null;
24
+ open?: boolean;
23
25
  children?: React.ReactNode;
24
26
  onChange?: (value: string) => void;
25
27
  onOptionSelect?: (
@@ -31,6 +33,7 @@ export interface SuggestionListProps
31
33
  noSuggestionMessage?: React.ReactNode;
32
34
  trimCustomInput?: boolean;
33
35
  haveValueAsOption?: boolean;
36
+ restoreFocus?: boolean;
34
37
  onClose?: (
35
38
  inputValue: string,
36
39
  cursorStartPosition: number | null,
@@ -40,8 +43,10 @@ export interface SuggestionListProps
40
43
 
41
44
  export function SuggestionList({
42
45
  value = '',
46
+ initialSearchValue: searchValue,
43
47
  scrollToValue,
44
48
  anchorElement,
49
+ open = false,
45
50
  children,
46
51
  onOptionSelect,
47
52
  noSuggestionMessage = '-- No Matches --',
@@ -51,6 +56,7 @@ export function SuggestionList({
51
56
  onKeyDown,
52
57
  trimCustomInput = false,
53
58
  haveValueAsOption = false,
59
+ restoreFocus = true,
54
60
  ...props
55
61
  }: SuggestionListProps) {
56
62
  // Extract valid Option components from children
@@ -70,6 +76,7 @@ export function SuggestionList({
70
76
  });
71
77
 
72
78
  const keyPressedDownRef = useRef('');
79
+ const KeyDownRegistered = useRef(false);
73
80
  const [maxResults, setMaxResults] = useState(MAX_RESULTS);
74
81
  const [focusedIndex, setFocusedIndex] = useState(selectedIndex);
75
82
  const [internalValue, setInternalValue] = useState(value);
@@ -82,6 +89,11 @@ export function SuggestionList({
82
89
  const [suggestionsHeight, setSuggestionsHeight] = useState<string | undefined>();
83
90
 
84
91
  function handleKeyUp(event: React.KeyboardEvent<HTMLInputElement>) {
92
+ if (!KeyDownRegistered.current) {
93
+ return;
94
+ }
95
+ KeyDownRegistered.current = false;
96
+
85
97
  const key = event.key;
86
98
  const input = event.currentTarget;
87
99
 
@@ -156,7 +168,7 @@ export function SuggestionList({
156
168
  function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
157
169
  const key = event.key;
158
170
  keyPressedDownRef.current = key;
159
-
171
+ KeyDownRegistered.current = true;
160
172
  // We handle these key events on keydown to be responsive navigation.
161
173
  switch (key) {
162
174
  case 'ArrowDown': {
@@ -339,12 +351,16 @@ export function SuggestionList({
339
351
  useLayoutEffect(() => {
340
352
  const input = internalInputRef.current;
341
353
 
354
+ if (!open) {
355
+ return;
356
+ }
357
+
342
358
  if (input != null && input.value.length > 0) {
343
359
  input.select();
344
360
  } else if (input != null) {
345
361
  input.focus();
346
362
  }
347
- }, []);
363
+ }, [open]);
348
364
 
349
365
  useLayoutEffect(() => {
350
366
  if (anchorElement != null) {
@@ -373,14 +389,19 @@ export function SuggestionList({
373
389
  setFocusedIndex(internalValue === '' ? selectedIndex : -1);
374
390
  }, [internalValue, selectedIndex]);
375
391
 
392
+ useLayoutEffect(() => {
393
+ setInternalValue(searchValue ?? '');
394
+ }, [searchValue]);
395
+
376
396
  return (
377
397
  <Popper
378
- open
398
+ open={open}
379
399
  anchorElement={anchorElement}
380
400
  onClose={handleUseClose}
381
401
  verticalAnchor="top"
382
402
  verticalOrigin="top"
383
403
  verticalOffset={-4}
404
+ restoreFocus={restoreFocus}
384
405
  >
385
406
  <VStack
386
407
  minHeight={`calc(${suggestionsHeight}, 8px)`}
@@ -20,7 +20,7 @@ export const Textarea = React.forwardRef(function Textarea(
20
20
  return (
21
21
  <textarea
22
22
  style={{ width, height, ...style }}
23
- className={clsx(className, styles.textarea, 'tcn-textarea')}
23
+ className={clsx(className, styles.textarea, 'tcn-textarea', 'tcn-control')}
24
24
  data-is-disabled={props.disabled || false}
25
25
  ref={ref}
26
26
  onChange={e => {
@@ -1,13 +1,13 @@
1
1
  import React, { useRef } from 'react';
2
2
  import { clsx } from 'clsx';
3
3
  import { useForkRef } from '../../utils/index.js';
4
- import { HStack, type HStackProps } from '../../stacks/h_stack.js';
4
+ import { type HStackProps } from '../../stacks/h_stack.js';
5
5
  import { Input } from '../input/input.js';
6
6
  import { Select } from '../select/select.js';
7
7
  import { OptionProps } from '../options/option.js';
8
8
  import styles from './unit_input.module.css';
9
9
 
10
- import { ZStack } from '../../stacks/z_stack.js';
10
+ import { InputGroup } from '../input_group/input_group.js';
11
11
 
12
12
  function getDisplayValue(value: string) {
13
13
  return value
@@ -80,25 +80,25 @@ export const UnitInput = React.forwardRef(function UnitInput(
80
80
  }
81
81
 
82
82
  return (
83
- <HStack
83
+ <InputGroup
84
84
  ref={ref}
85
85
  className={clsx(styles['unit-input'], 'tcn-unit-input')}
86
86
  height="auto"
87
87
  {...props}
88
88
  >
89
- <ZStack width="flex">
90
- <Input
91
- className={clsx(styles['unit-input-number'], 'tcn-unit-input-number')}
92
- ref={forkedInputRef}
93
- onChange={valueHandler}
94
- disabled={disabled}
95
- style={{
96
- borderEndEndRadius: 0,
97
- borderStartEndRadius: 0,
98
- textAlign: 'start',
99
- }}
100
- />
101
- </ZStack>
89
+ <Input
90
+ width="flex"
91
+ className={clsx(styles['unit-input-number'], 'tcn-unit-input-number')}
92
+ ref={forkedInputRef}
93
+ onChange={valueHandler}
94
+ disabled={disabled}
95
+ style={{
96
+ borderEndEndRadius: 0,
97
+ borderStartEndRadius: 0,
98
+ textAlign: 'start',
99
+ }}
100
+ />
101
+
102
102
  <Select
103
103
  className={clsx(styles['unit-input-select'], 'tcn-unit-input-select')}
104
104
  ref={unitRef}
@@ -110,6 +110,6 @@ export const UnitInput = React.forwardRef(function UnitInput(
110
110
  >
111
111
  {children}
112
112
  </Select>
113
- </HStack>
113
+ </InputGroup>
114
114
  );
115
115
  });
@@ -3,8 +3,6 @@ import { VStack } from '../../stacks/v_stack.js';
3
3
  import { SBNestedScaffold, SBContent, SBNestedRail } from './utils.js';
4
4
 
5
5
  import styles from './composed_stories.module.css';
6
- import clsx from 'clsx';
7
- import { Body } from '../body/body.js';
8
6
 
9
7
  const meta: Meta = {
10
8
  title: 'Layouts/Composed',
@@ -45,59 +43,6 @@ export const Baseline: Story = {
45
43
  ),
46
44
  };
47
45
 
48
- export const ScaffoldWithBody: Story = {
49
- render: () => (
50
- <SBContainer>
51
- <SBNestedScaffold depth={1}>
52
- <SBContent />
53
- <SBContent />
54
- <SBContent />
55
- </SBNestedScaffold>
56
- </SBContainer>
57
- ),
58
- };
59
-
60
- export const ScaffoldWithMain: Story = {
61
- render: () => (
62
- <SBContainer>
63
- <SBNestedScaffold depth={1}>
64
- <SBNestedRail depth={2} side={false} utilityStrip={false} scaffold={false}>
65
- <SBContent />
66
- <SBContent />
67
- <SBContent />
68
- </SBNestedRail>
69
- </SBNestedScaffold>
70
- </SBContainer>
71
- ),
72
- };
73
-
74
- export const RailWithMain: Story = {
75
- render: () => (
76
- <SBContainer>
77
- <SBNestedRail depth={1} scaffold={false}>
78
- <SBContent />
79
- <SBContent />
80
- <SBContent />
81
- </SBNestedRail>
82
- </SBContainer>
83
- ),
84
- };
85
-
86
- export const RailWithBody: Story = {
87
- render: () => (
88
- <SBContainer>
89
- <SBNestedRail depth={1} scaffold={false}>
90
- <Body className={clsx(styles.body, styles.secondary)}>
91
- Body
92
- <SBContent />
93
- <SBContent />
94
- <SBContent />
95
- </Body>
96
- </SBNestedRail>
97
- </SBContainer>
98
- ),
99
- };
100
-
101
46
  export const WithColumns: Story = {
102
47
  render: () => (
103
48
  <SBContainer>
@@ -0,0 +1,78 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { LayoutStoryDecorator } from './utils/layout_theme_provider.js';
4
+ import { SBLayoutContent } from './utils/content.js';
5
+ import { Rail } from '../rail/rail.js';
6
+ import { Side } from '../rail/side/side.js';
7
+ import { UtilityStrip } from '../rail/utility_strip/utility_strip.js';
8
+ import { Title } from '../../typography/index.js';
9
+ import { HBody } from '../body/h_body.js';
10
+ import { VBody } from '../body/v_body.js';
11
+ import { Body } from '../body/body.js';
12
+
13
+ const meta: Meta<typeof Rail> = {
14
+ title: 'Layouts/Rail',
15
+ component: Rail,
16
+ tags: ['autodocs'],
17
+ parameters: {
18
+ docs: {
19
+ description: {
20
+ component: 'A component that lays out content in a horizontal hierarchy.',
21
+ },
22
+ },
23
+ },
24
+ };
25
+
26
+ export default meta;
27
+
28
+ type Story = StoryObj<typeof Rail>;
29
+
30
+ export const Baseline: Story = {
31
+ render: () => (
32
+ <LayoutStoryDecorator>
33
+ <Rail>
34
+ <UtilityStrip>
35
+ <Title>Utility Strip</Title>
36
+ </UtilityStrip>
37
+ <Body>Body fills available space </Body>
38
+ <Side width="200px">Side</Side>
39
+ </Rail>
40
+ </LayoutStoryDecorator>
41
+ ),
42
+ };
43
+
44
+ export const HorizontalContent: Story = {
45
+ render: () => (
46
+ <LayoutStoryDecorator>
47
+ <Rail>
48
+ <UtilityStrip>
49
+ <Title>Utility Strip</Title>
50
+ </UtilityStrip>
51
+ <HBody>
52
+ <SBLayoutContent />
53
+ <SBLayoutContent />
54
+ <SBLayoutContent />
55
+ </HBody>
56
+ <Side width="200px">Side</Side>
57
+ </Rail>
58
+ </LayoutStoryDecorator>
59
+ ),
60
+ };
61
+
62
+ export const VerticalContent: Story = {
63
+ render: () => (
64
+ <LayoutStoryDecorator>
65
+ <Rail>
66
+ <UtilityStrip>
67
+ <Title>Utility Strip</Title>
68
+ </UtilityStrip>
69
+ <VBody>
70
+ <SBLayoutContent />
71
+ <SBLayoutContent />
72
+ <SBLayoutContent />
73
+ </VBody>
74
+ <Side width="200px">Side</Side>
75
+ </Rail>
76
+ </LayoutStoryDecorator>
77
+ ),
78
+ };
@@ -0,0 +1,90 @@
1
+ import { Scaffold } from '../scaffold/scaffold.js';
2
+ import { Header } from '../header/header.js';
3
+ import { UtilityBar } from '../utility_bar/utility_bar.js';
4
+ import { VBody } from '../body/v_body.js';
5
+ import { Footer } from '../footer/footer.js';
6
+ import type { Meta, StoryObj } from '@storybook/react-vite';
7
+
8
+ import { LayoutStoryDecorator } from './utils/layout_theme_provider.js';
9
+ import { SBLayoutContent } from './utils/content.js';
10
+ import { HBody } from '../body/h_body.js';
11
+ import { VStack } from '../../stacks/v_stack.js';
12
+ import { Body } from '../body/body.js';
13
+
14
+ const meta: Meta<typeof Scaffold> = {
15
+ title: 'Layouts/Scaffold',
16
+ component: Scaffold,
17
+ tags: ['autodocs'],
18
+ parameters: {
19
+ docs: {
20
+ description: {
21
+ component: 'A scaffold component that lays out content in a vertical hierarchy.',
22
+ },
23
+ },
24
+ },
25
+ };
26
+
27
+ export default meta;
28
+
29
+ type Story = StoryObj<typeof Scaffold>;
30
+
31
+ export const Baseline: Story = {
32
+ render: () => (
33
+ <LayoutStoryDecorator>
34
+ <Scaffold>
35
+ <Header>Header</Header>
36
+ <UtilityBar>Utility Bar</UtilityBar>
37
+ <Body>Body fills available space</Body>
38
+ <Footer>Footer</Footer>
39
+ </Scaffold>
40
+ </LayoutStoryDecorator>
41
+ ),
42
+ };
43
+
44
+ export const VerticalContent: Story = {
45
+ args: {
46
+ height: '100vh',
47
+ },
48
+ render: () => (
49
+ <LayoutStoryDecorator>
50
+ <Scaffold height="100vh">
51
+ <Header>Header</Header>
52
+ <UtilityBar>Utility Bar</UtilityBar>
53
+ <VBody>
54
+ <p>(V)Body</p>
55
+ <p>Fills available space</p>
56
+ <p>Scrolls vertically</p>
57
+ <SBLayoutContent />
58
+ <SBLayoutContent />
59
+ <SBLayoutContent />
60
+ </VBody>
61
+ <Footer>Footer</Footer>
62
+ </Scaffold>
63
+ </LayoutStoryDecorator>
64
+ ),
65
+ };
66
+
67
+ export const HorizontalContent: Story = {
68
+ args: {
69
+ height: '100vh',
70
+ },
71
+ render: () => (
72
+ <LayoutStoryDecorator>
73
+ <Scaffold height="100vh">
74
+ <Header>Header</Header>
75
+ <UtilityBar>Utility Bar</UtilityBar>
76
+ <HBody>
77
+ <VStack minWidth="200px" hAlign="center">
78
+ <p>(H)Body</p>
79
+ <p>Fills available space</p>
80
+ <p>Scrolls horizontally</p>
81
+ </VStack>
82
+ <SBLayoutContent />
83
+ <SBLayoutContent />
84
+ <SBLayoutContent />
85
+ </HBody>
86
+ <Footer>Footer</Footer>
87
+ </Scaffold>
88
+ </LayoutStoryDecorator>
89
+ ),
90
+ };