@kth/style 0.0.1-1 → 0.0.1

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/Tabs.d.ts CHANGED
@@ -1,20 +1,34 @@
1
1
  import React from "react";
2
- export declare function NavigationTabs({ id, children, url, defaultValue, }: {
2
+ interface TabProps {
3
+ children?: React.ReactNode;
4
+ id: string;
5
+ title: string;
6
+ }
7
+ interface GenericTabsProps {
8
+ classPrefix: string;
9
+ children: React.ReactElement<TabProps>[];
10
+ id: string;
11
+ defaultValue?: string;
12
+ url: "query" | "hash" | "none";
13
+ }
14
+ interface NavigationTabsProps {
3
15
  id: string;
4
16
  children: React.ReactElement<TabProps>[];
5
17
  url?: "query" | "none";
6
18
  defaultValue?: string;
7
- }): JSX.Element;
8
- export declare function ContentTabs({ id, children, url, defaultValue, }: {
19
+ }
20
+ interface ContentTabsProps {
9
21
  id: string;
10
22
  children: React.ReactElement<TabProps>[];
11
23
  url?: "hash" | "none";
12
24
  defaultValue?: string;
13
- }): JSX.Element;
14
- interface TabProps {
15
- children?: React.ReactNode;
16
- id: string;
17
- title: string;
18
25
  }
19
- export declare function Tab({ children, id }: TabProps): JSX.Element;
26
+ /**
27
+ * Highly customizable Tabs component made for testing purposes.
28
+ * You should use {@link NavigationTabs} or {@link ContentTabs} instead.
29
+ */
30
+ export declare function GenericTabs({ classPrefix, children, id, defaultValue, url, }: GenericTabsProps): JSX.Element;
31
+ export declare function NavigationTabs({ id, children, url, defaultValue, }: NavigationTabsProps): JSX.Element;
32
+ export declare function ContentTabs({ id, children, url, defaultValue, }: ContentTabsProps): JSX.Element;
33
+ export declare function Tab({ children }: TabProps): JSX.Element;
20
34
  export {};
package/dist/Tabs.js CHANGED
@@ -1,92 +1,112 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Tab = exports.ContentTabs = exports.NavigationTabs = void 0;
3
+ exports.Tab = exports.ContentTabs = exports.NavigationTabs = exports.GenericTabs = void 0;
4
4
  const react_1 = require("react");
5
- /** Get current URL hash */
6
- function useUrlHash() {
7
- const [currentHash, setCurrentHash] = react_1.default.useState("");
8
- react_1.default.useEffect(() => {
9
- function updateValue(event) {
10
- const hash = new URL(event?.newURL || location.href).hash.slice(1);
11
- setCurrentHash(hash);
12
- }
13
- window.addEventListener("hashchange", updateValue);
14
- updateValue();
15
- return () => {
16
- window.removeEventListener("hashchange", updateValue);
17
- };
18
- });
19
- return currentHash;
5
+ function getValueFromUrl(key) {
6
+ const url = new URL(location.href);
7
+ if (key) {
8
+ return url.searchParams.get(key);
9
+ }
10
+ else {
11
+ return url.hash === "" ? null : url.hash.slice(1);
12
+ }
20
13
  }
21
- /** Keep in sync the value of the query parameter `param` */
22
- function useUrlQuery(param) {
23
- const [value, _setValue] = react_1.default.useState("");
14
+ /**
15
+ * Sync state with URL hash or query parameter.
16
+ * @param defaultValue Default state value
17
+ * @param key Query parameter to use. Hash if empty
18
+ * @param enableUrl Will sync with URL if true (this function behaves the same
19
+ * as `React.useState` if false)
20
+ */
21
+ function useUrlState(defaultValue = "", key = "", enableUrl = false) {
22
+ const initialValue = getValueFromUrl(key) || defaultValue;
23
+ const [value, _setValue] = react_1.default.useState(initialValue);
24
24
  react_1.default.useEffect(() => {
25
25
  function updateValue() {
26
- const url = new URL(location.href);
27
- const value = url.searchParams.get(param) || "";
28
- _setValue(value);
26
+ if (enableUrl) {
27
+ _setValue(getValueFromUrl(key) || defaultValue);
28
+ }
29
29
  }
30
30
  window.addEventListener("popstate", updateValue);
31
- updateValue();
32
31
  return () => {
33
32
  window.removeEventListener("popstate", updateValue);
34
33
  };
35
- }, [param]);
36
- function setValue(newValue) {
34
+ });
35
+ function setValue(value) {
36
+ _setValue(value);
37
37
  const url = new URL(location.href);
38
- url.searchParams.set(param, newValue);
39
- history.pushState(undefined, "", url);
40
- _setValue(newValue);
38
+ if (key !== "") {
39
+ url.searchParams.set(key, value);
40
+ }
41
+ else {
42
+ url.hash = "#" + value;
43
+ }
44
+ if (enableUrl) {
45
+ history.pushState(undefined, "", url);
46
+ }
41
47
  }
42
48
  return [value, setValue];
43
49
  }
44
- function NavigationTabs({ id, children, url = "query", defaultValue, }) {
45
- const containerRef = react_1.default.useRef(null);
46
- const [queryTab, setQueryTab] = useUrlQuery(id);
47
- const [manualTab, setManualTab] = react_1.default.useState(defaultValue || "");
48
- const activeTab = url === "query" ? queryTab : manualTab;
50
+ function handleKeyDown(event, index, tabs, setActiveTab) {
51
+ event.preventDefault();
52
+ switch (event.key) {
53
+ case "ArrowLeft":
54
+ case "ArrowUp":
55
+ if (index > 0) {
56
+ setActiveTab(tabs[index - 1].props.id);
57
+ }
58
+ break;
59
+ case "ArrowRight":
60
+ case "ArrowDown":
61
+ if (index < tabs.length - 1) {
62
+ setActiveTab(tabs[index + 1].props.id);
63
+ }
64
+ break;
65
+ case "Home":
66
+ setActiveTab(tabs[0].props.id);
67
+ break;
68
+ case "End":
69
+ setActiveTab(tabs[tabs.length - 1].props.id);
70
+ break;
71
+ }
72
+ }
73
+ function useFocus(element) {
74
+ element?.scrollIntoView({ block: "nearest", inline: "nearest" });
75
+ element?.focus();
76
+ }
77
+ /**
78
+ * Highly customizable Tabs component made for testing purposes.
79
+ * You should use {@link NavigationTabs} or {@link ContentTabs} instead.
80
+ */
81
+ function GenericTabs({ classPrefix, children, id, defaultValue, url = "none", }) {
82
+ const ref = react_1.default.useRef(null);
83
+ const [activeTab, setActiveTab] = useUrlState(defaultValue, url === "query" ? id : "", url === "query" || url === "hash");
49
84
  let activeTabIndex = children.findIndex((c) => c.props.id === activeTab);
50
85
  if (activeTabIndex === -1) {
51
86
  activeTabIndex = 0;
52
87
  }
88
+ useFocus(ref.current?.querySelector(`#${activeTab}-tab`));
53
89
  return (react_1.default.createElement(react_1.default.Fragment, null,
54
- react_1.default.createElement("div", { ref: containerRef, className: "kth-navigation-tabs", id: id },
55
- react_1.default.createElement("ul", { className: "kth-navigation-tabs__tablist" }, children.map((child, index) => (react_1.default.createElement("li", { key: child.props.id },
56
- react_1.default.createElement("button", { className: "kth-navigation-tabs__tab", "aria-selected": index === activeTabIndex, onClick: (event) => {
90
+ react_1.default.createElement("div", { ref: ref, className: `${classPrefix}`, id: id },
91
+ react_1.default.createElement("ul", { className: `${classPrefix}__tablist`, role: "tablist" }, children.map((child, index) => (react_1.default.createElement("li", { role: "presentation", className: `${classPrefix}__tab-element` },
92
+ react_1.default.createElement("button", { key: child.props.id, role: "tab", "aria-selected": index === activeTabIndex, "aria-controls": child.props.id, id: `${child.props.id}-tab`, className: `${classPrefix}__tab`, tabIndex: index === activeTabIndex ? 0 : -1, onKeyDown: (event) => {
93
+ handleKeyDown(event, index, children, setActiveTab);
94
+ }, onClick: (event) => {
57
95
  event.preventDefault();
58
- if (url === "query") {
59
- setQueryTab(child.props.id);
60
- }
61
- else {
62
- setManualTab(child.props.id);
63
- }
96
+ setActiveTab(child.props.id);
64
97
  } }, child.props.title)))))),
65
- children[activeTabIndex]));
98
+ children.map((child, index) => (react_1.default.createElement("div", { className: `${classPrefix}__tabpanel`, role: "tabpanel", "aria-labelledby": `${child.props.id}-tab`, id: child.props.id, key: index, hidden: index !== activeTabIndex }, child)))));
99
+ }
100
+ exports.GenericTabs = GenericTabs;
101
+ function NavigationTabs({ id, children, url = "query", defaultValue, }) {
102
+ return (react_1.default.createElement(GenericTabs, { id: id, children: children, url: url, defaultValue: defaultValue, classPrefix: "kth-navigation-tabs" }));
66
103
  }
67
104
  exports.NavigationTabs = NavigationTabs;
68
105
  function ContentTabs({ id, children, url = "hash", defaultValue, }) {
69
- const containerRef = react_1.default.useRef(null);
70
- const [manualActiveTab, setManualActiveTab] = react_1.default.useState("");
71
- const hashActiveTab = useUrlHash() || defaultValue || "";
72
- const activeTab = url === "hash" ? hashActiveTab : manualActiveTab;
73
- let activeTabIndex = children.findIndex((c) => c.props.id === activeTab);
74
- if (activeTabIndex === -1) {
75
- activeTabIndex = 0;
76
- }
77
- return (react_1.default.createElement(react_1.default.Fragment, null,
78
- react_1.default.createElement("div", { ref: containerRef, className: "kth-content-tabs", id: id },
79
- react_1.default.createElement("ul", { className: "kth-content-tabs__tablist" }, children.map((child, index) => (react_1.default.createElement("li", { key: child.props.id },
80
- react_1.default.createElement("a", { href: `#${child.props.id}`, className: "kth-content-tabs__tab", "aria-selected": index === activeTabIndex, onClick: (event) => {
81
- if (url === "none") {
82
- event.preventDefault();
83
- setManualActiveTab(child.props.id);
84
- }
85
- } }, child.props.title)))))),
86
- children[activeTabIndex]));
106
+ return (react_1.default.createElement(GenericTabs, { id: id, children: children, url: url, defaultValue: defaultValue, classPrefix: "kth-content-tabs" }));
87
107
  }
88
108
  exports.ContentTabs = ContentTabs;
89
- function Tab({ children, id }) {
90
- return react_1.default.createElement("div", { id: id }, children);
109
+ function Tab({ children }) {
110
+ return react_1.default.createElement(react_1.default.Fragment, null, children);
91
111
  }
92
112
  exports.Tab = Tab;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kth/style",
3
- "version": "0.0.1-1",
3
+ "version": "0.0.1",
4
4
  "scripts": {
5
5
  "clean": "rm -rf dist",
6
6
  "dev": "storybook dev -p 6006",
@@ -14,10 +14,6 @@
14
14
  "scss",
15
15
  "dist"
16
16
  ],
17
- "dependencies": {
18
- "react": "^18.2.0",
19
- "react-dom": "^18.2.0"
20
- },
21
17
  "devDependencies": {
22
18
  "@storybook/addon-essentials": "^7.0.6",
23
19
  "@storybook/addon-interactions": "^7.0.6",
@@ -36,8 +32,13 @@
36
32
  "eslint-plugin-react-refresh": "^0.3.4",
37
33
  "eslint-plugin-storybook": "^0.6.11",
38
34
  "prop-types": "^15.8.1",
35
+ "react": "^18.2.0",
36
+ "react-dom": "^18.2.0",
39
37
  "storybook": "^7.0.6",
40
38
  "typescript": "^5.0.2",
41
39
  "vite": "^4.3.0"
40
+ },
41
+ "peerDependencies": {
42
+ "react": "*"
42
43
  }
43
44
  }
@@ -5,52 +5,60 @@ $border-secondary: 1px solid t.$color-neutral-10;
5
5
  @layer kth-style {
6
6
  .kth-content-tabs {
7
7
  overflow: auto;
8
- padding-block: t.$space-4;
9
- }
10
-
11
- .kth-content-tabs__tablist {
8
+ padding-block-start: t.$space-16;
12
9
  position: sticky;
13
- list-style: none;
14
- padding: 0;
15
- margin: 0;
16
- display: inline-flex;
17
- flex-wrap: nowrap;
18
- align-items: end;
19
- gap: t.$space-4;
20
- min-width: 100%;
21
- border-block-end: $border-secondary;
22
-
23
- li {
24
- margin-bottom: -1px;
10
+ top: calc(var(--kpm-bar-height, 0rem));
11
+ background: t.$color-neutral-0;
12
+
13
+ &__tablist {
14
+ list-style: none;
15
+ padding: 0;
16
+ margin: 0;
17
+ display: inline-flex;
18
+ flex-wrap: nowrap;
19
+ align-items: stretch;
20
+ gap: t.$space-4;
21
+ min-width: 100%;
22
+ border-block-end: $border-secondary;
25
23
  }
26
- }
27
- .kth-content-tabs__tab {
28
- box-sizing: border-box;
29
- padding-inline: var(--kth-0-button-padding-inline);
30
- padding-block: calc(var(--kth-0-button-padding-block) - 1px);
31
- border-block-width: 1px;
32
- white-space: nowrap;
33
- display: block;
34
- text-decoration: none;
35
- margin-block-end: -1px;
36
- color: t.$color-neutral-10;
37
- border: $border-secondary;
38
- border-radius: t.border-radius(8 8 0 0);
39
-
40
- &:hover {
41
- text-decoration: underline;
24
+
25
+ &__tab-element {
26
+ display: flex;
42
27
  }
43
28
 
44
- &[aria-selected="false"] {
45
- background: t.$color-neutral-2;
29
+ &__tab {
30
+ box-sizing: border-box;
31
+ padding-inline: var(--kth-0-clickable-padding-inline);
32
+ padding-block: calc(var(--kth-0-clickable-padding-block) - 1px);
33
+ border-block-width: 1px;
34
+ display: block;
35
+ text-decoration: none;
36
+ margin-block-end: -1px;
37
+ color: t.$color-neutral-10;
38
+ border: $border-secondary;
39
+ border-radius: t.border-radius(8 8 0 0);
40
+
41
+ &:hover {
42
+ text-decoration: underline;
43
+ }
44
+
45
+ &[aria-selected="false"] {
46
+ background: t.$color-neutral-2;
47
+ margin-block-start: #{t.$space-8};
48
+ }
49
+
50
+ &[aria-selected="true"] {
51
+ background: t.$color-neutral-0;
52
+ padding-block-start: calc(
53
+ var(--kth-0-clickable-padding-block) + #{t.$space-8} - 1px
54
+ );
55
+ border-block-end-color: transparent;
56
+ }
46
57
  }
47
58
 
48
- &[aria-selected="true"] {
49
- background: t.$color-neutral-0;
50
- padding-block-start: calc(
51
- var(--kth-0-button-padding-block) + #{t.$space-8} - 1px
52
- );
53
- border-block-end-color: transparent;
59
+ &__tabpanel {
60
+ border-inline: $border-secondary;
61
+ border-block-end: $border-secondary;
54
62
  }
55
63
  }
56
64
  }
@@ -5,45 +5,45 @@ $border-secondary: 1px solid t.$color-neutral-10;
5
5
  @layer kth-style {
6
6
  .kth-navigation-tabs {
7
7
  overflow: auto;
8
- }
9
-
10
- .kth-navigation-tabs__tablist {
11
- list-style: none;
12
- padding: 0;
13
- margin: 0;
14
- display: inline-flex;
15
- flex-wrap: nowrap;
16
- align-items: end;
17
- gap: t.$space-4;
18
- min-width: 100%;
19
- border-block-end: $border-secondary;
20
- }
21
-
22
- .kth-navigation-tabs__tab {
23
- font: inherit;
24
- background: none;
25
- border: none;
26
- box-sizing: border-box;
27
- padding-inline: var(--kth-0-button-padding-inline);
28
- padding-block: var(--kth-0-button-padding-block);
29
- white-space: nowrap;
30
- display: block;
31
- text-decoration: none;
32
- color: t.$color-neutral-10;
33
- margin-block-end: -1px;
34
8
 
35
- &:hover {
36
- text-decoration: underline;
9
+ &__tablist {
10
+ list-style: none;
11
+ padding: 0;
12
+ margin: 0;
13
+ display: inline-flex;
14
+ flex-wrap: nowrap;
15
+ align-items: end;
16
+ gap: t.$space-4;
17
+ min-width: 100%;
18
+ border-block-end: $border-secondary;
37
19
  }
38
20
 
39
- &[aria-selected="false"] {
21
+ &__tab {
22
+ font: inherit;
23
+ background: none;
24
+ border: none;
25
+ box-sizing: border-box;
26
+ padding-inline: var(--kth-0-clickable-padding-inline);
27
+ padding-block: var(--kth-0-clickable-padding-block);
28
+ white-space: nowrap;
29
+ display: block;
30
+ text-decoration: none;
40
31
  color: t.$color-neutral-10;
41
- }
32
+ margin-block-end: -1px;
33
+
34
+ &:hover {
35
+ text-decoration: underline;
36
+ }
37
+
38
+ &[aria-selected="false"] {
39
+ color: t.$color-neutral-10;
40
+ }
42
41
 
43
- &[aria-selected="true"] {
44
- color: t.$color-primary-5;
45
- padding-block-end: calc(var(--kth-0-button-padding-block) - 3px);
46
- border-block-end: 3px t.$color-primary-5 solid;
42
+ &[aria-selected="true"] {
43
+ color: t.$color-primary-5;
44
+ padding-block-end: calc(var(--kth-0-clickable-padding-block) - 3px);
45
+ border-block-end: 3px t.$color-primary-5 solid;
46
+ }
47
47
  }
48
48
  }
49
49
  }
package/scss/globals.scss CHANGED
@@ -1,5 +1,7 @@
1
1
  @use "utils/reset";
2
2
  @use "utils/sizes";
3
3
 
4
- @include reset.reset();
5
- @include sizes.sizes();
4
+ @layer kth-style {
5
+ @include reset.reset();
6
+ @include sizes.sizes();
7
+ }
@@ -3,17 +3,17 @@
3
3
  @mixin sizes {
4
4
  :root {
5
5
  // Padding (block and inline axis) for clickable elements
6
- --kth-0-button-padding-block: #{t.$space-8};
7
- --kth-0-button-padding-inline: #{t.$space-16};
6
+ --kth-0-clickable-padding-block: #{t.$space-8};
7
+ --kth-0-clickable-padding-inline: #{t.$space-16};
8
8
  }
9
9
 
10
10
  .kth-0-normal {
11
- --kth-0-button-padding-block: #{t.$space-8};
12
- --kth-0-button-padding-inline: #{t.$space-16};
11
+ --kth-0-clickable-padding-block: #{t.$space-8};
12
+ --kth-0-clickable-padding-inline: #{t.$space-16};
13
13
  }
14
14
 
15
15
  .kth-0-small {
16
- --kth-0-button-padding-block: #{t.$space-4};
17
- --kth-0-button-padding-inline: #{t.$space-12};
16
+ --kth-0-clickable-padding-block: #{t.$space-4};
17
+ --kth-0-clickable-padding-inline: #{t.$space-12};
18
18
  }
19
19
  }