@kth/style 0.0.12 → 0.1.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 (60) hide show
  1. package/dist/cjs/components/Button.d.ts +4 -4
  2. package/dist/cjs/components/ModalNavigationPanel.d.ts +4 -0
  3. package/dist/cjs/components/NavigationPanel.d.ts +7 -0
  4. package/dist/cjs/components/Radio.d.ts +2 -2
  5. package/dist/cjs/components/Select.d.ts +3 -5
  6. package/dist/cjs/components/Tabs.d.ts +1 -34
  7. package/dist/cjs/components/react/PersonalMenuPanel.d.ts +19 -0
  8. package/dist/cjs/components/react/Tabs.d.ts +34 -0
  9. package/dist/cjs/icons/400/ArrowBack.d.ts +2 -1
  10. package/dist/cjs/icons/400/Check.d.ts +2 -1
  11. package/dist/cjs/icons/500/ArrowForward.d.ts +2 -1
  12. package/dist/cjs/index.d.ts +3 -5
  13. package/dist/cjs/index.js +128 -157
  14. package/dist/cjs/react.d.ts +5 -0
  15. package/dist/cjs/react.js +179 -0
  16. package/dist/cjs/stories/ContentTabs.stories.d.ts +1 -2
  17. package/dist/esm/components/Button.d.ts +4 -4
  18. package/dist/esm/components/ModalNavigationPanel.d.ts +4 -0
  19. package/dist/esm/components/NavigationPanel.d.ts +7 -0
  20. package/dist/esm/components/Radio.d.ts +2 -2
  21. package/dist/esm/components/Select.d.ts +3 -5
  22. package/dist/esm/components/Tabs.d.ts +1 -34
  23. package/dist/esm/components/react/PersonalMenuPanel.d.ts +19 -0
  24. package/dist/esm/components/react/Tabs.d.ts +34 -0
  25. package/dist/esm/icons/400/ArrowBack.d.ts +2 -1
  26. package/dist/esm/icons/400/Check.d.ts +2 -1
  27. package/dist/esm/icons/500/ArrowForward.d.ts +2 -1
  28. package/dist/esm/index.d.ts +3 -5
  29. package/dist/esm/index.js +126 -129
  30. package/dist/esm/react.d.ts +5 -0
  31. package/dist/esm/react.js +149 -0
  32. package/dist/esm/stories/ContentTabs.stories.d.ts +1 -2
  33. package/package.json +14 -4
  34. package/scss/components/button.scss +41 -51
  35. package/scss/components/checkbox.scss +63 -0
  36. package/scss/components/details.scss +40 -0
  37. package/scss/components/fieldset.scss +60 -0
  38. package/scss/components/form-control.scss +25 -0
  39. package/scss/components/header-menu-item.scss +38 -0
  40. package/scss/components/input.scss +21 -0
  41. package/scss/components/main-header.scss +105 -0
  42. package/scss/components/search.scss +52 -0
  43. package/scss/components/select.scss +46 -0
  44. package/scss/tokens/colors.scss +63 -0
  45. package/scss/tokens/icons.scss +68 -0
  46. package/scss/tokens/{_spacing.scss → spacing.scss} +15 -0
  47. package/scss/tokens/typography.scss +35 -0
  48. package/scss/utils/mixins.scss +14 -0
  49. package/scss/utils/prose.scss +83 -76
  50. package/scss/utils/reset.scss +53 -255
  51. package/scss/components/content-tabs.scss +0 -68
  52. package/scss/components/filter-tabs.scss +0 -31
  53. package/scss/components/form.scss +0 -48
  54. package/scss/components/inline-icon.scss +0 -7
  55. package/scss/globals.scss +0 -5
  56. package/scss/tokens/_colors.scss +0 -20
  57. package/scss/tokens/_spacing.en.md +0 -40
  58. package/scss/tokens/_typography.scss +0 -71
  59. package/scss/tokens/index.scss +0 -6
  60. package/scss/utils/sizes.scss +0 -22
