diff --git a/packages/styleguide/.storybook/components/Elements/DetailedCode/DetailedCodeBody/index.tsx b/packages/styleguide/.storybook/components/Elements/DetailedCode/DetailedCodeBody/index.tsx new file mode 100644 index 00000000000..72fee5ade8d --- /dev/null +++ b/packages/styleguide/.storybook/components/Elements/DetailedCode/DetailedCodeBody/index.tsx @@ -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 = ({ + code, + language, + showEllipses = false, + hiddenLineCount, +}) => { + return ( + + + {showEllipses && ( + + ... {hiddenLineCount} more line{hiddenLineCount === 1 ? '' : 's'} + + )} + + ); +}; diff --git a/packages/styleguide/.storybook/components/Elements/DetailedCode/DetailedCodeButton/index.tsx b/packages/styleguide/.storybook/components/Elements/DetailedCode/DetailedCodeButton/index.tsx new file mode 100644 index 00000000000..2e0a9a4b434 --- /dev/null +++ b/packages/styleguide/.storybook/components/Elements/DetailedCode/DetailedCodeButton/index.tsx @@ -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 = ({ + isExpanded, + onToggle, + language, +}) => { + return ( + + + {language} + + {isExpanded ? 'Show Less Code' : 'Show More Code'} + + + + + + + ); +}; diff --git a/packages/styleguide/.storybook/components/Elements/DetailedCode/elements.tsx b/packages/styleguide/.storybook/components/Elements/DetailedCode/elements.tsx new file mode 100644 index 00000000000..23ddd3dd842 --- /dev/null +++ b/packages/styleguide/.storybook/components/Elements/DetailedCode/elements.tsx @@ -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, + }, + }) +); + +export const FloatingIndicator = styled(Box)( + css({ + position: 'absolute', + bottom: 16, + left: 16, + px: 12, + fontSize: 14, + fontFamily: 'monospace', + textColor: '#C9CDCF', + }) +); diff --git a/packages/styleguide/.storybook/components/Elements/DetailedCode/index.tsx b/packages/styleguide/.storybook/components/Elements/DetailedCode/index.tsx new file mode 100644 index 00000000000..64abd4f0f64 --- /dev/null +++ b/packages/styleguide/.storybook/components/Elements/DetailedCode/index.tsx @@ -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 = ({ + 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 ( + + + {hasMoreCode && ( + setIsExpanded((prev) => !prev)} + language={language} + /> + )} + + ); +}; diff --git a/packages/styleguide/.storybook/components/Elements/DetailedCode/types.ts b/packages/styleguide/.storybook/components/Elements/DetailedCode/types.ts new file mode 100644 index 00000000000..e0d9e964bda --- /dev/null +++ b/packages/styleguide/.storybook/components/Elements/DetailedCode/types.ts @@ -0,0 +1,25 @@ +import { Source } from '@storybook/addon-docs/blocks'; +import { ComponentProps } from 'react'; + +type SourceLanguage = ComponentProps['language']; + +export interface DetailedCodeProps { + code: string; + language?: SourceLanguage; + initiallyExpanded?: boolean; + preview?: boolean; + previewLines?: number; +} + +export interface DetailedCodeButtonProps { + isExpanded: boolean; + onToggle: () => void; + language: SourceLanguage; +} + +export interface DetailedCodeBodyProps { + code: string; + language: SourceLanguage; + showEllipses?: boolean; + hiddenLineCount: number; +} diff --git a/packages/styleguide/.storybook/components/index.tsx b/packages/styleguide/.storybook/components/index.tsx index 86b39cc1bbb..4be3198b4ab 100644 --- a/packages/styleguide/.storybook/components/index.tsx +++ b/packages/styleguide/.storybook/components/index.tsx @@ -2,6 +2,7 @@ export * from './ImageWrapper'; export * from './TokenTable'; export * from './Headers'; export * from './Elements/Callout'; +export * from './Elements/DetailedCode'; export * from './Elements/KeyboardKey'; export * from './Elements/Markdown'; export * from './Elements/ImageGallery'; diff --git a/packages/styleguide/src/lib/Organisms/ConnectedForm/ConnectedForm/ConnectedForm.mdx b/packages/styleguide/src/lib/Organisms/ConnectedForm/ConnectedForm/ConnectedForm.mdx index a50662d9e0f..a89f9ed2ab2 100644 --- a/packages/styleguide/src/lib/Organisms/ConnectedForm/ConnectedForm/ConnectedForm.mdx +++ b/packages/styleguide/src/lib/Organisms/ConnectedForm/ConnectedForm/ConnectedForm.mdx @@ -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 ( - console.log(thisField)} - resetOnSubmit - {...connectedFormProps} - > - submit this form. - - - - - - ); -}; -``` + + +### Example Code Preview False + + ## Variants diff --git a/packages/styleguide/src/lib/Organisms/ConnectedForm/ConnectedForm/example.ts b/packages/styleguide/src/lib/Organisms/ConnectedForm/ConnectedForm/example.ts new file mode 100644 index 00000000000..ec23ed91564 --- /dev/null +++ b/packages/styleguide/src/lib/Organisms/ConnectedForm/ConnectedForm/example.ts @@ -0,0 +1,67 @@ +export const example = `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 ( + console.log(thisField)} + resetOnSubmit + {...connectedFormProps} + > + submit this form. + + + + + + ); +};`;