@ndla/ui 16.2.0 → 18.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.
Files changed (121) hide show
  1. package/es/Article/Article.js +3 -3
  2. package/es/Article/ArticleFavoritesButton.js +4 -3
  3. package/es/Breadcrumblist/Breadcrumblist.js +7 -7
  4. package/es/Frontpage/FrontpageAllSubjects.js +8 -8
  5. package/es/Masthead/Masthead.js +2 -3
  6. package/es/Masthead/MastheadAuthModal.js +8 -3
  7. package/es/Messages/MessageBanner.js +75 -0
  8. package/es/Messages/MessageBox.js +123 -0
  9. package/es/{MessageBox → Messages}/MessageBoxTag.js +1 -1
  10. package/es/{MessageBox → Messages}/index.js +3 -3
  11. package/es/MyNdla/Resource/Folder.js +7 -7
  12. package/es/MyNdla/index.js +1 -2
  13. package/es/Programme/Programme.js +6 -6
  14. package/es/Programme/ProgrammeSubjects.js +2 -2
  15. package/es/Resource/ListResource.js +6 -6
  16. package/es/Resource/resourceComponents.js +8 -8
  17. package/es/TagSelector/SuggestionInput.js +111 -56
  18. package/es/TagSelector/Suggestions.js +19 -15
  19. package/es/TagSelector/TagSelector.js +8 -7
  20. package/es/Topic/Topic.js +22 -24
  21. package/es/TopicMenu/TopicMenu.js +2 -4
  22. package/es/TreeStructure/FolderItem.js +5 -5
  23. package/es/TreeStructure/FolderItems.js +3 -3
  24. package/es/TreeStructure/TreeStructure.js +51 -73
  25. package/es/TreeStructure/keyboardNavigation/keyboardNavigation.js +23 -11
  26. package/es/index.js +2 -2
  27. package/es/locale/messages-en.js +9 -1
  28. package/es/locale/messages-nb.js +10 -2
  29. package/es/locale/messages-nn.js +11 -3
  30. package/es/locale/messages-se.js +10 -2
  31. package/es/locale/messages-sma.js +10 -2
  32. package/lib/Article/Article.js +3 -3
  33. package/lib/Article/ArticleFavoritesButton.js +4 -3
  34. package/lib/Breadcrumblist/Breadcrumblist.js +7 -7
  35. package/lib/Frontpage/FrontpageAllSubjects.js +9 -9
  36. package/lib/Masthead/Masthead.js +2 -3
  37. package/lib/Masthead/MastheadAuthModal.js +14 -7
  38. package/lib/Messages/MessageBanner.d.ts +16 -0
  39. package/lib/Messages/MessageBanner.js +78 -0
  40. package/lib/{MessageBox → Messages}/MessageBox.d.ts +6 -14
  41. package/lib/Messages/MessageBox.js +128 -0
  42. package/lib/{MessageBox → Messages}/MessageBoxTag.d.ts +0 -0
  43. package/lib/{MessageBox → Messages}/MessageBoxTag.js +1 -1
  44. package/lib/{MessageBox → Messages}/index.d.ts +3 -3
  45. package/lib/Messages/index.js +31 -0
  46. package/lib/MyNdla/Resource/Folder.js +7 -7
  47. package/lib/MyNdla/index.d.ts +1 -2
  48. package/lib/MyNdla/index.js +0 -8
  49. package/lib/Programme/Programme.js +6 -6
  50. package/lib/Programme/ProgrammeSubjects.js +3 -3
  51. package/lib/Resource/ListResource.js +6 -6
  52. package/lib/Resource/resourceComponents.js +8 -8
  53. package/lib/TagSelector/SuggestionInput.js +111 -57
  54. package/lib/TagSelector/Suggestions.js +26 -23
  55. package/lib/TagSelector/TagSelector.js +8 -7
  56. package/lib/Topic/Topic.js +22 -24
  57. package/lib/TopicMenu/TopicMenu.js +2 -4
  58. package/lib/TreeStructure/FolderItem.js +5 -5
  59. package/lib/TreeStructure/FolderItems.js +3 -3
  60. package/lib/TreeStructure/TreeStructure.js +52 -73
  61. package/lib/TreeStructure/TreeStructure.types.d.ts +2 -2
  62. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.js +23 -11
  63. package/lib/TreeStructure/keyboardNavigation/keyboardNavigation.types.d.ts +1 -1
  64. package/lib/index.d.ts +2 -2
  65. package/lib/index.js +6 -13
  66. package/lib/locale/messages-en.d.ts +8 -0
  67. package/lib/locale/messages-en.js +9 -1
  68. package/lib/locale/messages-nb.d.ts +8 -0
  69. package/lib/locale/messages-nb.js +10 -2
  70. package/lib/locale/messages-nn.d.ts +8 -0
  71. package/lib/locale/messages-nn.js +11 -3
  72. package/lib/locale/messages-se.d.ts +8 -0
  73. package/lib/locale/messages-se.js +10 -2
  74. package/lib/locale/messages-sma.d.ts +8 -0
  75. package/lib/locale/messages-sma.js +10 -2
  76. package/package.json +13 -13
  77. package/src/Article/Article.tsx +1 -1
  78. package/src/Article/ArticleFavoritesButton.tsx +4 -3
  79. package/src/Breadcrumblist/Breadcrumblist.tsx +1 -1
  80. package/src/Frontpage/FrontpageAllSubjects.tsx +1 -1
  81. package/src/Masthead/Masthead.tsx +3 -6
  82. package/src/Masthead/MastheadAuthModal.tsx +9 -0
  83. package/src/Messages/MessageBanner.tsx +66 -0
  84. package/src/Messages/MessageBox.tsx +156 -0
  85. package/src/{MessageBox → Messages}/MessageBoxTag.tsx +0 -0
  86. package/src/{MessageBox → Messages}/index.ts +3 -3
  87. package/src/MyNdla/Resource/Folder.tsx +2 -2
  88. package/src/MyNdla/index.ts +1 -2
  89. package/src/Programme/Programme.tsx +1 -1
  90. package/src/Programme/ProgrammeSubjects.tsx +1 -1
  91. package/src/Resource/ListResource.tsx +1 -1
  92. package/src/Resource/resourceComponents.tsx +3 -0
  93. package/src/TagSelector/SuggestionInput.tsx +90 -24
  94. package/src/TagSelector/Suggestions.tsx +14 -0
  95. package/src/TagSelector/TagSelector.tsx +6 -4
  96. package/src/Topic/Topic.tsx +2 -2
  97. package/src/TopicMenu/TopicMenu.jsx +2 -2
  98. package/src/TreeStructure/FolderItem.tsx +5 -2
  99. package/src/TreeStructure/FolderItems.tsx +1 -1
  100. package/src/TreeStructure/TreeStructure.tsx +33 -35
  101. package/src/TreeStructure/TreeStructure.types.ts +2 -2
  102. package/src/TreeStructure/keyboardNavigation/keyboardNavigation.ts +7 -7
  103. package/src/TreeStructure/keyboardNavigation/keyboardNavigation.types.ts +1 -1
  104. package/src/index.ts +2 -2
  105. package/src/locale/messages-en.ts +10 -1
  106. package/src/locale/messages-nb.ts +10 -2
  107. package/src/locale/messages-nn.ts +11 -3
  108. package/src/locale/messages-se.ts +10 -2
  109. package/src/locale/messages-sma.ts +10 -2
  110. package/es/MessageBox/MessageBox.js +0 -220
  111. package/es/MyNdla/Navigation/VerticalNavigation.js +0 -51
  112. package/es/MyNdla/Navigation/index.js +0 -2
  113. package/lib/MessageBox/MessageBox.js +0 -234
  114. package/lib/MessageBox/index.js +0 -35
  115. package/lib/MyNdla/Navigation/VerticalNavigation.d.ts +0 -10
  116. package/lib/MyNdla/Navigation/VerticalNavigation.js +0 -61
  117. package/lib/MyNdla/Navigation/index.d.ts +0 -2
  118. package/lib/MyNdla/Navigation/index.js +0 -15
  119. package/src/MessageBox/MessageBox.tsx +0 -201
  120. package/src/MyNdla/Navigation/VerticalNavigation.tsx +0 -93
  121. package/src/MyNdla/Navigation/index.ts +0 -2