@@ -0,0 +1,5 @@
1
+ export { Tab, ContentTabs, NavigationTabs } from "./components/react/Tabs";
2
+ export { Select, Option, OptionGroup } from "./components/Select";
3
+ export { BackButton, Button, ForwardButton } from "./components/Button";
4
+ export { default as IconArrowBack } from "./icons/400/ArrowBack";
5
+ export { default as IconCheck } from "./icons/400/Check";
@@ -0,0 +1,149 @@
1
+ import * as React from 'react';
2
+ import React__default from 'react';
3
+
4
+ function getValueFromUrl(key) {
5
+ const url = new URL(location.href);
6
+ if (key) {
7
+ return url.searchParams.get(key);
8
+ }
9
+ else {
10
+ return url.hash === "" ? null : url.hash.slice(1);
11
+ }
12
+ }
13
+ /**
14
+ * Sync state with URL hash or query parameter.
15
+ * @param defaultValue Default state value
16
+ * @param key Query parameter to use. Hash if empty
17
+ * @param enableUrl Will sync with URL if true (this function behaves the same
18
+ * as `React.useState` if false)
19
+ */
20
+ function useUrlState(defaultValue = "", key = "", enableUrl = false) {
21
+ const initialValue = (enableUrl && getValueFromUrl(key)) || defaultValue;
22
+ const [value, _setValue] = React__default.useState(initialValue);
23
+ React__default.useEffect(() => {
24
+ function updateValue() {
25
+ if (enableUrl) {
26
+ _setValue(getValueFromUrl(key) || defaultValue);
27
+ }
28
+ }
29
+ window.addEventListener("popstate", updateValue);
30
+ return () => {
31
+ window.removeEventListener("popstate", updateValue);
32
+ };
33
+ });
34
+ function setValue(value) {
35
+ _setValue(value);
36
+ const url = new URL(location.href);
37
+ if (key !== "") {
38
+ url.searchParams.set(key, value);
39
+ }
40
+ else {
41
+ url.hash = "#" + value;
42
+ }
43
+ if (enableUrl) {
44
+ history.pushState(undefined, "", url);
45
+ }
46
+ }
47
+ return [value, setValue];
48
+ }
49
+ function handleKeyDown(event, index, tabs, setActiveTab) {
50
+ event.preventDefault();
51
+ switch (event.key) {
52
+ case "ArrowLeft":
53
+ case "ArrowUp":
54
+ if (index > 0) {
55
+ setActiveTab(tabs[index - 1].props.id);
56
+ }
57
+ break;
58
+ case "ArrowRight":
59
+ case "ArrowDown":
60
+ if (index < tabs.length - 1) {
61
+ setActiveTab(tabs[index + 1].props.id);
62
+ }
63
+ break;
64
+ case "Home":
65
+ setActiveTab(tabs[0].props.id);
66
+ break;
67
+ case "End":
68
+ setActiveTab(tabs[tabs.length - 1].props.id);
69
+ break;
70
+ }
71
+ }
72
+ function useFocus(element) {
73
+ element?.scrollIntoView({ block: "nearest", inline: "nearest" });
74
+ element?.focus();
75
+ }
76
+ /**
77
+ * Highly customizable Tabs component made for testing purposes.
78
+ * You should use {@link NavigationTabs} or {@link ContentTabs} instead.
79
+ */
80
+ function GenericTabs({ classPrefix, children, id, defaultValue, url = "none", }) {
81
+ const ref = React__default.useRef(null);
82
+ const [activeTab, setActiveTab] = useUrlState(defaultValue, url === "query" ? id : "", url === "query" || url === "hash");
83
+ let activeTabIndex = children.findIndex((c) => c.props.id === activeTab);
84
+ if (activeTabIndex === -1) {
85
+ activeTabIndex = 0;
86
+ }
87
+ useFocus(ref.current?.querySelector(`#${activeTab}-tab`));
88
+ return (React__default.createElement(React__default.Fragment, null,
89
+ React__default.createElement("div", { ref: ref, className: `${classPrefix}`, id: id },
90
+ React__default.createElement("ul", { className: `${classPrefix}__tablist`, role: "tablist" }, children.map((child, index) => (React__default.createElement("li", { role: "presentation", className: `${classPrefix}__tab-element` },
91
+ React__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) => {
92
+ handleKeyDown(event, index, children, setActiveTab);
93
+ }, onClick: (event) => {
94
+ event.preventDefault();
95
+ setActiveTab(child.props.id);
96
+ } }, child.props.title)))))),
97
+ children.map((child, index) => (React__default.createElement("div", { className: `${classPrefix}__tabpanel`, role: "tabpanel", "aria-labelledby": `${child.props.id}-tab`, id: child.props.id, key: index, hidden: index !== activeTabIndex }, child)))));
98
+ }
99
+ function NavigationTabs({ id, children, url = "query", defaultValue, }) {
100
+ return (React__default.createElement(GenericTabs, { id: id, children: children, url: url, defaultValue: defaultValue, classPrefix: "kth-navigation-tabs" }));
101
+ }
102
+ function ContentTabs({ id, children, url = "hash", defaultValue, }) {
103
+ return (React__default.createElement(GenericTabs, { id: id, children: children, url: url, defaultValue: defaultValue, classPrefix: "kth-content-tabs" }));
104
+ }
105
+ function Tab({ children }) {
106
+ return React__default.createElement(React__default.Fragment, null, children);
107
+ }
108
+
109
+ function Select({ name, label, description, value, onChange, children, error, }) {
110
+ const id = `select-${name}`;
111
+ return (React__default.createElement("div", { className: "kth-select" },
112
+ React__default.createElement("label", { htmlFor: id }, label),
113
+ description && React__default.createElement("p", { id: `${id}-description` }, description),
114
+ React__default.createElement("select", { id: id, "aria-describedby": description && `${id}-description`, name: name, value: value, onChange: (e) => onChange(e.target.value), "aria-invalid": error !== undefined ? "true" : "false" }, children),
115
+ error && (React__default.createElement("p", { className: "kth-select__error", role: "alert" }, error))));
116
+ }
117
+ function OptionGroup({ label, children }) {
118
+ return React__default.createElement("optgroup", { label: label }, children);
119
+ }
120
+ function Option({ value, children }) {
121
+ return React__default.createElement("option", { value: value }, children);
122
+ }
123
+
124
+ const SvgArrowBack = (props) => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1.25em", height: "1.25em", viewBox: "0 0 20 20", ...props },
125
+ React.createElement("path", { fill: "currentColor", d: "m10 16-6-6 6-6 1.063 1.063L6.875 9.25H16v1.5H6.875l4.188 4.188L10 16Z" })));
126
+
127
+ const SvgArrowForward = (props) => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1.25em", height: "1.25em", viewBox: "0 0 20 20", ...props },
128
+ React.createElement("path", { fill: "currentColor", d: "m10 16.075-1.227-1.227 3.983-3.983h-8.83v-1.73h8.83L8.773 5.152 10 3.925 16.075 10 10 16.075Z" })));
129
+
130
+ /** A button with just text */
131
+ function Button({ children, onClick, appearance }) {
132
+ const className = ["kth-button", appearance].join(" ");
133
+ return (React__default.createElement("button", { onClick: onClick, className: className }, children));
134
+ }
135
+ function BackButton({ children, onClick }) {
136
+ return (React__default.createElement("button", { onClick: onClick, className: "kth-button tertiary" },
137
+ React__default.createElement(SvgArrowBack, null),
138
+ children));
139
+ }
140
+ function ForwardButton({ children, onClick }) {
141
+ return (React__default.createElement("button", { onClick: onClick, className: "kth-button primary" },
142
+ children,
143
+ React__default.createElement(SvgArrowForward, null)));
144
+ }
145
+
146
+ const SvgCheck = (props) => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1.25em", height: "1.25em", viewBox: "0 0 20 20", ...props },
147
+ React.createElement("path", { fill: "currentColor", d: "M8.23 14.063 4.707 10.52 5.75 9.479l2.48 2.459 6.02-6L15.292 7l-7.063 7.063Z" })));
148
+
149
+ export { BackButton, Button, ContentTabs, ForwardButton, SvgArrowBack as IconArrowBack, SvgCheck as IconCheck, NavigationTabs, Option, OptionGroup, Select, Tab };
@@ -1,10 +1,9 @@
1
1
  import type { StoryObj } from "@storybook/react";
