@ndla/ui 28.0.0 → 29.0.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 (86) hide show
  1. package/es/Resource/ListResource.js +8 -8
  2. package/es/TagSelector/Control.js +23 -0
  3. package/es/TagSelector/DropdownIndicator.js +66 -0
  4. package/es/TagSelector/Input.js +19 -0
  5. package/es/TagSelector/Menu.js +26 -0
  6. package/es/TagSelector/MenuList.js +22 -0
  7. package/es/TagSelector/Option.js +55 -0
  8. package/es/TagSelector/SelectContainer.js +18 -0
  9. package/es/TagSelector/TagSelector.js +161 -100
  10. package/es/TagSelector/ValueButton.js +46 -0
  11. package/es/TagSelector/ariaMessages.js +104 -0
  12. package/es/TagSelector/index.js +2 -1
  13. package/es/TagSelector/types.js +0 -0
  14. package/es/TreeStructure/ComboboxButton.js +19 -18
  15. package/es/TreeStructure/TreeStructure.js +8 -8
  16. package/es/locale/messages-en.js +40 -2
  17. package/es/locale/messages-nb.js +40 -2
  18. package/es/locale/messages-nn.js +40 -2
  19. package/es/locale/messages-se.js +40 -2
  20. package/es/locale/messages-sma.js +40 -2
  21. package/lib/Resource/ListResource.js +8 -8
  22. package/lib/TagSelector/Control.d.ts +12 -0
  23. package/lib/TagSelector/Control.js +35 -0
  24. package/lib/TagSelector/DropdownIndicator.d.ts +12 -0
  25. package/lib/TagSelector/DropdownIndicator.js +80 -0
  26. package/lib/TagSelector/Input.d.ts +12 -0
  27. package/lib/TagSelector/Input.js +33 -0
  28. package/lib/TagSelector/Menu.d.ts +12 -0
  29. package/lib/TagSelector/Menu.js +40 -0
  30. package/lib/TagSelector/MenuList.d.ts +13 -0
  31. package/lib/TagSelector/MenuList.js +36 -0
  32. package/lib/TagSelector/Option.d.ts +12 -0
  33. package/lib/TagSelector/Option.js +61 -0
  34. package/lib/TagSelector/SelectContainer.d.ts +12 -0
  35. package/lib/TagSelector/SelectContainer.js +31 -0
  36. package/lib/TagSelector/TagSelector.d.ts +14 -11
  37. package/lib/TagSelector/TagSelector.js +165 -96
  38. package/lib/TagSelector/ValueButton.d.ts +16 -0
  39. package/lib/TagSelector/ValueButton.js +55 -0
  40. package/lib/TagSelector/ariaMessages.d.ts +16 -0
  41. package/lib/TagSelector/ariaMessages.js +113 -0
  42. package/lib/TagSelector/index.d.ts +2 -1
  43. package/lib/TagSelector/index.js +3 -5
  44. package/lib/TagSelector/types.d.ts +11 -0
  45. package/lib/TagSelector/types.js +1 -0
  46. package/lib/TreeStructure/ComboboxButton.js +19 -18
  47. package/lib/TreeStructure/TreeStructure.js +7 -7
  48. package/lib/locale/messages-en.d.ts +40 -2
  49. package/lib/locale/messages-en.js +40 -2
  50. package/lib/locale/messages-nb.d.ts +40 -2
  51. package/lib/locale/messages-nb.js +40 -2
  52. package/lib/locale/messages-nn.d.ts +40 -2
  53. package/lib/locale/messages-nn.js +40 -2
  54. package/lib/locale/messages-se.d.ts +40 -2
  55. package/lib/locale/messages-se.js +40 -2
  56. package/lib/locale/messages-sma.d.ts +40 -2
  57. package/lib/locale/messages-sma.js +40 -2
  58. package/package.json +15 -14
  59. package/src/Resource/ListResource.tsx +1 -1
  60. package/src/TagSelector/Control.tsx +34 -0
  61. package/src/TagSelector/DropdownIndicator.tsx +47 -0
  62. package/src/TagSelector/Input.tsx +31 -0
  63. package/src/TagSelector/Menu.tsx +38 -0
  64. package/src/TagSelector/MenuList.tsx +30 -0
  65. package/src/TagSelector/Option.tsx +53 -0
  66. package/src/TagSelector/SelectContainer.tsx +32 -0
  67. package/src/TagSelector/TagSelector.tsx +105 -84
  68. package/src/TagSelector/ValueButton.tsx +46 -0
  69. package/src/TagSelector/ariaMessages.ts +87 -0
  70. package/src/TagSelector/index.ts +2 -1
  71. package/src/TagSelector/types.ts +12 -0
  72. package/src/TreeStructure/ComboboxButton.tsx +15 -17
  73. package/src/TreeStructure/TreeStructure.tsx +2 -11
  74. package/src/locale/messages-en.ts +41 -2
  75. package/src/locale/messages-nb.ts +41 -2
  76. package/src/locale/messages-nn.ts +41 -2
  77. package/src/locale/messages-se.ts +41 -2
  78. package/src/locale/messages-sma.ts +41 -2
  79. package/es/TagSelector/SuggestionInput.js +0 -285
  80. package/es/TagSelector/Suggestions.js +0 -97
  81. package/lib/TagSelector/SuggestionInput.d.ts +0 -19
  82. package/lib/TagSelector/SuggestionInput.js +0 -299
  83. package/lib/TagSelector/Suggestions.d.ts +0 -12
  84. package/lib/TagSelector/Suggestions.js +0 -99
  85. package/src/TagSelector/SuggestionInput.tsx +0 -287
  86. package/src/TagSelector/Suggestions.tsx +0 -139
