@redocly/theme 0.68.0-next.0 → 0.68.0-next.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 (41) hide show
  1. package/lib/components/Catalog/CatalogEntities.js +2 -11
  2. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTable.js +3 -16
  3. package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.d.ts +1 -1
  4. package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +3 -9
  5. package/lib/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.js +1 -7
  6. package/lib/components/Feedback/Feedback.js +4 -1
  7. package/lib/core/hooks/use-active-heading.js +5 -2
  8. package/lib/core/hooks/use-telemetry-fallback.d.ts +1 -1
  9. package/lib/core/hooks/use-telemetry-fallback.js +1 -1
  10. package/lib/core/styles/dark.js +11 -0
  11. package/lib/core/styles/global.js +36 -34
  12. package/lib/markdoc/components/NumberedList/NumberedItem.d.ts +25 -0
  13. package/lib/markdoc/components/NumberedList/NumberedItem.js +217 -0
  14. package/lib/markdoc/components/NumberedList/NumberedList.d.ts +5 -0
  15. package/lib/markdoc/components/NumberedList/NumberedList.js +28 -0
  16. package/lib/markdoc/components/NumberedList/variables.d.ts +1 -0
  17. package/lib/markdoc/components/NumberedList/variables.js +42 -0
  18. package/lib/markdoc/components/default.d.ts +2 -0
  19. package/lib/markdoc/components/default.js +2 -0
  20. package/lib/markdoc/default.js +4 -0
  21. package/lib/markdoc/tags/numbered-item.d.ts +2 -0
  22. package/lib/markdoc/tags/numbered-item.js +35 -0
  23. package/lib/markdoc/tags/numbered-list.d.ts +2 -0
  24. package/lib/markdoc/tags/numbered-list.js +28 -0
  25. package/package.json +1 -1
  26. package/src/components/Catalog/CatalogEntities.tsx +7 -11
  27. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTable.tsx +8 -9
  28. package/src/components/Catalog/CatalogEntity/CatalogEntitySchema.tsx +6 -8
  29. package/src/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.tsx +2 -4
  30. package/src/components/Feedback/Feedback.tsx +11 -1
  31. package/src/core/hooks/use-active-heading.ts +7 -2
  32. package/src/core/hooks/use-telemetry-fallback.ts +1 -1
  33. package/src/core/styles/dark.ts +12 -0
  34. package/src/core/styles/global.ts +2 -0
  35. package/src/markdoc/components/NumberedList/NumberedItem.tsx +274 -0
  36. package/src/markdoc/components/NumberedList/NumberedList.tsx +37 -0
  37. package/src/markdoc/components/NumberedList/variables.ts +39 -0
  38. package/src/markdoc/components/default.ts +2 -0
  39. package/src/markdoc/default.ts +4 -0
  40. package/src/markdoc/tags/numbered-item.ts +46 -0
  41. package/src/markdoc/tags/numbered-list.ts +34 -0
@@ -8,6 +8,8 @@ export * from '../../markdoc/components/Tabs/TabItemFragment';
8
8
  export * from '../../markdoc/components/Tabs/Tab';
9
9
  export * from '../../markdoc/components/Tabs/TabList';
10
10
  export * from '../../markdoc/components/Tabs/Tabs';
11
+ export * from '../../markdoc/components/NumberedList/NumberedItem';
12
+ export * from '../../markdoc/components/NumberedList/NumberedList';
11
13
  export * from '../../markdoc/components/CodeBlock/CodeBlock';
12
14
  export * from '../../markdoc/components/Cards/Cards';
13
15
  export * from '../../markdoc/components/Cards/Card';
@@ -24,6 +24,8 @@ __exportStar(require("../../markdoc/components/Tabs/TabItemFragment"), exports);
24
24
  __exportStar(require("../../markdoc/components/Tabs/Tab"), exports);
25
25
  __exportStar(require("../../markdoc/components/Tabs/TabList"), exports);
26
26
  __exportStar(require("../../markdoc/components/Tabs/Tabs"), exports);
27
+ __exportStar(require("../../markdoc/components/NumberedList/NumberedItem"), exports);
28
+ __exportStar(require("../../markdoc/components/NumberedList/NumberedList"), exports);
27
29
  __exportStar(require("../../markdoc/components/CodeBlock/CodeBlock"), exports);