2
- import { ContentTabs } from "../components/Tabs";
3
2
  import "../../scss/globals.scss";
4
3
  import "../../scss/components/content-tabs.scss";
5
4
  declare const meta: {
6
5
  title: string;
7
- component: typeof ContentTabs;
6
+ component: any;
8
7
  tags: string[];
9
8
  };
10
9
  export default meta;
package/package.json CHANGED
@@ -1,14 +1,24 @@
1
1
  {
2
2
  "name": "@kth/style",
3
- "version": "0.0.12",
3
+ "version": "0.1.0",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
+ "exports": {
7
+ ".": {
8
+ "import": "./dist/esm/index.js",
9
+ "require": "./dist/cjs/index.js"
10
+ },
11
+ "./react": {
12
+ "import": "./dist/esm/react.js",
13
+ "require": "./dist/cjs/react.js"
14
+ },
15
+ "./scss/*": "./scss/*"
16
+ },
6
17
  "scripts": {
7
18
  "clean": "rm -rf dist",
8
19
  "dev": "storybook dev -p 6006",
9
- "build": "npm run clean && npm run build:components && npm run build:storybook",
20
+ "build": "npm run clean && npm run build:components",
10
21
  "build:components": "rollup -c",
11
- "build:storybook": "storybook build -o ../../apps/style-web/public/storybook",
12
22
  "test": "npm run test:lint",
13
23
  "test:lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
14
24
  },
@@ -47,5 +57,5 @@
47
57
  "peerDependencies": {
48
58
  "react": "*"
49
59
  },
50
- "gitHead": "6774939491027282851d19e5ae9022183f98cfe5"
60
+ "gitHead": "8768a00294cdeeb082cf4b043f192dc64aaec5fb"
51
61
  }