@@ -1,4 +1,4 @@
1
- /*
1
+ /**
2
2
  * Copyright (c) 2022-present, NDLA.
3
3
  *
4
4
  * This source code is licensed under the GPLv3 license found in the
@@ -6,107 +6,128 @@
6
6
  *
7
7
  */
8
8
 
9
- import React, { useState, useRef, useEffect, ChangeEvent } from 'react';
9
+ import React, { KeyboardEvent, useMemo, useState } from 'react';
10
+ import CreatableSelect from 'react-select/creatable';
11
+ import { MultiValue, StylesConfig } from 'react-select';
10
12
  import styled from '@emotion/styled';
11
- import { spacingUnit, fonts } from '@ndla/core';
12
- import { uuid } from '@ndla/util';
13
- import SuggestionInput from './SuggestionInput';
13
+ import { colors, fonts, spacing, utils } from '@ndla/core';
14
+ import { useTranslation } from 'react-i18next';
15
+ import { TagType } from './types';
16
+ import ValueButton from './ValueButton';
17
+ import DropdownIndicator from './DropdownIndicator';
18
+ import SelectContainer from './SelectContainer';
19
+ import MenuList from './MenuList';
20
+ import Control from './Control';
21
+ import Option from './Option';
22
+ import Menu from './Menu';
23
+ import { createAriaMessages } from './ariaMessages';
24
+ import Input from './Input';
14
25
 
15
- const DEFAULT_DROPDOWN_MAXHEIGHT = '240px';
26
+ const styles: StylesConfig<TagType, true> = {
27
+ menu: () => ({}),
28
+ dropdownIndicator: () => ({}),
29
+ placeholder: (provided) => ({
30
+ ...provided,
31
+ padding: `0 ${spacing.small}`,
32
+ color: colors.brand.primary,
33
+ margin: 0,
34
+ }),
35
+ valueContainer: (provided) => ({ ...provided, padding: 0 }),
36
+ indicatorSeparator: () => ({
37
+ display: 'none',
38
+ }),
39
+ indicatorsContainer: (provided) => ({
40
+ ...provided,
41
+ alignSelf: 'flex-end',
42
+ }),
43
+ };
16
44
 
