diff --git a/packages/gamut/__tests__/__snapshots__/gamut.test.ts.snap b/packages/gamut/__tests__/__snapshots__/gamut.test.ts.snap index 8511c5708f..44fc7b1987 100644 --- a/packages/gamut/__tests__/__snapshots__/gamut.test.ts.snap +++ b/packages/gamut/__tests__/__snapshots__/gamut.test.ts.snap @@ -33,6 +33,7 @@ exports[`Gamut Exported Keys 1`] = ` "DataList", "DataTable", "DelayedRenderWrapper", + "DetailedCode", "Dialog", "Disclosure", "Drawer", diff --git a/packages/gamut/src/DetailedCode/DetailedCodeBody/index.tsx b/packages/gamut/src/DetailedCode/DetailedCodeBody/index.tsx new file mode 100644 index 0000000000..35f9492d23 --- /dev/null +++ b/packages/gamut/src/DetailedCode/DetailedCodeBody/index.tsx @@ -0,0 +1,19 @@ +import { Source } from '@storybook/blocks'; +import { ComponentProps } from 'react'; +import * as React from 'react'; + +import { DetailedCodeBodyWrapper } from '../elements'; +import { DetailedCodeBodyProps } from '../types'; + +type SourceLanguage = ComponentProps['language']; + +export const DetailedCodeBody: React.FC = ({ + code, + language, +}) => { + return ( + + + + ); +}; diff --git a/packages/gamut/src/DetailedCode/DetailedCodeButton/index.tsx b/packages/gamut/src/DetailedCode/DetailedCodeButton/index.tsx new file mode 100644 index 0000000000..54042c1ec2 --- /dev/null +++ b/packages/gamut/src/DetailedCode/DetailedCodeButton/index.tsx @@ -0,0 +1,43 @@ +import { MiniChevronDownIcon } from '@codecademy/gamut-icons'; +import * as React from 'react'; + +import { Anchor } from '../../Anchor'; +import { Rotation } from '../../Animation'; +import { FlexBox } from '../../Box'; +import { Text } from '../../Typography'; +import { DetailedCodeButtonProps } from '../types'; + +export const DetailedCodeButton: React.FC = ({ + isExpanded, + setIsExpanded, +}) => { + const handleClick = () => { + if (setIsExpanded) { + setIsExpanded((prev: boolean) => !prev); + } + }; + + return ( + + + {isExpanded ? 'Show Less Code' : 'Show More Code'} + + + + + + ); +}; diff --git a/packages/gamut/src/DetailedCode/elements.tsx b/packages/gamut/src/DetailedCode/elements.tsx new file mode 100644 index 0000000000..8297bf95e1 --- /dev/null +++ b/packages/gamut/src/DetailedCode/elements.tsx @@ -0,0 +1,25 @@ +import { css } from '@codecademy/gamut-styles'; +import styled from '@emotion/styled'; + +import { FlexBox } from '../Box'; + +export const DetailedCodeWrapper = styled(FlexBox)( + css({ + width: '100%', + flexDirection: 'column', + borderRadius: 'md', + border: 1, + bg: 'background', + }) +); + +export const DetailedCodeBodyWrapper = styled(FlexBox)( + css({ + flexDirection: 'column', + /* Override Storybook's Source component default styles to remove unwanted spacing and borders in the container */ + '& .docblock-source': { + borderRadius: 'none', + margin: 0, + }, + }) +); diff --git a/packages/gamut/src/DetailedCode/index.tsx b/packages/gamut/src/DetailedCode/index.tsx new file mode 100644 index 0000000000..3696643c8f --- /dev/null +++ b/packages/gamut/src/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 = 10; +const DEFAULT_LANGUAGE = 'tsx'; + +const getPreviewCode = (code: string, previewLines: number) => { + const lines = code.split('\n'); + + if (lines.length <= previewLines) { + return code; + } + + return lines.slice(0, previewLines).join('\n'); +}; + +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 previewCode = previewEnabled + ? getPreviewCode(code, normalizedPreviewLines) + : code; + + const hasMoreCode = + previewEnabled && code.split('\n').length > normalizedPreviewLines; + + const displayedCode = isExpanded ? code : previewCode; + + return ( + + + {hasMoreCode && ( + + )} + + ); +}; diff --git a/packages/gamut/src/DetailedCode/types.ts b/packages/gamut/src/DetailedCode/types.ts new file mode 100644 index 0000000000..1af22b7bab --- /dev/null +++ b/packages/gamut/src/DetailedCode/types.ts @@ -0,0 +1,17 @@ +export interface DetailedCodeProps { + code: string; + language: string; + initiallyExpanded?: boolean; + preview?: boolean; + previewLines?: number; +} + +export interface DetailedCodeButtonProps { + isExpanded?: boolean; + setIsExpanded?: React.Dispatch>; +} + +export interface DetailedCodeBodyProps { + code: string; + language: string; +} diff --git a/packages/gamut/src/index.tsx b/packages/gamut/src/index.tsx index 0537494d5d..13d7c10c13 100644 --- a/packages/gamut/src/index.tsx +++ b/packages/gamut/src/index.tsx @@ -16,6 +16,7 @@ export * from './Coachmark'; export * from './ConnectedForm'; export * from './ContentContainer'; export * from './DelayedRenderWrapper'; +export * from './DetailedCode'; export * from './Disclosure'; export * from './DataList'; export * from './Drawer'; diff --git a/packages/styleguide/src/lib/Organisms/ConnectedForm/ConnectedForm/ConnectedForm.mdx b/packages/styleguide/src/lib/Organisms/ConnectedForm/ConnectedForm/ConnectedForm.mdx index b71619aed5..ba5869182d 100644 --- a/packages/styleguide/src/lib/Organisms/ConnectedForm/ConnectedForm/ConnectedForm.mdx +++ b/packages/styleguide/src/lib/Organisms/ConnectedForm/ConnectedForm/ConnectedForm.mdx @@ -1,8 +1,10 @@ +import { DetailedCode } from '@codecademy/gamut'; import { Canvas, Controls, Meta } from '@storybook/blocks'; import { ComponentHeader, LinkTo } from '~styleguide/blocks'; import * as ConnectedFormStories from './ConnectedForm.stories'; +import { example } from './example'; export const parameters = { title: 'ConnectedForm', @@ -40,75 +42,7 @@ 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. - - - - - - ); -}; -``` + ## 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 0000000000..ec23ed9156 --- /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. + + + + + + ); +};`;