@codecademy/styleguide 79.1.3-alpha.4c2055.0 → 79.1.3-alpha.5aed17.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.
@@ -2,7 +2,6 @@ export * from './ImageWrapper';
2
2
  export * from './TokenTable';
3
3
  export * from './Headers';
4
4
  export * from './Elements/Callout';
5
- export * from './Elements/DetailedCode';
6
5
  export * from './Elements/KeyboardKey';
7
6
  export * from './Elements/Markdown';
8
7
  export * from './Elements/ImageGallery';
package/CHANGELOG.md CHANGED
@@ -3,9 +3,11 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ### [79.1.3-alpha.4c2055.0](https://github.com/Codecademy/gamut/compare/@codecademy/styleguide@79.1.2...@codecademy/styleguide@79.1.3-alpha.4c2055.0) (2026-02-20)
6
+ ### [79.1.3-alpha.5aed17.0](https://github.com/Codecademy/gamut/compare/@codecademy/styleguide@79.1.2...@codecademy/styleguide@79.1.3-alpha.5aed17.0) (2026-02-23)
7
7
 
8
- **Note:** Version bump only for package @codecademy/styleguide
8
+ ### Bug Fixes
9
+
10
+ - **CSP:** add better nonce support ([afdff6e](https://github.com/Codecademy/gamut/commit/afdff6e78aa8bed6ce8e051742b3e52822b9ba82))
9
11
 
10
12
  ### [79.1.2](https://github.com/Codecademy/gamut/compare/@codecademy/styleguide@79.1.1...@codecademy/styleguide@79.1.2) (2026-02-12)
11
13
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@codecademy/styleguide",
3
3
  "description": "Styleguide & Component library for codecademy.com",
4
- "version": "79.1.3-alpha.4c2055.0",
4
+ "version": "79.1.3-alpha.5aed17.0",
5
5
  "author": "Codecademy Engineering",
6
6
  "license": "MIT",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
10
10
  "repository": "git@github.com:Codecademy/gamut.git",
11
- "gitHead": "66a47d95c5cda3c06ca46315f16a377c238014d4"
11
+ "gitHead": "2fc086084082b22b37677da7054e586293aa2e78"
12
12
  }
@@ -63,6 +63,25 @@ GamutProvider handles a few critical tasks that need to happen in order for comp
63
63
  3. Adds Global styles and CSS Variables
64
64
  4. Sets the current Color Mode context and variables.
65
65
 
66
+ ### Content Security Policy (CSP)
67
+
68
+ If your app uses a strict Content-Security-Policy (e.g. `style-src` without `'unsafe-inline'`), pass a nonce to `GamutProvider` so Emotion and other Gamut-managed style tags are allowed:
69
+
70
+ ```tsx
71
+ <GamutProvider nonce={yourCspNonce}>
72
+ <App />
73
+ </GamutProvider>
74
+ ```
75
+
76
+ Your nonce should be the same value you use in your CSP header (e.g. `style-src 'self' 'nonce-{value}'`). Gamut uses the [get-nonce](https://www.npmjs.com/package/get-nonce) singleton so that style tags injected by react-style-singleton (e.g. from FocusTrap) also receive this nonce.
77
+
78
+ **Motion components:** When you pass a nonce to GamutProvider, it also wraps children in framer-motion's MotionConfig so that motion components (Drawer, Alert, List, Toaster, etc.) get the nonce on their injected style tags. No extra wrapper is needed.
79
+
80
+ **Video (Vidstack):** The Video component uses `@vidstack/react`, which applies styles via inline styles (element.style / CSSOM). CSP nonces apply only to `<style>` and `<script>` elements, not to inline style attributes, so **nonce cannot fix** Vidstack's CSP violations. If you use strict CSP and the Video component, relax CSP for that context (e.g. allow the Video route or sandbox) or avoid using Video where strict CSP is required.
81
+
82
+
83
+
84
+
66
85
  **Note:** For react frameworks like Next and Gatsby this will be slightly different (see the SSR section for further steps for each framework). Your entry points for each framework will be:
67
86
 
68
87
  - **Next** `_app.tsx`
@@ -1,9 +1,8 @@
1
1
  import { Canvas, Controls, Meta } from '@storybook/blocks';
2
2
 
3
- import { ComponentHeader, DetailedCode, LinkTo } from '~styleguide/blocks';
3
+ import { ComponentHeader, LinkTo } from '~styleguide/blocks';
4
4
 
5
5
  import * as ConnectedFormStories from './ConnectedForm.stories';
6
- import { example } from './example';
7
6
 
8
7
  export const parameters = {
9
8
  title: 'ConnectedForm',
@@ -41,7 +40,75 @@ This hook also returns the `FormRequiredText` component - include this before yo
41
40
 
42
41
  ### Example code
43
42
 
44
- <DetailedCode code={example} language="tsx" preview />
43
+ ```tsx
44
+ import {
45
+ ConnectedCheckbox,
46
+ ConnectedInput,
47
+ ConnectedSelect,
48
+ useConnectedForm,
49
+ } from '@codecademy/gamut';
50
+
51
+ import { TerminalIcon } from '@codecademy/gamut-icons';
52
+
53
+ export const GoodForm = () => {
54
+ const {
55
+ ConnectedFormGroup,
56
+ ConnectedForm,
57
+ connectedFormProps,
58
+ FormRequiredText,
59
+ } = useConnectedForm({
60
+ defaultValues: {
61
+ thisField: true,
62
+ thatField: 'zero',
63
+ anotherField: 'state your name.',
64
+ },
65
+ validationRules: {
66
+ thisField: { required: 'you need to check this.' },
67
+ thatField: {
68
+ pattern: {
69
+ value: /^(?:(?!zero).)*$/,
70
+ message: 'literally anything but zero',
71
+ },
72
+ },
73
+ },
74
+ });
75
+
76
+ return (
77
+ <ConnectedForm
78
+ onSubmit={({ thisField }) => console.log(thisField)}
79
+ resetOnSubmit
80
+ {...connectedFormProps}
81
+ >
82
+ <SubmitButton>submit this form.</SubmitButton>
83
+ <ConnectedFormGroup
84
+ name="thisField"
85
+ label="cool checkbox bruh"
86
+ field={{
87
+ component: ConnectedCheckbox,
88
+ label: 'check it ouuut',
89
+ }}
90
+ />
91
+ <ConnectedFormGroup
92
+ name="thatField"
93
+ label="cool select dude"
94
+ field={{
95
+ component: ConnectedSelect,
96
+ options: ['one', 'two', 'zero'],
97
+ }}
98
+ />
99
+ <ConnectedFormGroup
100
+ name="anotherField"
101
+ label="cool input"
102
+ field={{
103
+ component: ConnectedInput,
104
+ icon: TerminalIcon,
105
+ }}
106
+ />
107
+ <FormRequiredText />
108
+ </ConnectedForm>
109
+ );
110
+ };
111
+ ```
45
112
 
46
113
  ## Variants
47
114
 
@@ -1,20 +0,0 @@
1
- import { Source } from '@storybook/blocks';
2
- import * as React from 'react';
3
-
4
- import { DetailedCodeBodyWrapper, FloatingIndicator } from '../elements';
5
- import { DetailedCodeBodyProps } from '../types';
6
-
7
- export const DetailedCodeBody: React.FC<DetailedCodeBodyProps> = ({
8
- code,
9
- language,
10
- showFloatingBadge = false,
11
- }) => {
12
- return (
13
- <DetailedCodeBodyWrapper hasFloatingBadge={showFloatingBadge}>
14
- <Source code={code} dark language={language} />
15
- {showFloatingBadge && (
16
- <FloatingIndicator aria-label="More code below">...</FloatingIndicator>
17
- )}
18
- </DetailedCodeBodyWrapper>
19
- );
20
- };
@@ -1,46 +0,0 @@
1
- import { MiniChevronDownIcon } from '@codecademy/gamut-icons';
2
- import * as React from 'react';
3
- import { Anchor, Rotation, FlexBox, Text } from '@codecademy/gamut';
4
-
5
- import { DetailedCodeButtonProps } from '../types';
6
-
7
- export const DetailedCodeButton: React.FC<DetailedCodeButtonProps> = ({
8
- isExpanded,
9
- setIsExpanded,
10
- language,
11
- }) => {
12
- const handleClick = () => {
13
- if (setIsExpanded) {
14
- setIsExpanded((prev) => !prev);
15
- }
16
- };
17
-
18
- return (
19
- <Anchor
20
- aria-expanded={isExpanded}
21
- height="100%"
22
- px={16}
23
- py={12}
24
- variant="interface"
25
- width="100%"
26
- onClick={handleClick}
27
- >
28
- <FlexBox
29
- columnGap={16}
30
- flexDirection="row"
31
- justifyContent="space-between"
32
- width="100%"
33
- >
34
- <Text>{language}</Text>
35
- <FlexBox columnGap={8} flexDirection="row" alignItems="center">
36
- <Text fontWeight={400}>
37
- {isExpanded ? 'Show Less Code' : 'Show More Code'}
38
- </Text>
39
- <Rotation height={16} rotated={isExpanded} width={16}>
40
- <MiniChevronDownIcon aria-hidden size={16} />
41
- </Rotation>
42
- </FlexBox>
43
- </FlexBox>
44
- </Anchor>
45
- );
46
- };
@@ -1,46 +0,0 @@
1
- import { css } from '@codecademy/gamut-styles';
2
- import styled from '@emotion/styled';
3
- import { FlexBox } from '@codecademy/gamut';
4
-
5
- export const DetailedCodeWrapper = styled(FlexBox)(
6
- css({
7
- width: '100%',
8
- flexDirection: 'column',
9
- borderRadius: 'md',
10
- border: 1,
11
- bg: 'background',
12
- })
13
- );
14
-
15
- export const DetailedCodeBodyWrapper = styled(FlexBox)<{
16
- hasFloatingBadge?: boolean;
17
- }>(({ hasFloatingBadge }) =>
18
- css({
19
- position: 'relative',
20
- flexDirection: 'column',
21
- /* Override Storybook's Source component default styles to remove unwanted spacing and borders in the container */
22
- '& .docblock-source': {
23
- borderRadius: 'none',
24
- margin: 0,
25
- pb: hasFloatingBadge ? 48 : 0,
26
- },
27
- })
28
- );
29
-
30
- export const FloatingIndicator = styled(FlexBox)(
31
- css({
32
- position: 'absolute',
33
- bottom: 16,
34
- left: '50%',
35
- transform: 'translateX(-50%)',
36
- zIndex: 1,
37
- bg: 'inherit',
38
- px: 12,
39
- py: 4,
40
- borderRadius: 'lg',
41
- fontSize: 26,
42
- fontWeight: 700,
43
- color: 'white',
44
- letterSpacing: '0.1em',
45
- })
46
- );
@@ -1,56 +0,0 @@
1
- import React, { useState } from 'react';
2
-
3
- import { DetailedCodeBody } from './DetailedCodeBody';
4
- import { DetailedCodeButton } from './DetailedCodeButton';
5
- import { DetailedCodeWrapper } from './elements';
6
- import { DetailedCodeProps } from './types';
7
-
8
- const DEFAULT_PREVIEW_LINES = 20;
9
- const DEFAULT_LANGUAGE = 'tsx';
10
-
11
- const getPreviewCode = (code: string, previewLines: number) => {
12
- const lines = code.split('\n');
13
-
14
- if (lines.length <= previewLines) {
15
- return code;
16
- }
17
-
18
- return lines.slice(0, previewLines).join('\n');
19
- };
20
-
21
- export const DetailedCode: React.FC<DetailedCodeProps> = ({
22
- code,
23
- initiallyExpanded = false,
24
- language = DEFAULT_LANGUAGE,
25
- preview = false,
26
- previewLines = DEFAULT_PREVIEW_LINES,
27
- }) => {
28
- const [isExpanded, setIsExpanded] = useState(initiallyExpanded);
29
- const normalizedPreviewLines = Math.max(0, previewLines);
30
- const previewEnabled = preview && normalizedPreviewLines > 0;
31
- const previewCode = previewEnabled
32
- ? getPreviewCode(code, normalizedPreviewLines)
33
- : code;
34
-
35
- const hasMoreCode =
36
- previewEnabled && code.split('\n').length > normalizedPreviewLines;
37
-
38
- const displayedCode = isExpanded ? code : previewCode;
39
-
40
- return (
41
- <DetailedCodeWrapper>
42
- <DetailedCodeBody
43
- code={displayedCode}
44
- language={language}
45
- showFloatingBadge={hasMoreCode && !isExpanded}
46
- />
47
- {hasMoreCode && (
48
- <DetailedCodeButton
49
- isExpanded={isExpanded}
50
- setIsExpanded={setIsExpanded}
51
- language={language}
52
- />
53
- )}
54
- </DetailedCodeWrapper>
55
- );
56
- };
@@ -1,24 +0,0 @@
1
- import { Source } from '@storybook/blocks';
2
- import { ComponentProps } from 'react';
3
-
4
- type SourceLanguage = ComponentProps<typeof Source>['language'];
5
-
6
- export interface DetailedCodeProps {
7
- code: string;
8
- language?: SourceLanguage;
9
- initiallyExpanded?: boolean;
10
- preview?: boolean;
11
- previewLines?: number;
12
- }
13
-
14
- export interface DetailedCodeButtonProps {
15
- isExpanded: boolean;
16
- setIsExpanded: React.Dispatch<React.SetStateAction<boolean>>;
17
- language: SourceLanguage;
18
- }
19
-
20
- export interface DetailedCodeBodyProps {
21
- code: string;
22
- language: SourceLanguage;
23
- showFloatingBadge?: boolean;
24
- }
@@ -1,67 +0,0 @@
1
- export const example = `import {
2
- ConnectedCheckbox,
3
- ConnectedInput,
4
- ConnectedSelect,
5
- useConnectedForm,
6
- } from '@codecademy/gamut';
7
-
8
- import { TerminalIcon } from '@codecademy/gamut-icons';
9
-
10
- export const GoodForm = () => {
11
- const {
12
- ConnectedFormGroup,
13
- ConnectedForm,
14
- connectedFormProps,
15
- FormRequiredText,
16
- } = useConnectedForm({
17
- defaultValues: {
18
- thisField: true,
19
- thatField: 'zero',
20
- anotherField: 'state your name.',
21
- },
22
- validationRules: {
23
- thisField: { required: 'you need to check this.' },
24
- thatField: {
25
- pattern: {
26
- value: /^(?:(?!zero).)*$/,
27
- message: 'literally anything but zero',
28
- },
29
- },
30
- },
31
- });
32
-
33
- return (
34
- <ConnectedForm
35
- onSubmit={({ thisField }) => console.log(thisField)}
36
- resetOnSubmit
37
- {...connectedFormProps}
38
- >
39
- <SubmitButton>submit this form.</SubmitButton>
40
- <ConnectedFormGroup
41
- name="thisField"
42
- label="cool checkbox bruh"
43
- field={{
44
- component: ConnectedCheckbox,
45
- label: 'check it ouuut',
46
- }}
47
- />
48
- <ConnectedFormGroup
49
- name="thatField"
50
- label="cool select dude"
51
- field={{
52
- component: ConnectedSelect,
53
- options: ['one', 'two', 'zero'],
54
- }}
55
- />
56
- <ConnectedFormGroup
57
- name="anotherField"
58
- label="cool input"
59
- field={{
60
- component: ConnectedInput,
61
- icon: TerminalIcon,
62
- }}
63
- />
64
- <FormRequiredText />
65
- </ConnectedForm>
66
- );
67
- };`;