gatsby-theme-carbon 4.3.66 → 4.4.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.
package/gatsby-config.mjs CHANGED
@@ -51,6 +51,7 @@ export default (themeOptions) => {
51
51
  gatsbyPluginSharpOptions = {},
52
52
  isServiceWorkerEnabled = false,
53
53
  isSwitcherEnabled = true,
54
+ isCodeWrapEnabled = false,
54
55
  } = themeOptions;
55
56
 
56
57
  const theme = { ...defaultTheme, ...themeOption };
@@ -101,6 +102,7 @@ export default (themeOptions) => {
101
102
  interiorTheme: theme.interior,
102
103
  isServiceWorkerEnabled,
103
104
  isSwitcherEnabled,
105
+ isCodeWrapEnabled,
104
106
  title: 'Gatsby Theme Carbon',
105
107
  description:
106
108
  'Add a description by supplying it to siteMetadata in your gatsby-config.js file.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gatsby-theme-carbon",
3
- "version": "4.3.66",
3
+ "version": "4.4.0",
4
4
  "main": "index.js",
5
5
  "author": "vpicone <vp@vincepic.one> (@vpicone)",
6
6
  "repository": {
@@ -32,6 +32,17 @@
32
32
  color: currentColor;
33
33
  cursor: pointer;
34
34
  }
35
+
36
+ &:focus-visible {
37
+ outline: 2px solid $focus;
38
+ outline-offset: 2px;
39
+ }
40
+ }
41
+
42
+ // Keep each link (and its focus ring) inside a single CSS column fragment
43
+ .multiple-columns li,
44
+ .multiple-columns .link {
45
+ break-inside: avoid;
35
46
  }
36
47
 
