-
Notifications
You must be signed in to change notification settings - Fork 31
feat: Detailed code component for longer code examples #3236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 15 commits
0f23814
a95a191
b688abc
ac512c6
51d159d
d0a7dad
2711ba7
ccc004e
6f8e4b9
d488790
ade841a
b7cff3f
0394239
bb75a84
9a52768
da8b65f
ada707c
25c9f86
2d61914
5d10a0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { Source } from '@storybook/blocks'; | ||
|
|
||
| import { DetailedCodeBodyWrapper, FloatingIndicator } from '../elements'; | ||
| import { DetailedCodeBodyProps } from '../types'; | ||
|
|
||
| export const DetailedCodeBody: React.FC<DetailedCodeBodyProps> = ({ | ||
| code, | ||
| language, | ||
| showFloatingBadge = false, | ||
| }) => { | ||
| return ( | ||
| <DetailedCodeBodyWrapper hasFloatingBadge={showFloatingBadge}> | ||
| <Source code={code} dark language={language} /> | ||
| {showFloatingBadge && ( | ||
| <FloatingIndicator aria-label="More code below">...</FloatingIndicator> | ||
| )} | ||
| </DetailedCodeBodyWrapper> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { MiniChevronDownIcon } from '@codecademy/gamut-icons'; | ||
| import * as React from 'react'; | ||
| import { Anchor, Rotation, FlexBox, Text } from '@codecademy/gamut'; | ||
|
|
||
| import { DetailedCodeButtonProps } from '../types'; | ||
|
|
||
| export const DetailedCodeButton: React.FC<DetailedCodeButtonProps> = ({ | ||
| isExpanded, | ||
| onToggle, | ||
| language, | ||
| }) => { | ||
| return ( | ||
| <Anchor | ||
| aria-expanded={isExpanded} | ||
| px={16} | ||
| py={12} | ||
| variant="interface" | ||
| width="100%" | ||
| onClick={onToggle} | ||
| > | ||
| <FlexBox columnGap={16} justifyContent="space-between"> | ||
| <Text>{language}</Text> | ||
| <FlexBox columnGap={8} flexDirection="row" alignItems="center"> | ||
| <Text>{isExpanded ? 'Show Less Code' : 'Show More Code'}</Text> | ||
| <Rotation rotated={isExpanded}> | ||
| <MiniChevronDownIcon aria-hidden size={16} /> | ||
| </Rotation> | ||
| </FlexBox> | ||
| </FlexBox> | ||
| </Anchor> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { css } from '@codecademy/gamut-styles'; | ||
| import styled from '@emotion/styled'; | ||
| import { FlexBox } from '@codecademy/gamut'; | ||
|
|
||
| export const DetailedCodeWrapper = styled(FlexBox)( | ||
| css({ | ||
| width: '100%', | ||
| flexDirection: 'column', | ||
| borderRadius: 'md', | ||
| border: 1, | ||
| bg: 'background', | ||
| }) | ||
| ); | ||
|
|
||
| export const DetailedCodeBodyWrapper = styled(FlexBox)<{ | ||
| hasFloatingBadge?: boolean; | ||
|
nhivpham marked this conversation as resolved.
Outdated
|
||
| }>(({ hasFloatingBadge }) => | ||
| css({ | ||
| position: 'relative', | ||
| flexDirection: 'column', | ||
| /* Override Storybook's Source component default styles to remove unwanted spacing and borders in the container */ | ||
| '& .docblock-source': { | ||
| borderRadius: 'none', | ||
| margin: 0, | ||
| pb: hasFloatingBadge ? 48 : 0, | ||
|
nhivpham marked this conversation as resolved.
Outdated
|
||
| }, | ||
| }) | ||
| ); | ||
|
|
||
| export const FloatingIndicator = styled(FlexBox)( | ||
|
nhivpham marked this conversation as resolved.
Outdated
|
||
| css({ | ||
| position: 'absolute', | ||
| bottom: 16, | ||
| left: '50%', | ||
| transform: 'translateX(-50%)', | ||
| zIndex: 1, | ||
| bg: 'inherit', | ||
| px: 12, | ||
| py: 4, | ||
| borderRadius: 'lg', | ||
|
nhivpham marked this conversation as resolved.
Outdated
|
||
| fontSize: 26, | ||
| fontWeight: 700, | ||
| color: 'white', | ||
|
nhivpham marked this conversation as resolved.
Outdated
|
||
| letterSpacing: '0.1em', | ||
| }) | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import React, { useState } from 'react'; | ||
|
|
||
| import { DetailedCodeBody } from './DetailedCodeBody'; | ||
| import { DetailedCodeButton } from './DetailedCodeButton'; | ||
| import { DetailedCodeWrapper } from './elements'; | ||
| import { DetailedCodeProps } from './types'; | ||
|
|
||
| const DEFAULT_PREVIEW_LINES = 20; | ||
| const DEFAULT_LANGUAGE = 'tsx'; | ||
|
|
||
| export const DetailedCode: React.FC<DetailedCodeProps> = ({ | ||
| code, | ||
| initiallyExpanded = false, | ||
| language = DEFAULT_LANGUAGE, | ||
| preview = false, | ||
| previewLines = DEFAULT_PREVIEW_LINES, | ||
| }) => { | ||
| const [isExpanded, setIsExpanded] = useState(initiallyExpanded); | ||
| const normalizedPreviewLines = Math.max(0, previewLines); | ||
| const previewEnabled = preview && normalizedPreviewLines > 0; | ||
|
|
||
| const codeLines = code.split('\n'); | ||
| const hasMoreCode = | ||
| previewEnabled && codeLines.length > normalizedPreviewLines; | ||
|
|
||
| const previewCode = previewEnabled | ||
| ? codeLines.slice(0, normalizedPreviewLines).join('\n') | ||
| : code; | ||
|
|
||
| const displayedCode = isExpanded ? code : previewCode; | ||
|
|
||
| return ( | ||
| <DetailedCodeWrapper> | ||
| <DetailedCodeBody | ||
| code={displayedCode} | ||
| language={language} | ||
| showFloatingBadge={hasMoreCode && !isExpanded} | ||
| /> | ||
| {hasMoreCode && ( | ||
| <DetailedCodeButton | ||
| isExpanded={isExpanded} | ||
| onToggle={() => setIsExpanded((prev) => !prev)} | ||
| language={language} | ||
| /> | ||
| )} | ||
| </DetailedCodeWrapper> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { Source } from '@storybook/blocks'; | ||
| import { ComponentProps } from 'react'; | ||
|
|
||
| type SourceLanguage = ComponentProps<typeof Source>['language']; | ||
|
|
||
| export interface DetailedCodeProps { | ||
| code: string; | ||
| language?: SourceLanguage; | ||
| initiallyExpanded?: boolean; | ||
| preview?: boolean; | ||
| previewLines?: number; | ||
|
Comment on lines
+7
to
+11
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For these types, and the other types as well, I'd recommend adding comments to explain what the prop is for/doing see: packages/gamut/src/Disclosure/types.ts as an example |
||
| } | ||
|
|
||
| export interface DetailedCodeButtonProps { | ||
| isExpanded: boolean; | ||
| onToggle: () => void; | ||
| language: SourceLanguage; | ||
| } | ||
|
|
||
| export interface DetailedCodeBodyProps { | ||
| code: string; | ||
| language: SourceLanguage; | ||
| showFloatingBadge?: boolean; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,67 @@ | ||||||||
| export const example = `import { | ||||||||
| ConnectedCheckbox, | ||||||||
| ConnectedInput, | ||||||||
| ConnectedSelect, | ||||||||
|
||||||||
| ConnectedSelect, | |
| ConnectedSelect, | |
| SubmitButton, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems valid fwiw, @nhivpham
Uh oh!
There was an error while loading. Please reload this page.