17
- const StyledLabel = styled.label`
18
- font-weight: ${fonts.weight.semibold};
45
+ const StyledTagSelector = styled.div`
46
+ display: flex;
47
+ flex-direction: column;
48
+ flex: 1;
49
+ overflow: hidden;
19
50
  `;
20
51
 
21
- export interface TagType {
22
- name: string;
23
- id: string;
52
+ interface StyledLabelProps {
53
+ labelHidden?: boolean;
24
54
  }
25
55
 
56
+ const StyledLabel = styled.label<StyledLabelProps>`
57
+ font-weight: ${fonts.weight.semibold};
58
+ ${(p) => p.labelHidden && utils.labelHidden}
59
+ `;
60
+
26
61
  interface Props {
27
62
  label: string;
28
- tags: TagType[];
29
- tagsSelected: string[];
30
- onToggleTag: (id: string) => void;
31
- onCreateTag: (tagName: string) => void;
32
- inline?: boolean;
33
- prefix?: string;
63
+ tags: string[];
64
+ selected: string[];
65
+ onChange: (tags: string[]) => void;
66
+ onCreateTag: (name: string) => void;
67
+ className?: string;
68
+ labelHidden?: boolean;
34
69
  }
35
70
 
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
- };
71
+ const TagSelector = ({ selected: _selected, tags: _tags, onChange, onCreateTag, className, label }: Props) => {
72
+ const { t } = useTranslation();
73
+ const [input, setInput] = useState('');
74
+ const tags = useMemo(() => _tags.map((tag) => ({ value: tag, label: tag })), [_tags]);
75
+ const selected = useMemo(() => _selected.map((tag) => ({ value: tag, label: tag })), [_selected]);
42
76
 
43
- const getSuggestions = (tags: TagType[], inputValue: string): TagType[] => {
44
- if (inputValue === '') {
45
- return [];
46
- }
47
- const inputLowercase = inputValue.toLowerCase();
48
- return tags
49
- .filter(({ name }) => name.toLowerCase().startsWith(inputLowercase))
50
- .sort((a, b) => a.name.localeCompare(b.name, 'nb'));
51
- };
52
-
53
- const TagSelector = ({ label, tags, tagsSelected, onCreateTag, onToggleTag, inline, prefix }: Props) => {
54
- const [inputValue, setInputValue] = useState('');
55
- const [expanded, setExpanded] = useState(false);
56
- const [dropdownMaxHeight, setDropdownMaxHeight] = useState(DEFAULT_DROPDOWN_MAXHEIGHT);
57
- const containerRef = useRef<HTMLDivElement>(null);
58
- const inputIdRef = useRef<string>(uuid());
59
-
60
- useEffect(() => {
61
- setExpanded(false);
62
- }, [tagsSelected]);
63
-
64
- useEffect(() => {
65
- const setMaxDropdownMaxHeight = () => {
66
- if (!inline && containerRef.current && typeof window !== 'undefined') {
67
- // Calculate distance from bottom of container to bottom of viewport
68
- const containerBottom = containerRef.current.getBoundingClientRect().bottom;
69
- const viewportBottom = document.documentElement.scrollHeight;
70
- const maxDropdownHeight = viewportBottom - containerBottom;
71
- setDropdownMaxHeight(`${maxDropdownHeight - spacingUnit}px`);
72
- }
73
- };
74
- if (!inline && typeof window !== 'undefined') {
75
- if (expanded) {
76
- setMaxDropdownMaxHeight();
77
- window.addEventListener('resize', setMaxDropdownMaxHeight);
78
- } else {
79
- window.removeEventListener('resize', setMaxDropdownMaxHeight);
77
+ const handleSpaceClick = (e: KeyboardEvent<HTMLDivElement>) => {
78
+ if (e.key === ' ') {
79
+ e.preventDefault();
80
+ if (!_selected.find((tag) => tag === input) && input !== '') {
81
+ onChange(_selected.concat(input));
80
82
  }
83
+ setInput('');
81
84
  }
82
- return () => {
83
- typeof window !== 'undefined' && window.removeEventListener('resize', setMaxDropdownMaxHeight);
84
- };
85
- }, [expanded, inline]);
85
+ };
86
+
87
+ const handleChange = (tags: MultiValue<TagType>) => {
88
+ onChange(tags.map((tag) => tag.value));
89
+ };
90
+
91
+ const createLabel = (tag: string) => t('tagSelector.createLabel', { tag });
86
92
 
87
93
  return (
88
- <div ref={containerRef}>
89
- <StyledLabel htmlFor={inputIdRef.current}>{label}</StyledLabel>
90
- <SuggestionInput
91
- onChange={(e: ChangeEvent<HTMLInputElement>) => {
92
- const target = e.target as HTMLInputElement;
93
- setInputValue(target.value);
94
- setExpanded(false);
94
+ <StyledTagSelector className={className}>
95
+ {label && (
96
+ <StyledLabel labelHidden htmlFor="tagselector-creatable" id="tagselector-label">
97
+ {label}
98
+ </StyledLabel>
99
+ )}
100
+ <CreatableSelect
101
+ id="tagselector-creatable"
102
+ aria-labelledby={label ? 'tagselector-label' : undefined}
103
+ ariaLiveMessages={createAriaMessages(t)}
104
+ components={{
105
+ DropdownIndicator,
106
+ MultiValue: ValueButton,
107
+ SelectContainer,
108
+ MenuList,
109
+ Control,
110
+ Option,
111
+ Menu,
112
+ Input,
95
113
  }}
96
- suggestions={expanded ? tags : getSuggestions(tags, inputValue)}
97
- value={inputValue}
98
- onCreateTag={onCreateTag}
99
- onToggleTag={onToggleTag}
100
- setInputValue={setInputValue}
101
- addedTags={sortedTags(tags, tagsSelected)}
102
- expanded={expanded}
103
- setExpanded={setExpanded}
104
- dropdownMaxHeight={dropdownMaxHeight}
105
- inline={inline}
106
- scrollAnchorElement={containerRef}
107
- prefix={prefix}
114
+ formatCreateLabel={createLabel}
115
+ inputValue={input}
116
+ isClearable={false}
117
+ isMulti
118
+ noOptionsMessage={() => t('tagSelector.noOptions')}
119
+ onChange={handleChange}
120
+ onCreateOption={onCreateTag}
121
+ onInputChange={setInput}
122
+ onKeyDown={handleSpaceClick}
123
+ options={tags}
124
+ placeholder={t('tagSelector.placeholder')}
125
+ screenReaderStatus={({ count }) => t('tagSelector.aria.screenReaderStatus', { count })}
126
+ styles={styles}
127
+ tabSelectsValue={false}
128
+ value={selected}
108
129
  />
109
- </div>
130
+ </StyledTagSelector>
110
131
  );
111
132
  };
112
133
 
@@ -0,0 +1,46 @@
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 { MultiValueProps } from 'react-select';
11
+ import { buttonStyleV2 as buttonStyle } from '@ndla/button';
12
+ import styled from '@emotion/styled';
13
+ import { colors, spacing } from '@ndla/core';
14
+ import { Cross } from '@ndla/icons/action';
15
+ import { TagType } from './types';
16
+
17
+ interface StyledProps {
18
+ selected: boolean;
19
+ }
20
+
21
+ export const StyledValueButton = styled.div<StyledProps>`
22
+ && {
23
+ background: ${({ selected }) => selected && colors.brand.primary};
24
+ color: ${({ selected }) => selected && colors.white};
25
+ padding: ${spacing.xxsmall} ${spacing.small};
26
+ margin: ${spacing.xxsmall};
27
+ border: none;
28
+ }
29
+ `;
30
+
31
+ const ValueButton = ({ innerProps, children, removeProps, isFocused }: MultiValueProps<TagType, true>) => {
32
+ return (
33
+ <StyledValueButton
34
+ selected={isFocused}
35
+ role="button"
36
+ css={buttonStyle({ colorTheme: 'lighter', shape: 'pill', size: 'small' })}
37
+ {...innerProps}
38
+ {...removeProps}>
39
+ <span aria-hidden>#</span>
40
+ {children}
41
+ <Cross />
42
+ </StyledValueButton>
43
+ );
44
+ };
45
+
46
+ export default ValueButton;
@@ -0,0 +1,87 @@
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 { TFunction } from 'react-i18next';
10
+ import {
11
+ AriaGuidanceProps,
12
+ AriaOnChangeProps,
13
+ AriaOnFilterProps,
14
+ AriaOnFocusProps,
15
+ GroupBase,
16
+ OptionsOrGroups,
17
+ } from 'react-select';
18
+ import { TagType } from './types';
19
+
20
+ export const createAriaMessages = (t: TFunction) => ({
21
+ guidance: (props: AriaGuidanceProps) => {
22
+ const { isSearchable, isMulti, isDisabled, tabSelectsValue, context } = props;
23
+ switch (context) {
24
+ case 'menu':
25
+ return `${t('tagSelector.aria.guidance.menu.updown')}${
26
+ isDisabled ? '' : `, ${t('tagSelector.aria.guidance.menu.enter')}`
27
+ }, ${t('tagSelector.aria.guidance.menu.escape')}${
28
+ tabSelectsValue ? `, ${t('tagSelector.aria.guidance.menu.tab')}` : ''
29
+ }.`;
30
+ case 'input':
31
+ return `${props['aria-label'] || t('tagSelector.aria.guidance.input.select')} ${t(
32
+ 'tagSelector.aria.guidance.input.focused',
33
+ )} ${isSearchable ? `, ${t('tagSelector.aria.guidance.input.refine')}` : ''}, ${t(
34
+ 'tagSelector.aria.guidance.input.down',
35
+ )}, ${isMulti ? ` ${t('tagSelector.aria.guidance.input.left')}` : ''}, ${t(
36
+ 'tagSelector.aria.guidance.input.space',
37
+ )}`;
38
+ case 'value':
39
+ return t('tagSelector.aria.guidance.value');
40
+ default:
41
+ return '';
42
+ }
43
+ },
44
+
45
+ onChange: (props: AriaOnChangeProps<TagType, true>) => {
46
+ const { action, label = '', labels, isDisabled } = props;
47
+ switch (action) {
48
+ case 'deselect-option':
49
+ case 'pop-value':
50
+ case 'remove-value':
51
+ return t('tagSelector.aria.onChange.deselect', { label });
52
+ case 'clear':
53
+ return t('tagSelector.aria.onChange.clear');
54
+ case 'initial-input-focus':
55
+ return t('tagSelector.aria.onChange.initialFocus', { labels: labels.join(',') });
56
+ case 'select-option':
57
+ return isDisabled
58
+ ? t('tagSelector.aria.onChange.selectedDisabled', { label })
59
+ : t('tagSelector.aria.onChange.selected', { label });
60
+ default:
61
+ return '';
62
+ }
63
+ },
64
+
65
+ onFocus: (props: AriaOnFocusProps<TagType, GroupBase<TagType>>) => {
66
+ const { context, focused, options, label = '', selectValue, isDisabled, isSelected } = props;
67
+
68
+ const getArrayIndex = (arr: OptionsOrGroups<TagType, GroupBase<TagType>>, item: TagType) =>
69
+ arr && arr.length ? `${arr.indexOf(item) + 1} ${t('tagSelector.aria.onFocus.of')} ${arr.length}` : '';
70
+
71
+ if (context === 'value' && selectValue) {
72
+ return t('tagSelector.aria.onFocus.value', { label, position: getArrayIndex(selectValue, focused) });
73
+ }
74
+
75
+ if (context === 'menu') {
76
+ const disabled = isDisabled ? ` ${t('tagSelector.aria.disabled')}` : '';
77
+ const status = `${isSelected ? t('tagSelector.aria.selected') : t('tagSelector.aria.focused')}${disabled}`;
78
+ return t('tagSelector.aria.onFocus.menu', { label, status, position: getArrayIndex(options, focused) });
79
+ }
80
+ return '';
81
+ },
82
+
83
+ onFilter: (props: AriaOnFilterProps) => {
84
+ const { inputValue, resultsMessage } = props;
85
+ return `${resultsMessage}${inputValue ? ` ${t('tagSelector.aria.onFilter')} ` + inputValue : ''}.`;
86
+ },
87
+ });
@@ -6,7 +6,8 @@
6
6
  *
7
7
  */
8
8
 
9
- import TagSelector, { TagType } from './TagSelector';
9
+ import TagSelector from './TagSelector';
10
+ import { TagType } from './types';
10
11
 
11
12
  export type { TagType };
12
13
 
@@ -0,0 +1,12 @@
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
+ export interface TagType {
10
+ readonly value: string;
11
+ readonly label: string;
12
+ }
@@ -31,6 +31,7 @@ const StyledSelectedFolder = styled(Button)`
31
31
  flex: 1;