37
48
  .list--small .link {
@@ -155,7 +155,7 @@ ArticleCard.propTypes = {
155
155
  readTime: PropTypes.string,
156
156
 
157
157
  /**
158
- * Action icon, default is blank, options are Launch, ArrowRight, Download
158
+ * Action icon, default is blank, options are launch, arrowRight, download, disabled, email
159
159
  */
160
160
  actionIcon: PropTypes.string,
161
161
 
@@ -14,10 +14,60 @@ import Sidebar from './Sidebar';
14
14
 
15
15
  import useMetadata from '../../util/hooks/useMetadata';
16
16
 
17
+ export const parseCodeMetaData = (metaData) => {
18
+ // metaData string is of format: path=/directory/file.mdx src=https://gatsby.carbondesignsystem.com showAll wrap
19
+ const result = { path: '', src: '', showAll: false, wrap: null };
20
+ if (!metaData) {
21
+ return result;
22
+ }
23
+ const metaDataObject = metaData.split(' ').reduce((obj, item) => {
24
+ if (!item) {
25
+ return obj;
26
+ }
27
+ const [key, value] = item.split('=');
28
+
29
+ // Checking for boolean values coming as string or not coming like in showAll prop
30
+ if (value === 'true' || !value) {
31
+ obj[key] = true;
32
+ } else if (value === 'false') {
33
+ obj[key] = false;
34
+ } else {
35
+ // else setting what is passed after =
36
+ obj[key] = value;
37
+ }
38
+ return obj;
39
+ }, {});
40
+
41
+ if (metaDataObject.path) {
42
+ result.path = metaDataObject.path;
43
+ }
44
+
45
+ if (metaDataObject.src) {
46
+ result.src = metaDataObject.src;
47
+ }
48
+
49
+ if (metaDataObject.showAll) {
50
+ result.showAll = true;
51
+ }
52
+
53
+ if (metaDataObject.wrap === true || metaDataObject.wrap === 'true') {
54
+ result.wrap = true;
55
+ } else if (metaDataObject.wrap === false || metaDataObject.wrap === 'false') {
56
+ result.wrap = false;
57
+ }
58
+
59
+ return result;
60
+ };
61
+
17
62
  const Code = ({ children, className: classNameProp = '', metaData }) => {
18
- const [path, setPath] = useState('');
19
- const [src, setSrc] = useState('');
20
- const [showAll, setShowAll] = useState(false);
63
+ // Initialize from metaData so SSR/first paint already reflects
64
+ // path/src/showAll/wrap (useEffect below keeps them in sync on change).
65
+ const [path, setPath] = useState(() => parseCodeMetaData(metaData).path);
66
+ const [src, setSrc] = useState(() => parseCodeMetaData(metaData).src);
67
+ const [showAll, setShowAll] = useState(
68
+ () => parseCodeMetaData(metaData).showAll
69
+ );
70
+ const [wrap, setWrap] = useState(() => parseCodeMetaData(metaData).wrap);
21
71
  const [hasMoreThanNineLines, setHasMoreThanNineLines] = useState(false);
22
72
  const [shouldShowMore, setShouldShowMore] = useState(false);
23
73
  const [isInlineCode, setIsInlineCode] = useState(false);
@@ -29,38 +79,15 @@ const Code = ({ children, className: classNameProp = '', metaData }) => {
29
79
  }, [classNameProp]);
30
80
 
31
81
  useEffect(() => {
32
- // metaData string is of format: path=/directory/file.mdx src=https://gatsby.carbondesignsystem.com
33
- if (metaData) {
34
- const metaDataObject = metaData.split(' ').reduce((obj, item) => {
35
- const [key, value] = item.split('=');
36
-
37
- // Checking for boolean values coming as string or not coming like in showAll prop
38
- if (value === 'true' || !value) {
39
- obj[key] = true;
40
- } else if (value === 'false') {
41
- obj[key] = false;
42
- } else {
43
- // else setting what is passed after =
44
- obj[key] = value;
45
- }
46
- return obj;
47
- }, {});
48
-
49
- if (metaDataObject.path) {
50
- setPath(metaDataObject.path);
51
- }
52
-
53
- if (metaDataObject.src) {
54
- setSrc(metaDataObject.src);
55
- }
56
-
57
- if (metaDataObject.showAll) {
58
- setShowAll(true);
59
- }
60
- }
82
+ const parsed = parseCodeMetaData(metaData);
83
+ setPath(parsed.path);
84
+ setSrc(parsed.src);
85
+ setShowAll(parsed.showAll);
86
+ setWrap(parsed.wrap);
61
87
  }, [metaData]);
62
88
 
63
- const { interiorTheme } = useMetadata();
89
+ const { interiorTheme, isCodeWrapEnabled } = useMetadata();
90
+ const shouldWrap = wrap !== null ? wrap : Boolean(isCodeWrapEnabled);
64
91
  const language = classNameProp.replace(/language-/, '').replace('mdx', 'jsx');
65
92
 
66
93
  const removeTrailingEmptyLine = (lines) => {
@@ -90,6 +117,7 @@ const Code = ({ children, className: classNameProp = '', metaData }) => {
90
117
  if (isInlineCode) {
91
118
  return <code>{children}</code>;
92
119
  }
120
+ const showToggleButton = hasMoreThanNineLines && !showAll;
93
121
  return (
94
122
  <Row className={cx(styles.row)}>
95
123
  <PathRow src={src} path={path}>
@@ -100,20 +128,33 @@ const Code = ({ children, className: classNameProp = '', metaData }) => {
100
128
  language={language}
101
129
  theme={getTheme(interiorTheme)}>
102
130
  {({ className, style, tokens, getLineProps, getTokenProps }) => (
103
- <div className={styles.container}>
131
+ <div
132
+ className={cx(styles.container, {
133
+ [styles.hasButton]: showToggleButton,
134
+ [styles.wrapContainer]: shouldWrap,
135
+ })}>
104
136
  <pre
105
137
  className={cx(styles.highlight, {
106
138
  [styles.sideBarMinHeight]: !path && src,
139
+ [styles.wrap]: shouldWrap,
107
140
  [className]: className,
108
141
  })}
109
142
  style={style}>
110
- {getLines(tokens).map((line, i) => (
111
- <div {...getLineProps({ line, key: i })} key={i}>
112
- {line.map((token, key) => (
113
- <span {...getTokenProps({ token, key })} key={key} />
114
- ))}
115
- </div>
116
- ))}
143
+ {getLines(tokens).map((line, i) => {
144
+ const lineProps = getLineProps({ line, key: i });
145
+ return (
146
+ <div
147
+ {...lineProps}
148
+ key={i}
149
+ className={cx(lineProps.className, {
150
+ [styles.wrappedLine]: shouldWrap,
151
+ })}>
152
+ {line.map((token, key) => (
153
+ <span {...getTokenProps({ token, key })} key={key} />
154
+ ))}
155
+ </div>
156
+ );
157
+ })}
117
158
  </pre>
118
159
  <Sidebar path={path} src={src}>
119
160
  {children}
@@ -121,12 +162,14 @@ const Code = ({ children, className: classNameProp = '', metaData }) => {
121
162
  </div>
122
163
  )}
123
164
  </Highlight>
124
- {hasMoreThanNineLines && !showAll && (
165
+ {showToggleButton && (
125
166
  <button
126
167
  className={cx(styles.showMoreButton, {
127
168
  [styles.dark]: interiorTheme === 'dark',
128
169
  })}
129
170
  onClick={() => setShouldShowMore(!shouldShowMore)}
171
+ aria-expanded={shouldShowMore}
172
+ aria-label={shouldShowMore ? 'Show less code' : 'Show more code'}
130
173
  type="button">
131
174
  {shouldShowMore ? (
132
175
  <>
@@ -67,6 +67,44 @@ li .row {
67
67
  text-wrap: wrap;
68
68
  }
69
69
 
70
+ // Reserve room for the absolutely positioned Show more/less button so it
71
+ // doesn't cover code in default and nested-list contexts (see #868).
72
+ .has-button .highlight {
73
+ padding-bottom: $spacing-10;
74
+ }
75
+
76
+ // Opt-in soft wrap via ```lang wrap. Breaks long log lines/URLs instead of
77
+ // forcing horizontal scroll, while keeping copy content unchanged.
78
+ .highlight.wrap {
79
+ white-space: pre-wrap;
80
+ overflow-wrap: anywhere;
81
+ overflow-x: hidden;
82
+ // Reserve room for the copy/src buttons so wrapped line endings never
83
+ // render underneath the sidebar (see #868).
84
+ padding-right: 3rem;
85
+ }
86
+
87
+ .wrapped-line {
88
+ white-space: pre-wrap;
89
+ overflow-wrap: anywhere;
90
+ }
91
+
92
+ // In wrap mode nothing scrolls under the sidebar, so drop the fade gradient
93
+ // (it would obscure wrapped line endings) and shrink to the button column.
94
+ .wrap-container .sidebar {
95
+ background: transparent;
96
+ padding-left: 8px;
97
+ }
98
+
99
+ .wrap-container .sidebar.dark {
100
+ background: transparent;
101
+ }
102
+
103
+ // Don't force extra scroll width when wrapping; keep it for scroll mode only.
104
+ .highlight.wrap :global(.token:last-of-type) {
105
+ margin-right: 0;
106
+ }
107
+
70
108
  .sideBarMinHeight {
71
109
  min-height: 96px;
72
110
  }
@@ -64,7 +64,7 @@
64
64
  padding: 0;
65
65
  border: 0;
66
66
  margin: -1px;
67
- clip: rect(0, 0, 0, 0);
67
+ clip-path: inset(50%);
68
68
  visibility: inherit;
69
69
  white-space: nowrap;
70
70
  }
@@ -141,21 +141,23 @@ export const Tabs = (props) => {
141
141
  [setActiveTab, activeTab, tabList.current]
142
142
  );
143
143
  return (
144
- <TabContext.Provider value={value}>
145
- {isMobile ? (
146
- <Select _id={id}>{props.children}</Select>
147
- ) : (
148
- <TabList _id={id}>{props.children}</TabList>
149
- )}
150
- {React.Children.map(props.children, (child, index) => {
151
- if (elementIsNullOrString(child)) return child;
152
- return React.cloneElement(child, {
153
- _id: `${id}__${index}`,
154
- active: activeTab === index,
155
- index,
156
- });
157
- })}
158
- </TabContext.Provider>
144
+ <div className={cx(styles.tabs, props.className)}>
145
+ <TabContext.Provider value={value}>
146
+ {isMobile ? (
147
+ <Select _id={id}>{props.children}</Select>
148
+ ) : (
149
+ <TabList _id={id}>{props.children}</TabList>
150
+ )}
151
+ {React.Children.map(props.children, (child, index) => {
152
+ if (elementIsNullOrString(child)) return child;
153
+ return React.cloneElement(child, {
154
+ _id: `${id}__${index}`,
155
+ active: activeTab === index,
156
+ index,
157
+ });
158
+ })}
159
+ </TabContext.Provider>
160
+ </div>
159
161
  );
160
162
  };
161
163
 
@@ -1,3 +1,7 @@
1
+ .tabs + * {
2
+ --space: #{$spacing-05};
3
+ }
4
+
1
5
  .tab-list {
2
6
  display: flex;
3
7
  max-width: min-content;
@@ -193,3 +193,11 @@ img {
193
193
  span:has(:global(.gatsby-resp-image-wrapper)) {
194
194
  display: block;
195
195
  }
196
+
197
+ // Linked images ([![alt](src)](href)) inherit paragraph text padding.
198
+ // Reset it so the image aligns flush like a standalone image.
199
+ .paragraph > a:global(.gatsby-resp-image-link) {
200
+ display: block;
201
+ margin: 0;
202
+ padding: 0;
203
+ }
@@ -12,6 +12,7 @@ const useMetadata = () => {
12
12
  isSwitcherEnabled
13
13
  homepageTheme
14
14
  interiorTheme
15
+ isCodeWrapEnabled
15
16
  navigationStyle
16
17
  lang
17
18
  }