@@ -10,7 +10,7 @@ import React, { ReactNode, useEffect, useRef } from 'react';
10
10
  import BEMHelper from 'react-bem-helper';
11
11
  import { WithTranslation, withTranslation } from 'react-i18next';
12
12
  import { DisplayOnPageYOffset } from '../Animation';
13
- import { MessageBox, MessageBoxType } from '../MessageBox';
13
+ import { MessageBanner } from '../Messages';
14
14
 
15
15
  const classes = new BEMHelper({
16
16
  name: 'masthead',
@@ -95,12 +95,9 @@ export const Masthead = ({
95
95
  )}
96
96
  <div id="masthead" {...classes('', { fixed: !!fixed, infoContent: !!infoContent, ndlaFilm: !!ndlaFilm })}>
97
97
  {messages?.map((message) => (
98
- <MessageBox
99
- type={MessageBoxType.masthead}
100
- showCloseButton={message.closable}
101
- onClose={() => onCloseAlert?.(message.number)}>
98
+ <MessageBanner showCloseButton={message.closable} onClose={() => onCloseAlert?.(message.number)}>
102
99
  {message.content}
103
- </MessageBox>
100
+ </MessageBanner>
104
101
  ))}
105
102
  {infoContent && (
106
103
  <DisplayOnPageYOffset yOffsetMin={0} yOffsetMax={90}>
@@ -10,6 +10,8 @@ import React from 'react';
10
10
  import styled from '@emotion/styled';
11
11
  import Button from '@ndla/button';
12
12
  import { Feide } from '@ndla/icons/common';
13
+ import { colors, fonts, spacing } from '@ndla/core';
14
+ import { useTranslation } from 'react-i18next';
13
15
 
14
16
  import AuthModal, { AuthModalProps } from '../User/AuthModal';
15
17
 
@@ -18,7 +20,12 @@ type FeideWrapperProps = {
18
20
  };
19
21
 
20
22
  const StyledButton = styled(Button)<FeideWrapperProps>`
23
+ font-weight: ${fonts.weight.semibold};
24
+ display: flex;
25
+ align-items: center;
21
26
  .feide-icon svg {
27
+ margin-left: ${spacing.xsmall};
28
+ fill: ${colors.brand.primary};
22
29
  color: ${(props) => (props.inverted ? `#ffffff` : `#000000`)};
23
30
  width: 22px;
24
31
  height: 22px;
@@ -35,11 +42,13 @@ interface Props extends AuthModalProps {
35
42
  }
36
43
 
37
44
  const MastheadAuthModal = ({ inverted, ...rest }: Props) => {
45
+ const { t } = useTranslation();
38
46
  return (
39
47
  <AuthModal
40
48
  {...rest}
41
49
  activateButton={
42
50
  <StyledButton inverted={inverted} ghostPill={!inverted} ghostPillInverted={inverted} aria-label="Feide">
51
+ {t('myNdla.myNDLA')}
43
52
  <span className="feide-icon">
44
53
  <Feide />
45
54
  </span>
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Copyright (c) 2022-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import React from 'react';
10
+ import styled from '@emotion/styled';
11
+ import { colors, spacing } from '@ndla/core';
12
+ // @ts-ignore
13
+ import { Remarkable } from 'remarkable';
14
+ import { CloseButton } from '@ndla/button';
15
+
16
+ interface WrapperProps {
17
+ small?: boolean;
18
+ }
19
+
20
+ const MessageBannerWrapper = styled.div<WrapperProps>`
21
+ display: grid;
22
+ grid-template-areas: '. content closebutton';
23
+ grid-template-columns: minmax(30px, 1fr) minmax(0, auto) minmax(30px, 1fr);
24
+ padding: ${({ small }) => (small ? spacing.xxsmall : spacing.small)};
25
+ background: ${colors.support.yellowLight};
26
+ color: ${colors.brand.greyDark};
27
+ border: none;
28
+ `;
29
+
30
+ const TextWrapper = styled.div`
31
+ display: flex;
32
+ grid-area: content;
33
+ align-items: center;
34
+ & p {
35
+ margin: 0;
36
+ }
37
+ `;
38
+
39
+ const StyledClosebutton = styled(CloseButton)`
40
+ grid-area: closebutton;
41
+ padding: 0;
42
+ justify-self: flex-end;
43
+ align-self: flex-start;
44
+ `;
45
+
46
+ interface Props {
47
+ small?: boolean;
48
+ children?: string;
49
+ showCloseButton?: boolean;
50
+ onClose?: () => void;
51
+ }
52
+
53
+ const markdown = new Remarkable({ breaks: true });
54
+ markdown.inline.ruler.enable(['sub', 'sup']);
55
+ markdown.block.ruler.disable(['list', 'table']);
56
+
57
+ const MessageBanner = ({ children, onClose, showCloseButton, small }: Props) => {
58
+ return (
59
+ <MessageBannerWrapper small={small}>
60
+ <TextWrapper dangerouslySetInnerHTML={{ __html: markdown.render(children) }} />
61
+ {showCloseButton && <StyledClosebutton onClick={onClose} />}
62
+ </MessageBannerWrapper>
63
+ );
64
+ };
65
+
66
+ export default MessageBanner;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Copyright (c) 2022-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import React from 'react';
10
+ import styled from '@emotion/styled';
11
+ import { breakpoints, colors, fonts, mq, spacing } from '@ndla/core';
12
+ import { InformationOutline, HumanMaleBoard, Forward, WarningOutline } from '@ndla/icons/common';
13
+ import { WithTranslation, withTranslation } from 'react-i18next';
14
+
15
+ // @ts-ignore
16
+ import { Remarkable } from 'remarkable';
17
+ import { CloseButton } from '@ndla/button';
18
+ import { css } from '@emotion/core';
19
+
20
+ const markdown = new Remarkable({ breaks: true });
21
+ markdown.inline.ruler.enable(['sub', 'sup']);
22
+ markdown.block.ruler.disable(['list', 'table']);
23
+
24
+ type MessageBoxType = 'ghost' | 'danger';
25
+
26
+ interface StyledProps {
27
+ type?: MessageBoxType;
28
+ }
29
+
30
+ const MessageBoxWrapper = styled.div<StyledProps>`
31
+ display: flex;
32
+ padding: ${spacing.small};
33
+ font-family: ${fonts.sans};
34
+ border-radius: 5px;
35
+ background: ${colors.support.yellowLight};
36
+ color: ${colors.brand.greyDark};
37
+
38
+ ${fonts.sizes('18px')};
39
+ ${mq.range({ until: breakpoints.tabletWide })} {
40
+ ${fonts.sizes('16px')};
41
+ }
42
+
43
+ ${({ type }) =>
44
+ type === 'ghost' &&
45
+ css`
46
+ background: transparent;
47
+ border: 1px solid ${colors.brand.neutral7};
48
+ color: ${colors.brand.greyDark};
49
+ `}
50
+
51
+ ${({ type }) =>
52
+ type === 'danger' &&
53
+ css`
54
+ background: ${colors.support.redLightest};
55
+ color: ${colors.support.red};
56
+ `}
57
+ `;
58
+
59
+ const InfoWrapper = styled.div`
60
+ display: flex;
61
+ flex-direction: row;
62
+ flex: 1;
63
+ padding: ${spacing.small};
64
+ padding-right: 0;
65
+ `;
66
+
67
+ const TextWrapper = styled.div`
68
+ & p {
69
+ margin: 0;
70
+ }
71
+ `;
72
+
73
+ const IconWrapper = styled.div`
74
+ display: flex;
75
+ align-items: flex-start;
76
+ padding-right: ${spacing.small};
77
+
78
+ svg {
79
+ width: 24px;
80
+ height: 24px;
81
+ }
82
+ `;
83
+
84
+ const LinkWrapper = styled.div`
85
+ display: flex;
86
+ flex-wrap: wrap;
87
+ gap: ${spacing.normal};
88
+ padding-top: ${spacing.nsmall};
89
+
90
+ svg {
91
+ flex-shrink: 0;
92
+ }
93
+ `;
94
+
95
+ const Link = styled.a`
96
+ display: flex;
97
+ align-items: center;
98
+ color: ${colors.brand.primary};
99
+ gap: ${spacing.xxsmall};
100
+ font-weight: ${fonts.weight.semibold};
101
+ `;
102
+
103
+ const StyledClosebutton = styled(CloseButton)`
104
+ padding: 0;
105
+ `;
106
+
107
+ interface LinkProps {
108
+ href?: string;
109
+ text?: string;
110
+ }
111
+
112
+ interface Props {
113
+ type?: MessageBoxType;
114
+ children?: string;
115
+ links?: LinkProps[];
116
+ showCloseButton?: boolean;
117
+ onClose?: () => void;
118
+ }
119
+
120
+ const Icon = ({ type }: StyledProps) => {
121
+ if (type === 'ghost') {
122
+ return <HumanMaleBoard />;
123
+ }
124
+ if (type === 'danger') {
125
+ return <WarningOutline />;
126
+ }
127
+ return <InformationOutline />;
128
+ };
129
+
130
+ export const MessageBox = ({ type, children = '', links, showCloseButton, onClose }: Props & WithTranslation) => {
131
+ return (
132
+ <MessageBoxWrapper type={type}>
133
+ <InfoWrapper>
134
+ <IconWrapper>
135
+ <Icon type={type} />
136
+ </IconWrapper>
137
+ <div>
138
+ <TextWrapper dangerouslySetInnerHTML={{ __html: markdown.render(children) }} />
139
+ {links && (
140
+ <LinkWrapper>
141
+ {links.map((x) => (
142
+ <Link href={x.href}>
143
+ <span>{x.text}</span>
144
+ <Forward />
145
+ </Link>
146
+ ))}
147
+ </LinkWrapper>
148
+ )}
149
+ </div>
150
+ </InfoWrapper>
151
+ {showCloseButton && <StyledClosebutton onClick={onClose} />}
152
+ </MessageBoxWrapper>
153
+ );
154
+ };
155
+
156
+ export default withTranslation()(MessageBox);
File without changes
@@ -6,8 +6,8 @@
6
6
  *
7
7
  */
8
8
 
9
- import MessageBox from './MessageBox';
10
9
  import MessageBoxTag from './MessageBoxTag';
11
- import { MessageBoxType } from './MessageBox';
10
+ import MessageBox from './MessageBox';
11
+ import MessageBanner from './MessageBanner';
12
12
 
13
- export { MessageBox, MessageBoxTag, MessageBoxType };
13
+ export { MessageBox, MessageBoxTag, MessageBanner };
@@ -59,7 +59,7 @@ const FolderWrapper = styled(SafeLink)`
59
59
  display: flex;
60
60
  align-items: center;
61
61
  padding: ${spacing.small};
62
- border: 1px solid ${colors.brand.light};
62
+ border: 1px solid ${colors.brand.neutral7};
63
63
  border-radius: 2px;
64
64
  box-shadow: none;
65
65
  text-decoration: none;
@@ -124,7 +124,7 @@ const IconCount = ({ type, count, layoutType }: IconCountProps) => {
124
124
  return (
125
125
  <IconCountWrapper type={layoutType}>
126
126
  <Icon aria-label={t(`myNdla.${type}s`)} />
127
- <span>{layoutType === 'block' ? count : t(`myNdla.${type}s`, { count: 3 })}</span>
127
+ <span>{layoutType === 'block' ? count : t(`myNdla.${type}s`, { count })}</span>
128
128
  </IconCountWrapper>
129
129
  );
130
130
  };
@@ -1,4 +1,3 @@
1
1
  import Folder from './Resource/Folder';
2
2
  import FolderInput from './Resource/FolderInput';
3
- import { VerticalNavigation } from './Navigation';
4
- export { VerticalNavigation, Folder, FolderInput };
3
+ export { Folder, FolderInput };
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
5
5
  import LayoutItem, { OneColumn } from '../Layout';
6
6
  import ProgrammeSubjects from './ProgrammeSubjects';
7
7
  import { GradesProps } from './ProgrammeSubjects';
8
- import MessageBox from '../MessageBox/MessageBox';
8
+ import MessageBox from '../Messages/MessageBox';
9
9
  import { NavigationHeading } from '..';
10
10
  const StyledWrapper = styled.div`
11
11
  display: flex;
@@ -12,7 +12,7 @@ import styled from '@emotion/styled';
12
12
  import Button from '@ndla/button';
13
13
  import { breakpoints, mq } from '@ndla/core';
14
14
  import { NavigationBox } from '../Navigation';
15
- import { MessageBox } from '../MessageBox';
15
+ import { MessageBox } from '../Messages';
16
16
 
17
17
  const GradesMenu = styled.div`
18
18
  margin-bottom: 28px;
@@ -47,7 +47,7 @@ const ResourceWrapper = styled(SafeLink)`
47
47
  text-decoration: none;
48
48
  box-shadow: none;
49
49
  padding: ${spacing.small};
50
- border: 1px solid ${colors.brand.light};
50
+ border: 1px solid ${colors.brand.neutral7};
51
51
  border-radius: 2px;
52
52
  color: ${colors.brand.greyDark};
53
53
  gap: 0 ${spacing.small};
@@ -48,6 +48,9 @@ const StyledTagListElement = styled.li`
48
48
  ::before {
49
49
  content: '#';
50
50
  }
51
+ &:hover {
52
+ color: ${colors.brand.primary};
53
+ }
51
54
  `;
52
55
 
53
56
  const StyledTopicList = styled.ul`
@@ -19,6 +19,39 @@ import { uuid } from '@ndla/util';
19
19
  import Suggestions from './Suggestions';
20
20
  import type { TagType } from './TagSelector';
21
21
 
22
+ const SuggestionTextWrapper = styled.div`
23
+ ${fonts.sizes(18)};
24
+ position: absolute;
25
+ display: flex;
26
+ flex-grow: 1;
27
+ left: 0;
28
+ right: 0;
29
+ overflow: hidden;
30
+ max-height: ${spacing.large};
31
+ padding: 8.333px;
32
+ padding-right: ${spacing.large};
33
+ span {
34
+ color: ${colors.brand.grey};
35
+ white-space: nowrap;
36
+ overflow: hidden !important;
37
+ text-overflow: ellipsis;
38
+ &:first-of-type {
39
+ color: transparent;
40
+ }
41
+ }
42
+ `;
43
+
44
+ const SuggestionText = ({ value, suggestionValue }: { value: string; suggestionValue: string }) => (
45
+ <SuggestionTextWrapper>
46
+ {!!value && (
47
+ <>
48
+ <span>{value}</span>
49
+ <span>{suggestionValue.substring(value.length)}</span>
50
+ </>
51
+ )}
52
+ </SuggestionTextWrapper>
53
+ );
54
+
22
55
  const Cross = styled(CrossRaw)`
23
56
  margin-left: ${spacing.xxsmall};
24
57
  `;
@@ -31,15 +64,18 @@ const StyledInput = styled.input`
31
64
  flex-grow: 1;
32
65
  border: 0;
33
66
  outline: none;
34
- background: transparent;
35
67
  ${fonts.sizes(18)};
68
+ z-index: 1;
69
+ position: relative;
70
+ background: transparent;
36
71
  `;
72
+
37
73
  const StyledInputWrapper = styled.div`
38
74
  display: flex;
39
75
  flex-wrap: wrap;
40
76
  gap: ${spacing.xsmall};
41
77
  padding: ${spacing.small};
42
- border: 1px solid ${colors.brand.greyLighter};
78
+ border: 1px solid ${colors.brand.neutral7};
43
79
  transition: border-color ${animations.durations.normal} ease;
44
80
  border-radius: ${misc.borderRadius};
45
81
  &:focus-within {
@@ -50,6 +86,12 @@ const StyledInputWrapper = styled.div`
50
86
  const CombinedInputAndDropdownWrapper = styled.div`
51
87
  display: flex;
52
88
  flex-grow: 1;
89
+ position: relative;
90
+ `;
91
+
92
+ const StyledTagButton = styled(Button)<{ enableTagButtonAnimation: boolean }>`
93
+ ${({ enableTagButtonAnimation }) =>
94
+ enableTagButtonAnimation ? animations.fadeInScaled(animations.durations.slow) : ''}
53
95
  `;
54
96
 
55
97
  interface SuggestionInputProps {
@@ -90,6 +132,7 @@ const SuggestionInput = ({
90
132
  const inputRef = useRef<HTMLInputElement>(null);
91
133
  const containerRef = useRef<HTMLDivElement>(null);
92
134
  const suggestionIdRef = useRef<string>(uuid());
135
+ const initalTags = useRef<string[]>(addedTags.map(({ id }) => id));
93
136
 
94
137
  useEffect(() => {
95
138
  setCurrentHighlightedIndex(0);
@@ -122,7 +165,8 @@ const SuggestionInput = ({
122
165
  <SuggestionInputContainer ref={containerRef}>
123
166
  <StyledInputWrapper>
124
167
  {addedTags.map(({ id, name }) => (
125
- <Button
168
+ <StyledTagButton
169
+ enableTagButtonAnimation={!initalTags.current.includes(id)}
126
170
  aria-label={t('tagSelector.removeTag', { name })}
127
171
  onClick={() => onToggleTag(id)}
128
172
  light
@@ -132,9 +176,12 @@ const SuggestionInput = ({
132
176
  {prefix}
133
177
  {name}
134
178
  <Cross />
135
- </Button>
179
+ </StyledTagButton>
136
180
  ))}
137
181
  <CombinedInputAndDropdownWrapper>
182
+ {suggestions[currentHighlightedIndex] && (
183
+ <SuggestionText value={value} suggestionValue={suggestions[currentHighlightedIndex].name} />
184
+ )}
138
185
  <StyledInput
139
186
  placeholder={t('tagSelector.placeholder')}
140
187
  value={value}
@@ -157,38 +204,57 @@ const SuggestionInput = ({
157
204
  }}
158
205
  ref={inputRef}
159
206
  onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
207
+ if (!['Enter', ' ', 'Tab', 'ArrowDown', 'ArrowUp', 'Backspace'].includes(e.key)) {
208
+ return;
209
+ }
210
+ const trimmedValue = value.replace(/\s/g, '');
160
211
  if (e.key === 'Escape') {
161
212
  setExpanded(false);
162
213
  e.preventDefault();
163
- } else if (e.key === 'Enter' || e.key === 'Tab') {
164
- if (value !== '' || expanded) {
165
- if (suggestions.length > 0) {
166
- if (!hasBeenAdded(suggestions[currentHighlightedIndex].id)) {
167
- onToggleTag(suggestions[currentHighlightedIndex].id);
168
- }
169
- setInputValue('');
170
- if (e.key === 'Enter') {
171
- e.preventDefault();
172
- }
173
- } else {
174
- onCreateTag(value);
175
- setInputValue('');
176
- e.preventDefault();
177
- }
178
- } else if (e.key === 'Enter') {
179
- e.preventDefault();
180
- }
181
- } else if (e.key === 'ArrowUp') {
214
+ return;
215
+ }
216
+ if (e.key === 'Backspace' && trimmedValue === '' && addedTags.length) {
217
+ // Remove the added last tag
218
+ onToggleTag(addedTags[addedTags.length - 1].id);
219
+ return;
220
+ }
221
+ if (e.key === 'ArrowUp') {
182
222
  setCurrentHighlightedIndex(
183
223
  currentHighlightedIndex - 1 < 0 ? suggestions.length - 1 : currentHighlightedIndex - 1,
184
224
  );
185
225
  e.preventDefault();
186
- } else if (e.key === 'ArrowDown') {
226
+ return;
227
+ }
228
+ if (e.key === 'ArrowDown') {
187
229
  setCurrentHighlightedIndex(
188
230
  currentHighlightedIndex + 1 >= suggestions.length ? 0 : currentHighlightedIndex + 1,
189
231
  );
190
232
  e.preventDefault();
233
+ return;
234
+ }
235
+ if (trimmedValue === '' && !expanded) {
236
+ if (e.key === 'Enter' || e.key === ' ') {
237
+ e.preventDefault();
238
+ }
239
+ return;
240
+ }
241
+ if (e.key === 'Enter' || e.key === 'Tab' || e.key === ' ') {
242
+ if (suggestions.length > 0) {
243
+ if (!hasBeenAdded(suggestions[currentHighlightedIndex].id)) {
244
+ onToggleTag(suggestions[currentHighlightedIndex].id);
245
+ } else if (trimmedValue.length < suggestions[currentHighlightedIndex].name.length) {
246
+ onCreateTag(trimmedValue);
247
+ e.preventDefault();
248
+ }
249
+ setInputValue('');
250
+ e.preventDefault();
251
+ return;
252
+ }
253
+ onCreateTag(trimmedValue);
254
+ setInputValue('');
255
+ e.preventDefault();
191
256
  }
257
+ return;
192
258
  }}
193
259
  />
194
260
  <Tooltip tooltip={expanded ? t('tagSelector.hideAllTags') : t('tagSelector.showAllTags')}>
@@ -8,6 +8,7 @@
8
8
 
9
9
  import React from 'react';
10
10
  import styled from '@emotion/styled';
11
+ import { css } from '@emotion/core';
11
12
  import { Check } from '@ndla/icons/editor';
12
13
  import { spacing, colors, misc, animations, fonts, shadows } from '@ndla/core';
13
14
  import Button from '@ndla/button';
@@ -60,6 +61,7 @@ interface SuggestionButtonProps {
60
61
  const SuggestionButton = styled(Button)<SuggestionButtonProps>`
61
62
  display: flex;
62
63
  justify-content: space-between;
64
+ align-items: center;
63
65
  ${fonts.sizes(18)};
64
66
  transition: ${misc.transition.default};
65
67
  font-weight: 400;
@@ -72,6 +74,18 @@ const SuggestionButton = styled(Button)<SuggestionButtonProps>`
72
74
  }
73
75
  }
74
76
  }
77
+ ${({ isHighlighted }) =>
78
+ isHighlighted
79
+ ? css`
80
+ background: ${colors.brand.lighter};
81
+ &:disabled {
82
+ background: ${colors.brand.greyLighter};
83
+ svg {
84
+ fill: ${colors.brand.grey};
85
+ }
86
+ }
87
+ `
88
+ : ''}
75
89
  `;
76
90
 
77
91
  interface Props {
@@ -33,10 +33,12 @@ interface Props {
33
33
  prefix?: string;
34
34
  }
35
35
 
36
- const sortedTags = (tags: TagType[], selectedTags: string[]): TagType[] =>
37
- tags
38
- .filter(({ id }) => selectedTags.some((idSelected) => idSelected === id))
39
- .sort((a, b) => a.name.localeCompare(b.name, 'nb'));
36
+ const sortedTags = (tags: TagType[], selectedTags: string[]): TagType[] => {
37
+ const returnTags = selectedTags
38
+ .map((selectedId) => tags.find(({ id }) => selectedId === id))
39
+ .filter((notUndefined) => notUndefined) as unknown as TagType[];
40
+ return returnTags;
41
+ };
40
42
 
41
43
  const getSuggestions = (tags: TagType[], inputValue: string): TagType[] => {
42
44
  if (inputValue === '') {
@@ -21,7 +21,7 @@ import Loader from './Loader';
21
21
  import { ItemProps } from '../Navigation/NavigationBox';
22
22
  import { NavigationBox } from '../Navigation';
23
23
  import { makeSrcQueryString, ImageCrop, ImageFocalPoint } from '../Image';
24
- import { MessageBox, MessageBoxType } from '../MessageBox';
24
+ import { MessageBox } from '../Messages';
25
25
 
26
26
  type InvertItProps = {
27
27
  invertedStyle?: boolean;
@@ -331,7 +331,7 @@ const Topic = ({
331
331
  </StyledAdditionalResource>
332
332
  )}
333
333
  </TopicHeading>
334
- {messageBox && <MessageBox type={MessageBoxType.medium}>{messageBox}</MessageBox>}
334
+ {messageBox && <MessageBox>{messageBox}</MessageBox>}
335
335
  <TopicIntroduction invertedStyle={invertedStyle}>
336
336
  {renderMarkdown ? parse(renderMarkdown(topic.introduction)) : topic.introduction}
337
337
  </TopicIntroduction>
@@ -28,7 +28,7 @@ import Logo from '../Logo';
28
28
  import FrontpageAllSubjects from '../Frontpage/FrontpageAllSubjects';
29
29
  import NavigationBox from '../Navigation/NavigationBox';
30
30
  import { ProgrammeSubjects } from '../Programme';
31
- import { MessageBox, MessageBoxType } from '../MessageBox';
31
+ import { MessageBanner } from '../Messages';
32
32
 
33
33
  const classes = new BEMHelper({
34
34
  name: 'topic-menu',
@@ -153,7 +153,7 @@ export const TopicMenu = ({
153
153
  return (
154
154
  <nav>
155
155
  {messages?.map((message) => (
156
- <MessageBox type={MessageBoxType.masthead}>{message}</MessageBox>
156
+ <MessageBanner>{message}</MessageBanner>
157
157
  ))}
158
158
  <ModalHeader modifier={['white', 'menu']}>
159
159
  <div {...classes('masthead-left')}>
@@ -42,7 +42,8 @@ const WrapperForFolderChild = styled.div<{ marked: boolean }>`
42
42
  right: ${spacing.xsmall};
43
43
  opacity: ${({ marked }) => (marked ? 1 : 0.25)};
44
44
  &:hover,
45
- &:focus {
45
+ &:focus,
46
+ &:focus-within {
46
47
  opacity: 1;
47
48
  }
48
49
  `;
@@ -170,7 +171,9 @@ const FolderItem = ({
170
171
  {name}
171
172
  </FolderName>
172
173
  {folderChild && (
173
- <WrapperForFolderChild marked={marked}>{folderChild(id, marked ? 0 : -1)}</WrapperForFolderChild>
174
+ <WrapperForFolderChild marked={marked}>
175
+ {folderChild(id, marked || id === focusedFolderId ? 0 : -1)}
176
+ </WrapperForFolderChild>
174
177
  )}
175
178
  </>
176
179
  )}
@@ -53,7 +53,7 @@ const FolderItems = ({
53
53
  <StyledUL role="group" firstLevel={firstLevel}>
54
54
  {data.map(({ name, data: dataChildren, id, url, icon }, _index) => {
55
55
  const newIdPaths = [...idPaths, _index];
56
- const isOpen = openFolders?.has(id);
56
+ const isOpen = openFolders?.includes(id);
57
57
  return (
58
58
  <StyledLI key={id} role="treeitem">
59
59
  <div>