32
32
  justify-content: flex-start;
33
33
  color: ${colors.black};
34
+ border: none;
34
35
  :hover,
35
36
  :focus {
36
37
  background: none;
@@ -82,6 +83,7 @@ const ComboboxButton = forwardRef<HTMLButtonElement, Props>(
82
83
 
83
84
  const onKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {
84
85
  if (e.key === 'Enter') {
86
+ onToggleTree(!showTree);
85
87
  if (showTree && focusedFolder) {
86
88
  setSelectedFolder(focusedFolder);
87
89
  }
@@ -89,6 +91,7 @@ const ComboboxButton = forwardRef<HTMLButtonElement, Props>(
89
91
  }
90
92
  if (e.key === 'Escape') {
91
93
  onToggleTree(false);
94
+ e.preventDefault();
92
95
  return;
93
96
  }
94
97
  if (['ArrowUp', 'ArrowDown'].includes(e.key) && !showTree) {
@@ -101,7 +104,16 @@ const ComboboxButton = forwardRef<HTMLButtonElement, Props>(
101
104
  };
102
105
 
103
106
  return (
104
- <StyledRow isOpen={showTree}>
107
+ <StyledRow
108
+ isOpen={showTree}
109
+ onMouseDown={(e) => {
110
+ if (!e.defaultPrevented) {
111
+ e.preventDefault();
112
+ e.stopPropagation();
113
+ onToggleTree(!showTree);
114
+ innerRef.current?.focus();
115
+ }
116
+ }}>
105
117
  <StyledSelectedFolder
106
118
  ref={innerRef}
107
119
  tabIndex={0}
@@ -116,24 +128,10 @@ const ComboboxButton = forwardRef<HTMLButtonElement, Props>(
116
128
  colorTheme="light"
117
129
  fontWeight="normal"
118
130
  shape="sharp"
119
- onKeyDown={onKeyDown}
120
- onClick={() => {
121
- innerRef.current?.focus();
122
- onToggleTree(!showTree);
123
- }}>
131
+ onKeyDown={onKeyDown}>
124
132
  {selectedFolder?.name}
125
133
  </StyledSelectedFolder>
126
- <IconButton
127
- aria-hidden
128
- aria-label=""
129
- tabIndex={-1}
130
- variant="ghost"
131
- colorTheme="greyLighter"
132
- size="small"
133
- onClick={() => {
134
- innerRef.current?.focus();
135
- onToggleTree(!showTree);
136
- }}>
134
+ <IconButton aria-hidden aria-label="" tabIndex={-1} variant="ghost" colorTheme="greyLighter" size="small">
137
135
  {showTree ? <ChevronUp /> : <ChevronDown />}
138
136
  </IconButton>
139
137
  </StyledRow>
@@ -8,7 +8,7 @@
8
8
 
9
9
  import React, { useEffect, useState, useMemo, useRef } from 'react';
10
10
  import styled from '@emotion/styled';
11
- import { colors, fonts, misc, spacing } from '@ndla/core';
11
+ import { colors, fonts, misc, utils } from '@ndla/core';
12
12
  import { css } from '@emotion/core';
13
13
  import { uniq } from 'lodash';
14
14
  import { IFolder } from '@ndla/types-learningpath-api';
@@ -62,16 +62,7 @@ const ScrollableDiv = styled.div<ScrollableDivProps>`
62
62
  type === 'picker' &&
63
63
  css`
64
64
  overflow: overlay;
65
- ::-webkit-scrollbar {
66
- width: ${spacing.small};
67
- }
68
- ::-webkit-scrollbar-thumb {
69
- border: 4px solid transparent;
70
- border-radius: 14px;
71
- background-clip: padding-box;
72
- padding: 0 4px;
73
- background-color: ${colors.brand.neutral7};
74
- }
65
+ ${utils.scrollbar}
75
66
  `}
76
67
  `;
77
68
 
@@ -30,11 +30,50 @@ const messages = {
30
30
  },
31
31
  },
32
32
  tagSelector: {
33
+ aria: {
34
+ screenReaderStatus: '{{count}} results available',
35
+ disabled: 'disabled',
36
+ selected: 'selected',
37
+ focused: 'focused',
38
+ guidance: {
39
+ menu: {
40
+ updown: 'Use Up and Down to choose tags',
41
+ enter: 'press Enter to select the currently focused tag',
42
+ escape: 'press Escape to exit the menu',
43
+ tab: 'press Tab to select the tag and exit the menu',
44
+ },
45
+ input: {
46
+ select: 'Tag menu',
47
+ focused: 'is focused',
48
+ refine: 'type to refine list',
49
+ down: 'press Down to open the menu',
50
+ left: 'press Left to focus selected tags',
51
+ space: 'press Space to create new tag',
52
+ },
53
+ value:
54
+ 'Use left and right to toggle between focused tags, press Backspace to remove the currently focused value. The last tag will be removed if none are selected.',
55
+ },
56
+ onChange: {
57
+ deselect: 'tag {{label}}, deselected.',
58
+ clear: 'All selected options have been cleared.',
59
+ initialFocus: `Tags {{labels}}, selected.`,
60
+ selectedDisabled: 'Tag {{label}} is disabled. Select another option.',
61
+ selected: 'Tag {{label}}, selected.',
62
+ },
63
+ onFocus: {
64
+ value: 'tag {{label}} focused, {{position}}.',
65
+ menu: 'tag {{label}} {{status}}, {{position}}.',
66
+ of: 'of',
67
+ },
68
+ onFilter: ' for search term ',
69
+ },
70
+ noOptions: 'No options',
33
71
  label: 'Add tag',
72
+ createLabel: 'Add tag {{tag}}',
34
73
  placeholder: 'Enter tag name',
35
74
  removeTag: 'Remove tag {{name}}',
36
- hideAllTags: 'Hide all tags',
37
- showAllTags: 'Show all tags',
75
+ hideTags: 'Hide tags',
76
+ showTags: 'Show tags',
38
77
  },
39
78
  htmlTitles: {
40
79
  titleTemplate,
@@ -30,11 +30,50 @@ const messages = {
30
30
  },
31
31
  },
32
32
  tagSelector: {
33
+ aria: {
34
+ screenReaderStatus: '{{count}} resultater tilgjengelig',
35
+ disabled: 'utilgjengelig',
36
+ selected: 'valgt',
37
+ focused: 'fokusert',
38
+ guidance: {
39
+ menu: {
40
+ updown: 'Bruk piltaster opp og ned for å velge emneknagger',
41
+ enter: 'trykk enter for å velge den markerte emneknaggen',
42
+ escape: 'trykk escape for å lukke menyen',
43
+ tab: 'trykk tab for å velge emneknaggen og lukke menyen',
44
+ },
45
+ input: {
46
+ select: 'Emneknagg-meny',
47
+ focused: 'er fokusert',
48
+ refine: 'skriv for å filtrere listen med emneknagger',
49
+ down: 'trykk pil ned for å åpne menyen',
50
+ left: 'trykk venstre pil for å fokusere valgte emneknagger',
51
+ space: 'trykk mellomrom for å opprette ny emneknagg',
52
+ },
53
+ value:
54
+ 'Bruk høyre og venstre pil for å navigere mellom valgte emneknagger, trykk backspace for å fjerne den valgte emneknaggen. Dersom ingen emneknagg er valgt fjernes den siste.',
55
+ },
56
+ onChange: {
57
+ deselect: 'emneknagg {{label}}, fjernet.',
58
+ clear: 'Alle valgte emneknagger fjernet.',
59
+ initialFocus: `Emneknagger {{labels}}, valgt.`,
60
+ selectedDisabled: 'Emneknagg {{label}} kan ikke velges. Velg et annet alternativ.',
61
+ selected: 'Emneknagg {{label}}, valgt.',
62
+ },
63
+ onFocus: {
64
+ value: 'emneknagg {{label}} fokusert, {{position}}.',
65
+ menu: 'emneknagg {{label}} {{status}}, {{position}}.',
66
+ of: 'av',
67
+ },
68
+ onFilter: ' for søkeord ',
69
+ },
70
+ noOptions: 'Ingen valgmuligheter',
33
71
  label: 'Legg til emneknagg',
72
+ createLabel: 'Legg til emneknagg {{tag}}',
34
73
  placeholder: 'Skriv inn emneknagg',
35
74
  removeTag: 'Ta vekk {{name}}',
36
- hideAllTags: 'Skjul alle emneknagger',
37
- showAllTags: 'Vis alle emneknagger',
75
+ hideTags: 'Skjul emneknagger',
76
+ showTags: 'Vis emneknagger',
38
77
  },
39
78
  htmlTitles: {
40
79
  titleTemplate,
@@ -30,11 +30,50 @@ const messages = {
30
30
  },
31
31
  },
32
32
  tagSelector: {
33
+ aria: {
34
+ screenReaderStatus: '{{count}} resultat tilgjengeleg',
35
+ disabled: 'utilgjengeleg',
36
+ selected: 'valgt',
37
+ focused: 'fokusert',
38
+ guidance: {
39
+ menu: {
40
+ updown: 'Bruk piltastar opp og ned for å velge emneknaggar',
41
+ enter: 'trykk enter for å velge den markerte emneknaggen',
42
+ escape: 'trykk escape for å lukke menyen',
43
+ tab: 'trykk tab for å velge emneknaggen og lukke menyen',
44
+ },
45
+ input: {
46
+ select: 'Emneknagg-meny',
47
+ focused: 'er fokusert',
48
+ refine: 'skriv for å filtrere lista med emneknaggar',
49
+ down: 'trykk pil ned for å åpne menyen',
50
+ left: 'trykk venstre pil for å fokusere valgte emneknaggar',
51
+ space: 'trykk mellomrom for å opprette ny emneknagg',
52
+ },
53
+ value:
54
+ 'Bruk høgre og venstre pil for å navigere mellom valgte emneknaggar, trykk backspace for å fjerne den valgte emneknaggen. Dersom ingen emneknagg er valgt fjernes den siste.',
55
+ },
56
+ onChange: {
57
+ deselect: 'emneknagg {{label}}, fjerna.',
58
+ clear: 'Alle valgte emneknaggar fjerna.',
59
+ initialFocus: `Emneknaggar {{labels}}, valgt.`,
60
+ selectedDisabled: 'Emneknagg {{label}} kan ikkje velgast. Velg eit anna alternativ.',
61
+ selected: 'Emneknagg {{label}}, valgt.',
62
+ },
63
+ onFocus: {
64
+ value: 'emneknagg {{label}} fokusert, {{position}}.',
65
+ menu: 'emneknagg {{label}} {{status}}, {{position}}.',
66
+ of: 'av',
67
+ },
68
+ onFilter: ' for søkeord ',
69
+ },
70
+ noOptions: 'Ingen mulige val',
33
71
  label: 'Legg til emneknagg',
72
+ createLabel: 'Legg til emneknagg {{tag}}',
34
73
  placeholder: 'Skriv inn emneknagg',
35
74
  removeTag: 'Ta vekk {{name}}',
36
- hideAllTags: 'Skjul alle emneknaggar',
37
- showAllTags: 'Vis alle emneknaggar',
75
+ hideTags: 'Skjul emneknaggar',
76
+ showTags: 'Vis emneknaggar',
38
77
  },
39
78
  htmlTitles: {
40
79
  titleTemplate,