@ndla/ui 45.0.13 → 45.0.14

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.
@@ -19,6 +19,13 @@ const blockEmbedData: ConceptEmbedData = {
19
19
  linkText: '',
20
20
  };
21
21
 
22
+ const glossBlockEmbedData: ConceptEmbedData = {
23
+ contentId: '4942',
24
+ resource: 'concept',
25
+ type: 'block',
26
+ linkText: '',
27
+ };
28
+
22
29
  const inlineEmbedData: ConceptEmbedData = {
23
30
  contentId: '2318',
24
31
  linkText: 'forklaring',
@@ -26,6 +33,13 @@ const inlineEmbedData: ConceptEmbedData = {
26
33
  type: 'inline',
27
34
  };
28
35
 
36
+ const glossInlineEmbedData: ConceptEmbedData = {
37
+ contentId: '23',
38
+ linkText: 'glose',
39
+ resource: 'concept',
40
+ type: 'inline',
41
+ };
42
+
29
43
  const conceptMetaData: ConceptData['concept'] = {
30
44
  id: 110,
31
45
  revision: 16,
@@ -68,7 +82,68 @@ const conceptMetaData: ConceptData['concept'] = {
68
82
  '<ndlaembed data-resource="image" data-resource_id="52863" data-alt="Eksempel på hvordan borevæsken kan trenge ut i formasjonen fra borehullet og skade formasjonens permeabilitet. Illustrasjon." data-size="full" data-align="" data-url="https://api.test.ndla.no/image-api/v2/images/52863"></ndlaembed>',
69
83
  language: 'nb',
70
84
  },
71
- conceptType: 'standard',
85
+ conceptType: 'concept',
86
+ };
87
+
88
+ const glossMetaData: ConceptData['concept'] = {
89
+ id: 4942,
90
+ revision: 6,
91
+ title: {
92
+ title: 'Ma Hong',
93
+ language: 'nb',
94
+ },
95
+ content: {
96
+ content: 'Hei',
97
+ language: 'nb',
98
+ },
99
+ copyright: {
100
+ creators: [],
101
+ processors: [],
102
+ rightsholders: [],
103
+ processed: false,
104
+ },
105
+ source: '',
106
+ metaImage: {
107
+ url: '',
108
+ alt: '',
109
+ language: 'und',
110
+ },
111
+ created: '2023-07-19T09:30:40.000Z',
112
+ updated: '2023-09-19T17:13:56.573Z',
113
+ updatedBy: ['XxnkdI7rApMl58MeG3p4g4B8', 'hd5ZL5Lm4kKkumWgN2gjy9wx'],
114
+ supportedLanguages: ['nb'],
115
+ articleIds: [],
116
+ status: {
117
+ current: 'IN_PROGRESS',
118
+ other: [],
119
+ },
120
+ responsible: {
121
+ responsibleId: 'XxnkdI7rApMl58MeG3p4g4B8',
122
+ lastUpdated: '2023-07-19T09:30:40.000Z',
123
+ },
124
+ conceptType: 'gloss',
125
+ glossData: {
126
+ gloss: '马红',
127
+ wordClass: 'personal-pronoun',
128
+ originalLanguage: 'zh',
129
+ transcriptions: {},
130
+ examples: [
131
+ [
132
+ {
133
+ example: '我叫马红',
134
+ language: 'zh',
135
+ transcriptions: {
136
+ pinyin: 'wo jiao ma hong ',
137
+ },
138
+ },
139
+ {
140
+ example: 'Jeg heter ma hong',
141
+ language: 'nb',
142
+ transcriptions: {},
143
+ },
144
+ ],
145
+ ],
146
+ },
72
147
  };
73
148
 
74
149
  const visualElementData: ConceptData['visualElement'] = {
@@ -141,6 +216,10 @@ const blockMetaData: ConceptData = {
141
216
  visualElement: visualElementData,
142
217
  };
143
218
 
219
+ const glossBlockData: ConceptData = {
220
+ concept: glossMetaData,
221
+ };
222
+
144
223
  const meta: Meta<typeof ConceptEmbed> = {
145
224
  title: 'Components/Embeds/ConceptEmbed',
146
225
  component: ConceptEmbed,
@@ -177,6 +256,11 @@ export const Block: StoryObj<typeof ConceptEmbed> = {
177
256
  },
178
257
  },
179
258
  };
259
+ export const GlossBlock: StoryObj<typeof ConceptEmbed> = {
260
+ args: {
261
+ embed: { resource: 'concept', status: 'success', embedData: glossBlockEmbedData, data: glossBlockData },
262
+ },
263
+ };
180
264
 
181
265
  export const BlockFailed: StoryObj<typeof ConceptEmbed> = {
182
266
  args: {
@@ -201,6 +285,17 @@ export const Inline: StoryObj<typeof ConceptEmbed> = {
201
285
  },
202
286
  };
203
287
 
288
+ export const GlossInline: StoryObj<typeof ConceptEmbed> = {
289
+ args: {
290
+ embed: {
291
+ resource: 'concept',
292
+ status: 'success',
293
+ embedData: glossInlineEmbedData,
294
+ data: glossBlockData,
295
+ },
296
+ },
297
+ };
298
+
204
299
  export const InlineFailed: StoryObj<typeof ConceptEmbed> = {
205
300
  args: {
206
301
  heartButton: StoryFavoriteButton,
@@ -14,7 +14,7 @@ import { Root, Trigger, Content, Anchor, Close, Portal } from '@radix-ui/react-p
14
14
  import { IconButtonV2 } from '@ndla/button';
15
15
  import { Cross } from '@ndla/icons/action';
16
16
  import { breakpoints, colors, mq, spacing } from '@ndla/core';
17
- import { AudioMeta, ConceptMetaData } from '@ndla/types-embed';
17
+ import { ConceptMetaData } from '@ndla/types-embed';
18
18
  import Tooltip from '@ndla/tooltip';
19
19
  import { COPYRIGHTED } from '@ndla/licenses';
20
20
  import { Notion as UINotion } from '../Notion';
@@ -26,11 +26,6 @@ import EmbedErrorPlaceholder from './EmbedErrorPlaceholder';
26
26
  import { HeartButtonType } from './types';
27
27
  import { Gloss } from '../Gloss';
28
28
 
29
- const BottomBorder = styled.div`
30
- margin-top: ${spacing.normal};
31
- border-bottom: 1px solid ${colors.brand.greyLight};
32
- `;
33
-
34
29
  interface PopoverPosition {
35
30
  top?: number;
36
31
  }
@@ -158,8 +153,9 @@ export const ConceptEmbed = ({ embed, fullWidth, heartButton: HeartButton }: Pro
158
153
  };
159
154
 
160
155
  interface InlineConceptProps extends ConceptNotionData {
161
- linkText: string;
156
+ linkText: ReactNode;
162
157
  heartButton?: HeartButtonType;
158
+ headerButtons?: ReactNode;
163
159
  conceptHeartButton?: ReactNode;
164
160
  }
165
161
 
@@ -221,7 +217,7 @@ const getModalPosition = (anchor: HTMLElement) => {
221
217
  return anchorPos.top - (articlePos?.top || -window.scrollY);
222
218
  };
223
219
 
224
- const InlineConcept = ({
220
+ export const InlineConcept = ({
225
221
  title,
226
222
  content,
227
223
  copyright,
@@ -232,6 +228,7 @@ const InlineConcept = ({
232
228
  conceptHeartButton,
233
229
  glossData,
234
230
  conceptType,
231
+ headerButtons,
235
232
  }: InlineConceptProps) => {
236
233
  const { t } = useTranslation();
237
234
  const anchorRef = useRef<HTMLDivElement>(null);
@@ -271,6 +268,7 @@ const InlineConcept = ({
271
268
  visualElement={visualElement}
272
269
  inPopover
273
270
  heartButton={heartButton}
271
+ headerButtons={headerButtons}
274
272
  conceptHeartButton={conceptHeartButton}
275
273
  closeButton={
276
274
  <Close asChild>
@@ -399,7 +397,7 @@ export const BlockConcept = ({
399
397
  />
400
398
  ) : (
401
399
  <Gloss
402
- glossData={glossData!}
400
+ glossData={glossData}
403
401
  title={title}
404
402
  audio={
405
403
  visualElement?.status === 'success' && visualElement.resource === 'audio'
@@ -408,12 +406,10 @@ export const BlockConcept = ({
408
406
  }
409
407
  />
410
408
  )}
411
- {copyright && conceptType === 'concept' ? (
409
+ {copyright && conceptType === 'concept' && (
412
410
  <EmbedByline copyright={copyright} bottomRounded topRounded type={conceptType as ConceptType}>
413
411
  {copyright.license?.license.toLowerCase() !== COPYRIGHTED && conceptHeartButton}
414
412
  </EmbedByline>
415
- ) : (
416
- <BottomBorder />
417
413
  )}
418
414
  </Figure>
419
415
  </Root>
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { forwardRef, ReactNode, RefAttributes } from 'react';
10
- import { AudioMeta, AudioMetaData, ConceptData, ConceptVisualElementMeta } from '@ndla/types-embed';
10
+ import { ConceptData, ConceptVisualElementMeta } from '@ndla/types-embed';
11
11
  import { useTranslation } from 'react-i18next';
12
12
  import { css } from '@emotion/react';
13
13
  import { breakpoints, colors, fonts, misc, mq, spacing } from '@ndla/core';
@@ -46,6 +46,7 @@ interface ConceptNotionProps extends RefAttributes<HTMLDivElement>, ConceptNotio
46
46
  inPopover?: boolean;
47
47
  tags?: string[];
48
48
  subjects?: string[];
49
+ headerButtons?: ReactNode;
49
50
  heartButton?: HeartButtonType;
50
51
  conceptHeartButton?: ReactNode;
51
52
  }
@@ -125,6 +126,12 @@ const StyledNotionDialogContent = styled(NotionDialogContent)`
125
126
  }
126
127
  `;
127
128
 
129
+ const ButtonWrapper = styled.div`
130
+ display: flex;
131
+ gap: ${spacing.xsmall};
132
+ align-items: center;
133
+ `;
134
+
128
135
  const StyledList = styled.ul`
129
136
  display: flex;
130
137
  gap: ${spacing.small};
@@ -158,6 +165,7 @@ export const ConceptNotionV2 = forwardRef<HTMLDivElement, ConceptNotionProps>(
158
165
  conceptHeartButton,
159
166
  conceptType,
160
167
  glossData,
168
+ headerButtons,
161
169
  ...rest
162
170
  },
163
171
  ref,
@@ -171,7 +179,10 @@ export const ConceptNotionV2 = forwardRef<HTMLDivElement, ConceptNotionProps>(
171
179
  <h1>
172
180
  {title.title} {<small>{t(`searchPage.resultType.${conceptType}`)}</small>}
173
181
  </h1>
174
- {closeButton}
182
+ <ButtonWrapper>
183
+ {headerButtons}
184
+ {closeButton}
185
+ </ButtonWrapper>
175
186
  </NotionHeader>
176
187
  {conceptType !== 'gloss' ? (
177
188
  <>
@@ -19,4 +19,5 @@ export { default as ConceptEmbed } from './ConceptEmbed';
19
19
  export { ConceptNotionV2 } from './conceptComponents';
20
20
  export { default as ConceptListEmbed } from './ConceptListEmbed';
21
21
  export { default as UnknownEmbed } from './UnknownEmbed';
22
+ export { InlineConcept } from './ConceptEmbed';
22
23
  export type { HeartButtonType } from './types';
@@ -24,7 +24,7 @@ export interface Props {
24
24
  title: string;
25
25
  language: string;
26
26
  };
27
- glossData: {
27
+ glossData?: {
28
28
  gloss: string;
29
29
  wordClass?: string;
30
30
  originalLanguage: string;
@@ -98,63 +98,70 @@ const Gloss = ({ title, glossData, audio }: Props) => {
98
98
 
99
99
  return (
100
100
  <>
101
- <Container>
102
- <Wrapper>
103
- <GlossContainer>
104
- <GlossSpan lang={glossData.originalLanguage}>{glossData.gloss}</GlossSpan>
105
- {glossData.transcriptions.traditional && (
106
- <span
107
- key={t('gloss.transcriptions.traditional')}
108
- aria-label={t('gloss.transcriptions.traditional')}
109
- lang={glossData.originalLanguage}
110
- >
111
- {glossData.transcriptions.traditional}
112
- </span>
113
- )}
114
- {glossData.transcriptions.pinyin && (
115
- <span
116
- key={t('gloss.transcriptions.pinyin')}
117
- aria-label={t('gloss.transcriptions.pinyin')}
118
- lang={glossData.originalLanguage}
119
- >
120
- {glossData.transcriptions.pinyin}
121
- </span>
122
- )}
123
- {glossData.wordClass && (
124
- <TypeSpan aria-label={t('gloss.wordClass')}>{t(`wordClass.${glossData.wordClass}`)}</TypeSpan>
125
- )}
126
- </GlossContainer>
127
- {audio?.src && <SpeechControl src={audio.src} title={audio.title}></SpeechControl>}
128
- </Wrapper>
129
- <span>{title.title}</span>
130
- </Container>
131
- {glossData.examples && glossData.examples.length > 0 && (
132
- <AccordionRoot type="single" collapsible>
133
- <AccordionItem value="1">
134
- <StyledAccordionHeader>{t('gloss.examples')}</StyledAccordionHeader>
135
- <StyledAccordionContent>
136
- {glossData.examples.map((example, index) => (
137
- <div key={index}>
138
- {example.map((translation, innerIndex) => (
139
- <div key={`${index}_${innerIndex}`}>
140
- <TranslatedText data-first={innerIndex === 0}>{translation.example}</TranslatedText>
141
- {translation.transcriptions.pinyin && (
142
- <TranslatedText key={t('gloss.transcriptions.pinyin')} lang={glossData.originalLanguage}>
143
- {translation.transcriptions?.pinyin}
144
- </TranslatedText>
145
- )}
146
- {translation.transcriptions.traditional && (
147
- <TranslatedText key={t('gloss.transcriptions.traditional')} lang={glossData.originalLanguage}>
148
- {translation.transcriptions?.traditional}
149
- </TranslatedText>
150
- )}
101
+ {glossData && (
102
+ <>
103
+ <Container>
104
+ <Wrapper>
105
+ <GlossContainer>
106
+ <GlossSpan lang={glossData.originalLanguage}>{glossData.gloss}</GlossSpan>
107
+ {glossData.transcriptions.traditional && (
108
+ <span
109
+ key={t('gloss.transcriptions.traditional')}
110
+ aria-label={t('gloss.transcriptions.traditional')}
111
+ lang={glossData.originalLanguage}
112
+ >
113
+ {glossData.transcriptions.traditional}
114
+ </span>
115
+ )}
116
+ {glossData.transcriptions.pinyin && (
117
+ <span
118
+ key={t('gloss.transcriptions.pinyin')}
119
+ aria-label={t('gloss.transcriptions.pinyin')}
120
+ lang={glossData.originalLanguage}
121
+ >
122
+ {glossData.transcriptions.pinyin}
123
+ </span>
124
+ )}
125
+ {glossData.wordClass && (
126
+ <TypeSpan aria-label={t('gloss.wordClass')}>{t(`wordClass.${glossData.wordClass}`)}</TypeSpan>
127
+ )}
128
+ </GlossContainer>
129
+ {audio?.src && <SpeechControl src={audio.src} title={audio.title}></SpeechControl>}
130
+ </Wrapper>
131
+ <span>{title.title}</span>
132
+ </Container>
133
+ {glossData.examples && glossData.examples.length > 0 && (
134
+ <AccordionRoot type="single" collapsible>
135
+ <AccordionItem value="1">
136
+ <StyledAccordionHeader>{t('gloss.examples')}</StyledAccordionHeader>
137
+ <StyledAccordionContent>
138
+ {glossData.examples.map((example, index) => (
139
+ <div key={index}>
140
+ {example.map((translation, innerIndex) => (
141
+ <div key={`${index}_${innerIndex}`}>
142
+ <TranslatedText data-first={innerIndex === 0}>{translation.example}</TranslatedText>
143
+ {translation.transcriptions.pinyin && (
144
+ <TranslatedText key={t('gloss.transcriptions.pinyin')} lang={glossData.originalLanguage}>
145
+ {translation.transcriptions?.pinyin}
146
+ </TranslatedText>
147
+ )}
148
+ {translation.transcriptions.traditional && (
149
+ <TranslatedText
150
+ key={t('gloss.transcriptions.traditional')}
151
+ lang={glossData.originalLanguage}
152
+ >
153
+ {translation.transcriptions?.traditional}
154
+ </TranslatedText>
155
+ )}
156
+ </div>
157
+ ))}
151
158
  </div>
152
159
  ))}
153
- </div>
154
- ))}
155
- </StyledAccordionContent>
156
- </AccordionItem>
157
- </AccordionRoot>
160
+ </StyledAccordionContent>
161
+ </AccordionItem>
162
+ </AccordionRoot>
163
+ )}
164
+ </>
158
165
  )}
159
166
  </>
160
167
  );
package/src/index.ts CHANGED
@@ -24,6 +24,7 @@ export {
24
24
  ConceptEmbed,
25
25
  ConceptListEmbed,
26
26
  UnknownEmbed,
27
+ InlineConcept,
27
28
  } from './Embed';
28
29
 
29
30
  export {