@@ -1,72 +1,62 @@
1
- @use "../tokens/" as t;
1
+ @use "../tokens/spacing";
2
+ @use "../tokens/icons";
3
+ @use "sass:math";
4
+
5
+ // TODO: move to its own tokens
6
+ $border-radius: spacing.$space-4;
7
+ $secondary-border-width: math.div(1rem, 16);
2
8
 
3
9
  @layer kth-style {
4
10
  .kth-button {
5
- padding-inline: var(--kth-0-clickable-padding-inline);
6
- border-radius: t.$space-4;
7
- display: inline-flex;
8
- gap: t.$space-4;
11
+ border-radius: $border-radius;
12
+ display: flex;
9
13
  align-items: center;
14
+ gap: spacing.$space-4;
10
15
 
11
- &.primary {
12
- background: t.$color-primary-5;
13
- color: t.$color-neutral-0;
16
+ &.primary,
17
+ &.success,
18
+ &.danger,
19
+ &.next {
20
+ color: var(--color-on-primary);
14
21
  border: none;
15
- padding-block: var(--kth-0-clickable-padding-block);
16
- font-weight: t.$font-weight-bold;
17
-
18
- &:hover {
19
- background: t.$color-primary-6;
20
- }
22
+ padding-block: var(--space-inner-block);
23
+ padding-inline: var(--space-inner-inline);
24
+ font: var(--font-label-s);
21
25
  }
22
26
 
23
- &.secondary {
24
- background: transparent;
25
- color: t.$color-neutral-10;
26
- border: 1px solid t.$color-neutral-10;
27
- padding-block: calc(var(--kth-0-clickable-padding-block) - 1px);
27
+ &.primary,
28
+ &.next {
29
+ background: var(--color-primary);
28
30
  }
29
31
 
30
- &.tertiary {
31
- padding-inline: 0;
32
- background: transparent;
33
- color: t.$color-primary-5;
34
- border: none;
35
- padding-block: calc(var(--kth-0-clickable-padding-block));
36
-
37
- &:hover {
38
- text-decoration: underline;
39
- }
32
+ &.success {
33
+ background: var(--color-success, green);
40
34
  }
41
- }
42
-
43
- .kth-select,
44
- .kth-input {
45
- margin-block: t.$space-24;
46
35
 
47
- label {
48
- display: inline-block;
49
- font-weight: 600;
36
+ &.danger {
37
+ background: var(--color-error, red);
50
38
  }
51
39
 
52
- p {
53
- margin: t.$space-4 0;
40
+ &.secondary,
41
+ &.previous {
42
+ --border-width: #{$secondary-border-width};
43
+ border-color: var(--color-secondary);
44
+ border-width: var(--border-width);
45
+ border-style: solid;
46
+
47
+ background: transparent;
48
+ padding-block: calc(var(--space-inner-block) - var(--border-width));
49
+ padding-inline: calc(var(--space-inner-inline) - var(--border-width));
54
50
  }
55
51
 
56
- select {
57
- margin-top: t.$space-8;
58
- padding-inline: var(--kth-0-clickable-padding-inline);
59
- padding-block: calc(var(--kth-0-clickable-padding-block) - 1px);
60
- border-radius: t.$space-4;
61
- border: 1px solid t.$color-neutral-10;
52
+ &.next::after {
53
+ @include icons.icon-arrow-right-500;
54
+ background-color: var(--color-on-primary);
62
55
  }
63
56
 
64
- input {
65
- margin-top: t.$space-8;
66
- padding-inline: var(--kth-0-clickable-padding-inline);
67
- padding-block: calc(var(--kth-0-clickable-padding-block) - 1px);
68
- border-radius: t.$space-4;
69
- border: 1px solid t.$color-neutral-10;
57
+ &.previous::before {
58
+ @include icons.icon-arrow-left-300;
59
+ background-color: var(--color-secondary);
70
60
  }
71
61
  }
72
62
  }
@@ -0,0 +1,63 @@
1
+ @use "../tokens/spacing.scss";
2
+ @use "../tokens/icons.scss";
3
+ @use "sass:math";
4
+
5
+ // TODO: move to its own tokens
6
+ $secondary-border-width: math.div(1rem, 16);
7
+ $input-size: spacing.$space-24;
8
+ $input-inner-size: spacing.$space-16;
9
+
10
+ @layer kth-style {
11
+ input[type="checkbox"],
12
+ input[type="radio"] {
13
+ appearance: none;
14
+ -webkit-appearance: none;
15
+ background-color: transparent;
16
+ margin-inline: 0;
17
+ margin-block: var(--space-inner-block);
18
+ font: inherit;
19
+ width: $input-size;
20
+ height: $input-size;
21
+ border-width: var(--border-width);
22
+ border-style: solid;
23
+ border-color: currentColor;
24
+ display: grid;
25
+ place-content: center;
26
+
27
+ &::before {
28
+ content: "";
29
+ transform: scale(0);
30
+ }
31
+
32
+ &:checked::before {
33
+ transform: scale(1);
34
+ }
35
+ }
36
+
37
+ input[type="checkbox"] {
38
+ border-radius: spacing.$space-4;
39
+
40
+ &::before {
41
+ width: $input-size;
42
+ height: $input-size;
43
+ box-shadow: inset $input-size $input-size var(--color-primary);
44
+
45
+ -webkit-mask-image: icons.$icon-check;
46
+ mask-image: icons.$icon-check;
47
+
48
+ // Windows High Contrast Mode
49
+ background-color: CanvasText;
50
+ }
51
+ }
52
+
53
+ input[type="radio"] {
54
+ border-radius: 100%;
55
+
56
+ &::before {
57
+ width: $input-inner-size;
58
+ height: $input-inner-size;
59
+ background-color: var(--color-primary);
60
+ border-radius: 100%;
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,40 @@
1
+ @use "../tokens/spacing.scss";
2
+ @use "../tokens/icons.scss";
3
+
4
+ $border-width: spacing.$space-4;
5
+
6
+ @layer kth-style {
7
+ .kth-details {
8
+ summary {
9
+ color: var(--color-tertiary);
10
+ padding-block: var(--space-inner-block);
11
+ cursor: pointer;
12
+ // padding-inline-start: spacing.$space-20;
13
+ list-style: none;
14
+ font: inherit;
15
+ display: grid;
16
+ grid-template-columns: 1.5rem auto;
17
+
18
+ &::before {
19
+ @include icons.icon-caret-right;
20
+ background-color: var(--color-tertiary);
21
+ margin-block: spacing.$space-2;
22
+ }
23
+
24
+ &:hover {
25
+ text-decoration: underline;
26
+ }
27
+ }
28
+
29
+ &[open] summary::before {
30
+ rotate: 0deg;
31
+ }
32
+
33
+ &__content {
34
+ border-inline-start: $border-width solid var(--color-tertiary);
35
+ padding-inline-start: spacing.$space-16;
36
+ padding-block: spacing.$space-8;
37
+ margin-inline-start: spacing.$space-8;
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,60 @@
1
+ @use "../tokens/spacing.scss";
2
+ @use "../tokens/icons.scss";
3
+ @use "sass:math";
4
+
5
+ $secondary-border-width: math.div(1rem, 16);
6
+ $input-size: 1.5rem;
7
+
8
+ // Coded as <fieldset>
9
+ .kth-fieldset {
10
+ border: 0;
11
+ padding: 0;
12
+ margin-inline: 0;
13
+ margin-block: spacing.$space-24;
14
+
15
+ > legend {
16
+ font: var(--font-label-s);
17
+ }
18
+
19
+ &__description {
20
+ margin-block: spacing.$space-4;
21
+ margin-inline: 0;
22
+ }
23
+
24
+ &__options {
25
+ --border-width: #{$secondary-border-width};
26
+ border-width: var(--border-width);
27
+ border-style: solid;
28
+ border-color: transparent;
29
+ margin-block: calc(0 - var(--border-width));
30
+ padding-inline: calc(#{spacing.$space-8} - var(--border-width));
31
+ }
32
+
33
+ &.error &__options {
34
+ border-color: var(--color-error, red);
35
+ }
36
+
37
+ &.error &__error {
38
+ margin-block: spacing.$space-4;
39
+ color: var(--color-error, red);
40
+ }
41
+
42
+ &__option {
43
+ display: grid;
44
+ grid-template-columns: $input-size auto;
45
+ gap: spacing.$space-0 spacing.$space-12;
46
+
47
+ :not(input) {
48
+ grid-column-start: 2;
49
+ }
50
+
51
+ label {
52
+ display: inline-block;
53
+ padding-block: var(--space-inner-block);
54
+ }
55
+ }
56
+
57
+ &__option-description {
58
+ margin-block-end: spacing.$space-16;
59
+ }
60
+ }
@@ -0,0 +1,25 @@
1
+ @use "../tokens/spacing.scss";
2
+ @use "../tokens/icons.scss";
3
+ $secondary-border-width: 1rem/16;
4
+
5
+ @layer kth-style {
6
+ .kth-form-control {
7
+ margin-block: spacing.$space-24;
8
+
9
+ > label {
10
+ font: var(--font-label-s);
11
+ display: block;
12
+ margin-block: spacing.$space-4;
13
+ }
14
+
15
+ > .kth-form-control__description {
16
+ margin-block: spacing.$space-4;
17
+ }
18
+
19
+ // Preferred to select via
20
+ // :has([aria-invalid="true"])... but Firefox does not support `has` yet
21
+ &.error .kth-form-control__error {
22
+ color: var(--color-error, red);
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,38 @@
1
+ @use "../tokens/spacing";
2
+ @use "../tokens/icons";
3
+
4
+ .kth-header-menu-item {
5
+ color: var(--color-primary);
6
+ text-decoration: none;
7
+ display: flex;
8
+ align-items: center;
9
+ gap: spacing.gap(4);
10
+
11
+ // Fixes for buttons:
12
+ border: none;
13
+ background: none;
14
+ padding: 0;
15
+
16
+ span {
17
+ padding-block: spacing.padding(8);
18
+ padding-block-end: spacing.padding(4);
19
+ border-bottom: transparent spacing.$space-4 solid;
20
+ }
21
+
22
+ &:hover span {
23
+ border-color: var(--color-primary);
24
+ }
25
+
26
+ &.dropdown::after {
27
+ @include icons.icon-caret-down;
28
+ background-color: var(--color-primary);
29
+ }
30
+ &.search::before {
31
+ @include icons.icon-search;
32
+ background-color: var(--color-primary);
33
+ }
34
+ &.language::before {
35
+ @include icons.icon-globe;
36
+ background-color: var(--color-primary);
37
+ }
38
+ }
@@ -0,0 +1,21 @@
1
+ @use "../tokens/spacing.scss";
2
+ @use "../tokens/icons.scss";
3
+ $secondary-border-width: 1rem/16;
4
+
5
+ @layer kth-style {
6
+ .kth-input {
7
+ --border-width: #{$secondary-border-width};
8
+
9
+ padding-inline: var(--space-inner-inline);
10
+ padding-block: calc(var(--space-inner-block) - var(--border-width));
11
+ border-radius: spacing.$space-4;
12
+ border-width: var(--border-width);
13
+ border-style: solid;
14
+ border-color: var(--color-secondary);
15
+
16
+ &[aria-invalid="true"] {
17
+ color: var(--color-error, red);
18
+ border-color: var(--color-error, red);
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,105 @@
1
+ @use "../tokens/colors.scss";
2
+ @use "../tokens/spacing.scss";
3
+ @use "../utils/mixins.scss";
4
+
5
+ @mixin horizontal-list {
6
+ display: flex;
7
+ list-style: none;
8
+ padding: 0;
9
+ margin: 0;
10
+
11
+ > :where(li) {
12
+ padding: 0;
13
+ margin: 0;
14
+ }
15
+ }
16
+
17
+ .kth-main-header.intranet {
18
+ @include colors.theme-intranet;
19
+
20
+ .kth-main-header__logotype.white {
21
+ display: none;
22
+ }
23
+ }
24
+ .kth-main-header.external {
25
+ @include colors.theme-inverse;
26
+
27
+ .kth-main-header__logotype.blue {
28
+ display: none;
29
+ }
30
+ }
31
+ .kth-main-header.student-web {
32
+ @include colors.theme-student-web;
33
+
34
+ .kth-main-header__logotype.white {
35
+ display: none;
36
+ }
37
+ }
38
+
39
+ .kth-main-header {
40
+ background: var(--color-background);
41
+ padding-block: spacing.padding(16);
42
+ position: relative;
43
+
44
+ &__container {
45
+ @include mixins.container;
46
+ display: flex;
47
+ align-items: center;
48
+ }
49
+
50
+ &__logotype {
51
+ width: 4rem;
52
+ height: 4rem;
53
+ border: none;
54
+ margin: 0;
55
+ margin-inline-end: spacing.margin(32);
56
+ padding: 0;
57
+
58
+ // Defensive measurement:
59
+ // - We prevent logo from shrinking
60
+ flex: 0 0 auto;
61
+
62
+ > img {
63
+ block-size: 100%;
64
+ inline-size: 100%;
65
+ }
66
+ }
67
+
68
+ &__mega-menu {
69
+ > ul {
70
+ @include horizontal-list();
71
+
72
+ // Defensive measurements:
73
+ // - `flex-wrap: wrap` makes sure that all menus are displayed decently
74
+ // - `0` in gap (block axis) makes sure that menu spacing is ok all the time
75
+ flex-wrap: wrap;
76
+ gap: 0 spacing.$space-16;
77
+ }
78
+ }
79
+
80
+ &__tools {
81
+ @include horizontal-list();
82
+ flex-wrap: wrap;
83
+ gap: 0 spacing.$space-16;
84
+ justify-content: flex-end;
85
+ margin-inline-start: auto;
86
+ }
87
+
88
+ &__mega-menu--collapsable {
89
+ display: none;
90
+ }
91
+ }
92
+
93
+ @media (width < 64rem) {
94
+ .kth-main-header__logotype {
95
+ margin-inline-end: 0;
96
+ }
97
+
98
+ .kth-main-header__mega-menu {
99
+ display: none;
100
+ }
101
+
102
+ .kth-main-header__mega-menu--collapsable {
103
+ display: block;
104
+ }
105
+ }