28
30
  __exportStar(require("../../markdoc/components/Cards/Cards"), exports);
29
31
  __exportStar(require("../../markdoc/components/Cards/Card"), exports);
@@ -53,6 +53,8 @@ const markdoc_example_1 = require("../markdoc/tags/markdoc-example");
53
53
  const partial_1 = require("../markdoc/tags/partial");
54
54
  const tab_1 = require("../markdoc/tags/tab");
55
55
  const tabs_1 = require("../markdoc/tags/tabs");
56
+ const numbered_item_1 = require("../markdoc/tags/numbered-item");
57
+ const numbered_list_1 = require("../markdoc/tags/numbered-list");
56
58
  const debug_1 = require("../markdoc/tags/debug");
57
59
  const code_snippet_1 = require("../markdoc/tags/code-snippet");
58
60
  const inline_svg_1 = require("../markdoc/tags/inline-svg");
@@ -76,6 +78,8 @@ exports.tags = {
76
78
  [partial_1.partial.tagName]: partial_1.partial.schema,
77
79
  [tab_1.tab.tagName]: tab_1.tab.schema,
78
80
  [tabs_1.tabs.tagName]: tabs_1.tabs.schema,
81
+ [numbered_item_1.numberedItem.tagName]: numbered_item_1.numberedItem.schema,
82
+ [numbered_list_1.numberedList.tagName]: numbered_list_1.numberedList.schema,
79
83
  [code_snippet_1.codeSnippet.tagName]: code_snippet_1.codeSnippet.schema,
80
84
  [inline_svg_1.inlineSvg.tagName]: inline_svg_1.inlineSvg.schema,
81
85
  [cards_1.cards.tagName]: cards_1.cards.schema,
@@ -0,0 +1,2 @@
1
+ import type { MarkdocSchemaWrapper } from '../../markdoc/tags/types';
2
+ export declare const numberedItem: MarkdocSchemaWrapper;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.numberedItem = void 0;
7
+ const markdoc_1 = __importDefault(require("@markdoc/markdoc"));
8
+ const utils_1 = require("../../core/utils");
9
+ const HEADING_TAG_NAME = /^(Heading|h[1-6])$/;
10
+ exports.numberedItem = {
11
+ schema: {
12
+ transform(node, config) {
13
+ const attributes = node.transformAttributes(config);
14
+ const children = node.transformChildren(config);
15
+ const [first, ...body] = children;
16
+ if ((0, utils_1.isTag)(first) && HEADING_TAG_NAME.test(String(first.name))) {
17
+ const headlineLevel = typeof first.attributes.level === 'number'
18
+ ? first.attributes.level
19
+ : Number(String(first.name).slice(1)) || undefined;
20
+ return new markdoc_1.default.Tag('NumberedItem', Object.assign(Object.assign({}, attributes), { headline: first.children, anchorId: first.attributes.id, headlineLevel }), body);
21
+ }
22
+ return new markdoc_1.default.Tag('NumberedItem', attributes, children);
23
+ },
24
+ attributes: {
25
+ icon: { type: String, resolver: 'relativeOrCdnIcon' },
26
+ iconRawContent: {
27
+ // internal use only
28
+ type: String,
29
+ render: true,
30
+ },
31
+ },
32
+ },
33
+ tagName: 'numbered-item',
34
+ };
35
+ //# sourceMappingURL=numbered-item.js.map
@@ -0,0 +1,2 @@
1
+ import type { MarkdocSchemaWrapper } from '../../markdoc/tags/types';
2
+ export declare const numberedList: MarkdocSchemaWrapper;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.numberedList = void 0;
7
+ const markdoc_1 = __importDefault(require("@markdoc/markdoc"));
8
+ const utils_1 = require("../../core/utils");
9
+ exports.numberedList = {
10
+ schema: {
11
+ transform(node, config) {
12
+ const attributes = node.transformAttributes(config);
13
+ const items = node
14
+ .transformChildren(config)
15
+ .filter((child) => (0, utils_1.isTag)(child) && child.name === 'NumberedItem');
16
+ items.forEach((item, index) => {
17
+ item.attributes = Object.assign(Object.assign({}, item.attributes), { type: attributes.type, size: attributes.size, index: index + 1, isLast: index === items.length - 1 });
18
+ });
19
+ return new markdoc_1.default.Tag('NumberedList', attributes, items);
20
+ },
21
+ attributes: {
22
+ type: { type: String, matches: ['icon', 'number', 'dot'], default: 'number' },
23
+ size: { type: String, matches: ['small', 'medium'], default: 'medium' },
24
+ },
25
+ },
26
+ tagName: 'numbered-list',
27
+ };
28
+ //# sourceMappingURL=numbered-list.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.68.0-next.0",
3
+ "version": "0.68.0-next.1",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -3,11 +3,8 @@ import React, { JSX, useEffect } from 'react';
3
3
  import type { CatalogEntityConfig } from '@redocly/config';
4
4
 
5
5
  import { BffCatalogEntity, BffCatalogEntityList } from '@redocly/theme/core/types';
6
- import {
7
- useThemeHooks,
8
- useCatalogEntities,
9
- useCatalogEntityDetails,
10
- } from '@redocly/theme/core/hooks';
6
+ import { useThemeHooks, useCatalogEntities } from '@redocly/theme/core/hooks';
7
+ import { getBaseDataAttributes } from '@redocly/theme/core/utils';
11
8
  import { ArrowDownIcon } from '@redocly/theme/icons/ArrowDownIcon/ArrowDownIcon';
12
9
  import { CatalogCardView } from '@redocly/theme/components/Catalog/CatalogCardView/CatalogCardView';
13
10
  import { CatalogTableView } from '@redocly/theme/components/Catalog/CatalogTableView/CatalogTableView';
@@ -45,9 +42,6 @@ export function CatalogEntities(props: CatalogEntitiesProps): JSX.Element {
45
42
  isColumnSorted,
46
43
  } = props;
47
44
 
48
- const { getEntityDetailsLink } = useCatalogEntityDetails({
49
- catalogConfig,
50
- });
51
45
  const { useTelemetry, useFetchCatalogEntities } = useThemeHooks();
52
46
  const telemetry = useTelemetry();
53
47
  const { initialFilter } = useCatalogEntities({
@@ -76,10 +70,12 @@ export function CatalogEntities(props: CatalogEntitiesProps): JSX.Element {
76
70
  if (searchQuery) {
77
71
  telemetry.sendCatalogEntitiesListSearchResultClickedMessage([
78
72
  {
79
- id: entity.id,
80
- object: 'catalog_entity',
81
- uri: getEntityDetailsLink(entity),
73
+ ...getBaseDataAttributes(
74
+ 'catalogEntitiesListSearchResultId',
75
+ 'catalogEntitiesListSearchResult',
76
+ ),
82
77
  query: searchQuery,
78
+ entityId: entity.id,
83
79
  entityKey: entity.key,
84
80
  entityType: entity.type,
85
81
  },
@@ -8,7 +8,8 @@ import type { BffCatalogEntity, BffCatalogRelatedEntity } from '@redocly/theme/c
8
8
  import { CatalogColumn } from '@redocly/theme/components/Catalog/CatalogTableView/CatalogTableView';
9
9
  import { CatalogActionsRow } from '@redocly/theme/components/Catalog/CatalogActionsRow';
10
10
  import { CatalogEntityRelationsTableContent } from '@redocly/theme/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTableContent';
11
- import { useCatalogEntityDetails, useThemeHooks } from '@redocly/theme/core/hooks';
11
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
12
+ import { getBaseDataAttributes } from '@redocly/theme/core/utils';
12
13
  import { reverseRelationMap } from '@redocly/theme/core/constants';
13
14
 
14
15
  export type CatalogEntityRelationsTableProps = {
@@ -51,22 +52,20 @@ export function CatalogEntityRelationsTable({
51
52
  }: CatalogEntityRelationsTableProps): JSX.Element {
52
53
  const { useTelemetry } = useThemeHooks();
53
54
  const telemetry = useTelemetry();
54
- const { getEntityDetailsLink } = useCatalogEntityDetails({
55
- catalogConfig,
56
- entitiesCatalogConfig,
57
- });
58
55
 
59
56
  const onRowClick = (relatedEntity: BffCatalogRelatedEntity) => {
60
57
  if (searchQuery) {
61
- telemetry.sendCatalogEntitiesRelatedEntitiesListSearchResultClickMessage([
58
+ telemetry.sendCatalogEntitiesRelatedEntitiesListSearchResultClickedMessage([
62
59
  {
63
- id: relatedEntity.id,
64
- object: 'catalog_entity',
65
- uri: getEntityDetailsLink(relatedEntity),
60
+ ...getBaseDataAttributes(
61
+ 'catalogEntitiesRelatedEntitiesListSearchResultId',
62
+ 'catalogEntitiesRelatedEntitiesListSearchResult',
63
+ ),
66
64
  query: searchQuery,
67
65
  entityKey: entity.key,
68
66
  entityType: entity.type,
69
67
  relationType: reverseRelationMap[relatedEntity.relationType],
68
+ relatedEntityId: relatedEntity.id,
70
69
  relatedEntityKey: relatedEntity.key,
71
70
  relatedEntityType: relatedEntity.type,
72
71
  },
@@ -5,8 +5,9 @@ import * as Sampler from 'openapi-sampler';
5
5
  import type { CatalogEntityConfig } from '@redocly/config';
6
6
  import type { BffCatalogEntity, BffCatalogRelatedEntity } from '@redocly/theme/core/types';
7
7
 
8
- import { useCatalogEntityDetails, useThemeHooks } from '@redocly/theme/core/hooks';
8
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
9
9
  import { useCatalogEntitySchema } from '@redocly/theme/core/hooks';
10
+ import { getBaseDataAttributes } from '@redocly/theme/core/utils';
10
11
  import { JsonViewer } from '@redocly/theme/components/JsonViewer/JsonViewer';
11
12
  import { CopyButton } from '@redocly/theme/components/Buttons/CopyButton';
12
13
 
@@ -24,7 +25,6 @@ export type CatalogEntitySchemaProps = {
24
25
  export function CatalogEntitySchema({
25
26
  entity,
26
27
  relatedEntity,
27
- catalogConfig,
28
28
  RedocSchema,
29
29
  StoreProvider,
30
30
  GraphqlTypeRenderer,
@@ -35,9 +35,6 @@ export function CatalogEntitySchema({
35
35
  const { definition, parsedSchema, rawSchema } = useCatalogEntitySchema({ entity, relatedEntity });
36
36
  const isGraphql = entity.metadata?.specType === 'graphql';
37
37
  const graphqlSDL = entity?.metadata?.sdl;
38
- const { getEntityDetailsLink } = useCatalogEntityDetails({
39
- catalogConfig,
40
- });
41
38
 
42
39
  return (
43
40
  <MetadataWrapper data-component-name="Catalog/CatalogEntity/CatalogEntitySchema">
@@ -54,9 +51,10 @@ export function CatalogEntitySchema({
54
51
  onCopyClick={() =>
55
52
  telemetry.sendCatalogEntitiesCopyDataSchemaClickedMessage([
56
53
  {
57
- id: entity.id,
58
- object: 'catalog_entity',
59
- uri: getEntityDetailsLink(entity),
54
+ ...getBaseDataAttributes('catalogEntitiesCopyDataSchemaId', 'button'),
55
+ entityId: entity.id,
56
+ entityKey: entity.key,
57
+ entityType: entity.type,
60
58
  },
61
59
  ])
62
60
  }
@@ -12,7 +12,7 @@ import { FilterInput } from '@redocly/theme/components/Filter/FilterInput';
12
12
  import { useThemeHooks } from '@redocly/theme/core/hooks';
13
13
  import { CheckboxIcon } from '@redocly/theme/icons/CheckboxIcon/CheckboxIcon';
14
14
  import { CounterTag } from '@redocly/theme/components/Tags/CounterTag';
15
- import { changeTextCasing } from '@redocly/theme/core/utils';
15
+ import { changeTextCasing, getBaseDataAttributes } from '@redocly/theme/core/utils';
16
16
  import { Accordion } from '@redocly/theme/components/Accordion/Accordion';
17
17
 
18
18
  export type CatalogFilterCheckboxesProps = FilterProps & {
@@ -104,9 +104,7 @@ export function CatalogFilterCheckboxes({
104
104
  filter.toggleOption(value);
105
105
  telemetry.sendCatalogEntitiesFilterCheckboxToggledMessage([
106
106
  {
107
- id: filter.property + '--' + value,
108
- object: 'checkbox',
109
- uri: window.location.href,
107
+ ...getBaseDataAttributes('catalogEntitiesFilterCheckboxId', 'checkbox'),
110
108
  category: filter.property,
111
109
  value: value,
112
110
  },
@@ -11,6 +11,7 @@ import { Comment } from '@redocly/theme/components/Feedback/Comment';
11
11
  import { Mood } from '@redocly/theme/components/Feedback/Mood';
12
12
  import { Scale } from '@redocly/theme/components/Feedback/Scale';
13
13
  import { DEFAULT_FEEDBACK_TYPE } from '@redocly/theme/core/constants';
14
+ import { getBaseDataAttributes } from '@redocly/theme/core/utils';
14
15
 
15
16
  const feedbackComponents = {
16
17
  rating: Rating,
@@ -58,7 +59,16 @@ export function Feedback(props: FeedbackProps & { path?: string }) {
58
59
  settings={settings}
59
60
  onSubmit={(values) => {
60
61
  submitFeedback({ type, values, path });
61
- telemetry.sendFeedbackMessage({ feedbackType: type });
62
+ telemetry.sendFeedbackMessage([
63
+ {
64
+ ...getBaseDataAttributes<
65
+ 'feedbackFormId',
66
+ 'feedbackForm',
67
+ 'urn:redocly:realm:ui:feedbackForm:feedbackFormId'
68
+ >('feedbackFormId', 'feedbackForm'),
69
+ feedbackType: type,
70
+ },
71
+ ]);
62
72
  }}
63
73
  />
64
74
  </FeedbackWrapper>
@@ -64,8 +64,13 @@ export function useActiveHeading(
64
64
  const headers = Array.from(allHeaders);
65
65
 
66
66
  if (displayedHeadingsRef.current && displayedHeadingsRef.current.length > 0) {
67
- const displayedIds = displayedHeadingsRef.current.map((h) => h?.id).filter(Boolean);
68
- return headers.filter((header) => displayedIds.includes(header.id));
67
+ // Resolve targets by id so anchors that are not headings (e.g. numbered
68
+ // list items with a lifted headline) can be scrolled to and tracked.
69
+ return displayedHeadingsRef.current
70
+ .map((heading) =>
71
+ heading?.id ? allContent.querySelector<HTMLElement>(`[id="${heading.id}"]`) : null,
72
+ )
73
+ .filter((element): element is HTMLElement => element !== null);
69
74
  }
70
75
 
71
76
  return headers;
@@ -8,7 +8,7 @@ export const useTelemetryFallback = () => ({
8
8
  sendCatalogEntitiesCopyDataSchemaClickedMessage: () => {},
9
9
  sendCatalogEntitiesViewModeChangedMessage: () => {},
10
10
  sendCatalogEntitiesRelatedEntitiesListSearchQueryMessage: () => {},
11
- sendCatalogEntitiesRelatedEntitiesListSearchResultClickMessage: () => {},
11
+ sendCatalogEntitiesRelatedEntitiesListSearchResultClickedMessage: () => {},
12
12
  sendCatalogEntitiesListSearchQueryMessage: () => {},
13
13
  sendCatalogEntitiesListSearchResultClickedMessage: () => {},
14
14
  sendRedirectMessage: () => {},
@@ -60,6 +60,17 @@ const badgesDarkMode = css`
60
60
  // @tokens End
61
61
  `
62
62
 
63
+ const numberedListDarkMode = css`
64
+ /**
65
+ * @tokens Markdown Numbered List
66
+ */
67
+
68
+ --md-numbered-list-marker-bg-color: var(--color-warm-grey-3); // @presenter Color
69
+ --md-numbered-list-marker-bg-color-hover: var(--color-warm-grey-4); // @presenter Color
70
+
71
+ // @tokens End
72
+ `
73
+
63
74
 
64
75
  export const darkMode = css`
65
76
  /* === Dark Theme Colors === */
@@ -352,6 +363,7 @@ export const darkMode = css`
352
363
  ${bannerDarkMode}
353
364
  ${admonitionDarkMode}
354
365
  ${badgesDarkMode}
366
+ ${numberedListDarkMode}
355
367
  ${activeBrandPaletteDark}
356
368
  ${codeBlockDarkMode}
357
369
  /**
@@ -29,6 +29,7 @@ import { productPicker } from '@redocly/theme/components/Product/variables';
29
29
  import { markdown } from '@redocly/theme/components/Markdown/variables';
30
30
  import { banner } from '@redocly/theme/components/Banner/variables';
31
31
  import { markdownTabs } from '@redocly/theme/markdoc/components/Tabs/variables';
32
+ import { markdownNumberedList } from '@redocly/theme/markdoc/components/NumberedList/variables';
32
33
  import { diagram } from '@redocly/theme/markdoc/components/Diagram/variables';
33
34
  import { lastUpdated } from '@redocly/theme/components/LastUpdated/variables';
34
35
  import { logo } from '@redocly/theme/components/Logo/variables';
@@ -1344,6 +1345,7 @@ export const styles = css<{ palette?: string }>`
1344
1345
  ${logo}
1345
1346
  ${markdown}
1346
1347
  ${markdownTabs}
1348
+ ${markdownNumberedList}
1347
1349
  ${diagram}
1348
1350
  ${menu}
1349
1351
  ${mobileMenu}
@@ -0,0 +1,274 @@
1
+ import React, { createElement } from 'react';
2
+ import styled, { css } from 'styled-components';
3
+
4
+ import type { JSX, PropsWithChildren, ReactNode } from 'react';
5
+
6
+ import { GenericIcon } from '@redocly/theme/icons/GenericIcon/GenericIcon';
7
+ import { LinkIcon } from '@redocly/theme/icons/LinkIcon/LinkIcon';
8
+
9
+ export const NumberedListType = {
10
+ ICON: 'icon',
11
+ NUMBER: 'number',
12
+ DOT: 'dot',
13
+ } as const;
14
+
15
+ export type NumberedListType = (typeof NumberedListType)[keyof typeof NumberedListType];
16
+
17
+ export const NumberedListSize = {
18
+ SMALL: 'small',
19
+ MEDIUM: 'medium',
20
+ } as const;
21
+
22
+ export type NumberedListSize = (typeof NumberedListSize)[keyof typeof NumberedListSize];
23
+
24
+ export type NumberedItemProps = PropsWithChildren<{
25
+ headline?: ReactNode;
26
+ anchorId?: string;
27
+ headlineLevel?: number;
28
+ icon?: string | ReactNode;
29
+ /* Internal use only: raw SVG content resolved for relative `.svg` icons */
30
+ iconRawContent?: string;
31
+ className?: string;
32
+ /* Injected by the parent NumberedList component */
33
+ type?: NumberedListType;
34
+ size?: NumberedListSize;
35
+ index?: number;
36
+ isLast?: boolean;
37
+ }>;
38
+
39
+ export function NumberedItem({
40
+ headline,
41
+ anchorId,
42
+ headlineLevel,
43
+ icon,
44
+ iconRawContent,
45
+ className,
46
+ type = NumberedListType.NUMBER,
47
+ size = NumberedListSize.MEDIUM,
48
+ index = 1,
49
+ isLast = true,
50
+ children,
51
+ }: NumberedItemProps): JSX.Element {
52
+ const isDot = type === NumberedListType.DOT;
53
+ const headlineTextId = anchorId ? `${anchorId}-heading-text` : undefined;
54
+
55
+ const marker = (
56
+ <Marker $type={type} $size={size} data-dot={isDot || undefined}>
57
+ {type === NumberedListType.ICON && (
58
+ <MarkerContent className="numbered-item-marker-content">
59
+ <GenericIcon icon={icon} rawContent={iconRawContent} />
60
+ </MarkerContent>
61
+ )}
62
+ {type === NumberedListType.NUMBER && (
63
+ <MarkerContent className="numbered-item-marker-content">{index}</MarkerContent>
64
+ )}
65
+ {anchorId && (
66
+ <MarkerLink className="numbered-item-marker-link" $isDot={isDot}>
67
+ <MarkerLinkIcon
68
+ aria-hidden="true"
69
+ focusable="false"
70
+ color="--md-numbered-list-marker-text-color"
71
+ />
72
+ </MarkerLink>
73
+ )}
74
+ </Marker>
75
+ );
76
+
77
+ return (
78
+ <NumberedItemWrapper
79
+ data-component-name="Markdoc/NumberedList/NumberedItem"
80
+ className={className}
81
+ id={anchorId}
82
+ $size={size}
83
+ $isDot={isDot}
84
+ >
85
+ <MarkerRail>
86
+ {anchorId ? (
87
+ <a
88
+ href={`#${anchorId}`}
89
+ {...(headline
90
+ ? { 'aria-labelledby': headlineTextId }
91
+ : { 'aria-label': 'Link to this item' })}
92
+ >
93
+ {marker}
94
+ </a>
95
+ ) : (
96
+ marker
97
+ )}
98
+ <Tail data-testid="numbered-item-tail" $isLast={isLast} />
99
+ </MarkerRail>
100
+ <NumberedItemContent>
101
+ {headline &&
102
+ createElement(
103
+ NumberedItemHeadline,
104
+ { as: `h${headlineLevel ?? 3}` as keyof JSX.IntrinsicElements, id: headlineTextId },
105
+ headline,
106
+ )}
107
+ <NumberedItemBody>{children}</NumberedItemBody>
108
+ </NumberedItemContent>
109
+ </NumberedItemWrapper>
110
+ );
111
+ }
112
+
113
+ const markerSize = css<{ $size?: string }>`
114
+ ${({ $size }) =>
115
+ $size === NumberedListSize.SMALL
116
+ ? css`
117
+ width: var(--md-numbered-list-marker-small-size);
118
+ height: var(--md-numbered-list-marker-small-size);
119
+ --icon-width: var(--md-numbered-list-icon-small-size);
120
+ --icon-height: var(--md-numbered-list-icon-small-size);
121
+ `
122
+ : css`
123
+ width: var(--md-numbered-list-marker-medium-size);
124
+ height: var(--md-numbered-list-marker-medium-size);
125
+ --icon-width: var(--md-numbered-list-icon-medium-size);
126
+ --icon-height: var(--md-numbered-list-icon-medium-size);
127
+ `}
128
+ `;
129
+
130
+ const Marker = styled.span<{ $type: string; $size: string }>`
131
+ position: relative;
132
+ display: flex;
133
+ align-items: center;
134
+ justify-content: center;
135
+ flex-shrink: 0;
136
+ border-radius: 50%;
137
+ color: var(--md-numbered-list-marker-text-color);
138
+ font-size: var(--md-numbered-list-marker-font-size);
139
+
140
+ ${({ $type }) =>
141
+ $type === NumberedListType.DOT
142
+ ? css`
143
+ width: var(--md-numbered-list-dot-size);
144
+ height: var(--md-numbered-list-dot-size);
145
+ background-color: var(--text-color-primary);
146
+ `
147
+ : css`
148
+ ${markerSize}
149
+ background-color: var(--md-numbered-list-marker-bg-color);
150
+ `}
151
+ `;
152
+
153
+ const MarkerContent = styled.span`
154
+ display: flex;
155
+ align-items: center;
156
+ justify-content: center;
157
+ `;
158
+
159
+ const MarkerLink = styled.span<{ $isDot?: boolean }>`
160
+ display: none;
161
+ align-items: center;
162
+ justify-content: center;
163
+
164
+ svg {
165
+ width: var(--icon-width, 16px);
166
+ height: var(--icon-height, 16px);
167
+ }
168
+
169
+ ${({ $isDot }) =>
170
+ $isDot &&
171
+ css`
172
+ position: absolute;
173
+ inset-inline-end: calc(100% + var(--md-numbered-list-dot-link-gap));
174
+ `}
175
+ `;
176
+
177
+ const MarkerLinkIcon = styled(LinkIcon)`
178
+ path:first-of-type {
179
+ fill: var(--md-numbered-list-marker-icon-bg-color);
180
+ }
181
+ `;
182
+
183
+ const MarkerRail = styled.span`
184
+ display: flex;
185
+ flex-direction: column;
186
+ align-items: center;
187
+ align-self: stretch;
188
+ flex-shrink: 0;
189
+
190
+ a {
191
+ display: flex;
192
+ border-radius: 50%;
193
+ /* Overrides the global markdown link rule a:not([role='button']), which is more specific. */
194
+ text-decoration: none !important;
195
+
196
+ &:hover,
197
+ &:focus {
198
+ text-decoration: none !important;
199
+ }
200
+ }
201
+ `;
202
+
203
+ const Tail = styled.span<{ $isLast: boolean }>`
204
+ flex: 1 0 0;
205
+ min-height: var(--spacing-xs);
206
+ width: var(--md-numbered-list-tail-width);
207
+ background: ${({ $isLast }) =>
208
+ $isLast
209
+ ? 'var(--md-numbered-list-tail-fade-gradient)'
210
+ : 'var(--md-numbered-list-tail-bg-color)'};
211
+ `;
212
+
213
+ const NumberedItemHeadline = styled.h3`
214
+ margin: 0;
215
+ color: var(--md-numbered-list-headline-text-color);
216
+ font-size: var(--md-numbered-list-headline-font-size);
217
+ font-weight: var(--md-numbered-list-headline-font-weight);
218
+ line-height: var(--md-numbered-list-headline-line-height);
219
+ `;
220
+
221
+ const NumberedItemBody = styled.div`
222
+ display: flex;
223
+ flex-direction: column;
224
+ gap: var(--md-numbered-list-body-gap);
225
+ color: var(--md-numbered-list-body-text-color);
226
+ font-size: var(--md-numbered-list-body-font-size);
227
+
228
+ > p {
229
+ margin: 0;
230
+ }
231
+ `;
232
+
233
+ const NumberedItemContent = styled.div`
234
+ display: flex;
235
+ flex-direction: column;
236
+ gap: var(--spacing-unit);
237
+ flex: 1;
238
+ min-width: 0;
239
+ padding-bottom: var(--spacing-sm);
240
+ `;
241
+
242
+ const NumberedItemWrapper = styled.li<{ $size?: string; $isDot?: boolean }>`
243
+ display: flex;
244
+ gap: var(--md-numbered-list-gap);
245
+ align-items: stretch;
246
+
247
+ ${({ $isDot }) =>
248
+ $isDot &&
249
+ css`
250
+ ${MarkerRail} {
251
+ padding-top: var(--md-numbered-list-dot-rail-padding-top);
252
+ }
253
+ `}
254
+
255
+ &:last-child ${NumberedItemContent} {
256
+ padding-bottom: 0;
257
+ }
258
+
259
+ &:hover,
260
+ &:focus-within {
261
+ ${Marker} {
262
+ background-color: ${({ $isDot }) =>
263
+ $isDot ? 'var(--text-color-primary)' : 'var(--md-numbered-list-marker-bg-color-hover)'};
264
+ }
265
+
266
+ a ${Marker}:not([data-dot]) .numbered-item-marker-content {
267
+ display: none;
268
+ }
269
+
270
+ a ${Marker} .numbered-item-marker-link {
271
+ display: flex;
272
+ }
273
+ }
274
+ `;
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX, PropsWithChildren } from 'react';
5
+
6
+ export type NumberedListProps = PropsWithChildren<{
7
+ className?: string;
8
+ }>;
9
+
10
+ export function NumberedList({ className, children }: NumberedListProps): JSX.Element | null {
11
+ if (React.Children.count(children) === 0) {
12
+ return null;
13
+ }
14
+
15
+ return (
16
+ <NumberedListWrapper
17
+ data-component-name="Markdoc/NumberedList/NumberedList"
18
+ className={className}
19
+ >
20
+ {children}
21
+ </NumberedListWrapper>
22
+ );
23
+ }
24
+
25
+ const NumberedListWrapper = styled.ol`
26
+ display: flex;
27
+ flex-direction: column;
28
+ margin: var(--md-numbered-list-margin);
29
+ padding: 0;
30
+ list-style: none;
31
+
32
+ && {
33
+ & > li:not(:last-child) {
34
+ margin-bottom: 0;
35
+ }
36
+ }
37
+ `;