-
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 all 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,22 @@ | ||
| import { Source } from '@storybook/addon-docs/blocks'; | ||
|
|
||
| import { DetailedCodeBodyWrapper, FloatingIndicator } from '../elements'; | ||
| import { DetailedCodeBodyProps } from '../types'; | ||
|
|
||
| export const DetailedCodeBody: React.FC<DetailedCodeBodyProps> = ({ | ||
| code, | ||
| language, | ||
| showEllipses = false, | ||
| hiddenLineCount, | ||
| }) => { | ||
| return ( | ||
| <DetailedCodeBodyWrapper hasShowCodeButton={showEllipses}> | ||
| <Source code={code} dark language={language} /> | ||
| {showEllipses && ( | ||
| <FloatingIndicator aria-label="More code below"> | ||
| ... {hiddenLineCount} more line{hiddenLineCount === 1 ? '' : 's'} | ||
| </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} 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,42 @@ | ||
| import { css } from '@codecademy/gamut-styles'; | ||
| import styled from '@emotion/styled'; | ||
| import { FlexBox, Box } from '@codecademy/gamut'; | ||
|
|
||
| export const DetailedCodeWrapper = styled(FlexBox)( | ||
| css({ | ||
| flexDirection: 'column', | ||
| borderRadius: 'md', | ||
| border: 1, | ||
| bg: 'background', | ||
| }) | ||
| ); | ||
|
|
||
| export const DetailedCodeBodyWrapper = styled(FlexBox)<{ | ||
| hasShowCodeButton?: boolean; | ||
| }>(({ hasShowCodeButton }) => | ||
| 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, | ||
| }, | ||
| /* Reserves space under the text for the overlay */ | ||
| '& .docblock-source pre': { | ||
| pb: hasShowCodeButton ? 48 : 20, | ||
|
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. changing the 20 => 24 would make the TS error go away |
||
| }, | ||
| }) | ||
| ); | ||
|
|
||
| export const FloatingIndicator = styled(Box)( | ||
| css({ | ||
| position: 'absolute', | ||
| bottom: 16, | ||
| left: 16, | ||
| px: 12, | ||
| fontSize: 14, | ||
| fontFamily: 'monospace', | ||
| textColor: '#C9CDCF', | ||
|
Comment on lines
+32
to
+40
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. On the second thought, let's follow some best practices and follow the design system I want you to use You will need to make more changes, lemme know if you'd want a hint but think of it as a challenge :)
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. |
||
| }) | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| 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 allLines = code.trimEnd().split('\n'); | ||
| const hiddenLineCount = previewEnabled | ||
| ? Math.max(0, allLines.length - normalizedPreviewLines) | ||
| : 0; | ||
| const hasMoreCode = hiddenLineCount > 0; | ||
|
|
||
| const codeSnippet = previewEnabled | ||
| ? allLines.slice(0, normalizedPreviewLines).join('\n') | ||
| : code; | ||
|
|
||
| const displayedCode = isExpanded ? code : codeSnippet; | ||
|
|
||
| return ( | ||
| <DetailedCodeWrapper> | ||
| <DetailedCodeBody | ||
| code={displayedCode} | ||
| language={language} | ||
| showEllipses={hasMoreCode && !isExpanded} | ||
| hiddenLineCount={hiddenLineCount} | ||
| /> | ||
| {hasMoreCode && ( | ||
| <DetailedCodeButton | ||
| isExpanded={isExpanded} | ||
| onToggle={() => setIsExpanded((prev) => !prev)} | ||
| language={language} | ||
| /> | ||
| )} | ||
| </DetailedCodeWrapper> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { Source } from '@storybook/addon-docs/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; | ||
| showEllipses?: boolean; | ||
| hiddenLineCount: number; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,9 @@ | ||
| import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks'; | ||
|
|
||
| import { ComponentHeader, LinkTo } from '~styleguide/blocks'; | ||
| import { ComponentHeader, DetailedCode, LinkTo } from '~styleguide/blocks'; | ||
|
|
||
| import * as ConnectedFormStories from './ConnectedForm.stories'; | ||
| import { example } from './example'; | ||
|
|
||
| export const parameters = { | ||
| title: 'ConnectedForm', | ||
|
|
@@ -40,75 +41,11 @@ This hook also returns the `FormRequiredText` component - include this before yo | |
|
|
||
| ### Example code | ||
|
|
||
| ```tsx | ||
| import { | ||
| ConnectedCheckbox, | ||
| ConnectedInput, | ||
| ConnectedSelect, | ||
| useConnectedForm, | ||
| } from '@codecademy/gamut'; | ||
|
|
||
| import { TerminalIcon } from '@codecademy/gamut-icons'; | ||
|
|
||
| export const GoodForm = () => { | ||
| const { | ||
| ConnectedFormGroup, | ||
| ConnectedForm, | ||
| connectedFormProps, | ||
| FormRequiredText, | ||
| } = useConnectedForm({ | ||
| defaultValues: { | ||
| thisField: true, | ||
| thatField: 'zero', | ||
| anotherField: 'state your name.', | ||
| }, | ||
| validationRules: { | ||
| thisField: { required: 'you need to check this.' }, | ||
| thatField: { | ||
| pattern: { | ||
| value: /^(?:(?!zero).)*$/, | ||
| message: 'literally anything but zero', | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| return ( | ||
| <ConnectedForm | ||
| onSubmit={({ thisField }) => console.log(thisField)} | ||
| resetOnSubmit | ||
| {...connectedFormProps} | ||
| > | ||
| <SubmitButton>submit this form.</SubmitButton> | ||
| <ConnectedFormGroup | ||
| name="thisField" | ||
| label="cool checkbox bruh" | ||
| field={{ | ||
| component: ConnectedCheckbox, | ||
| label: 'check it ouuut', | ||
| }} | ||
| /> | ||
| <ConnectedFormGroup | ||
| name="thatField" | ||
| label="cool select dude" | ||
| field={{ | ||
| component: ConnectedSelect, | ||
| options: ['one', 'two', 'zero'], | ||
| }} | ||
| /> | ||
| <ConnectedFormGroup | ||
| name="anotherField" | ||
| label="cool input" | ||
| field={{ | ||
| component: ConnectedInput, | ||
| icon: TerminalIcon, | ||
| }} | ||
| /> | ||
| <FormRequiredText /> | ||
| </ConnectedForm> | ||
| ); | ||
| }; | ||
| ``` | ||
| <DetailedCode code={example} language="tsx" preview /> | ||
|
|
||
| ### Example Code Preview False | ||
|
|
||
| <DetailedCode code={example} language="tsx" /> | ||
|
Comment on lines
+46
to
+48
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. Nice, I would add some kind of comment to say that this is for testing purposes to show the comparison -- as to denote that you're going to clean this up after review |
||
|
|
||
| ## Variants | ||
|
|
||
|
|
||
| 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

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.
Right now the flow on VoiceOver is:
Is it possible to make it so that "more code below" is read before the "copy" button?