Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Source } from '@storybook/blocks';
import { ComponentProps } from 'react';
import * as React from 'react';

import { DetailedCodeBodyWrapper, FloatingExpandButton } from '../elements';
import { DetailedCodeBodyProps } from '../types';

type SourceLanguage = ComponentProps<typeof Source>['language'];

export const DetailedCodeBody: React.FC<DetailedCodeBodyProps> = ({
code,
language,
showFloatingBadge = false,
isExpanded = false,
setIsExpanded,
}) => {
const handleClick = () => {
if (setIsExpanded) {
setIsExpanded((prev) => !prev);
}
};

return (
<DetailedCodeBodyWrapper hasFloatingBadge={showFloatingBadge}>
<Source code={code} dark language={language as SourceLanguage} />
{showFloatingBadge && (
<FloatingExpandButton
onClick={handleClick}
size="normal"
>
{isExpanded ? 'Show less code' : 'Show more code'}
</FloatingExpandButton>
)}
</DetailedCodeBodyWrapper>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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,
setIsExpanded,
}) => {
const handleClick = () => {
if (setIsExpanded) {
setIsExpanded((prev: boolean) => !prev);
}
};

return (
<Anchor
aria-expanded={isExpanded}
height="100%"
px={16}
py={12}
variant="interface"
width="100%"
onClick={handleClick}
>
<FlexBox
columnGap={16}
flexDirection="row"
justifyContent="space-between"
width="100%"
>
<Text>{isExpanded ? 'Show Less Code' : 'Show More Code'}</Text>
<Rotation height={16} rotated={isExpanded} width={16}>
<MiniChevronDownIcon aria-hidden size={16} />
</Rotation>
</FlexBox>
</Anchor>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { css } from '@codecademy/gamut-styles';
import styled from '@emotion/styled';
import { FlexBox, TextButton } 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;
}>(({ 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,
},
})
);

export const FloatingExpandButton = styled(TextButton)(
css({
position: 'absolute',
bottom: 16,
left: '50%',
transform: 'translateX(-50%)',
zIndex: 1,
bg: 'white',
})
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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<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 previewCode = previewEnabled
? getPreviewCode(code, normalizedPreviewLines)
: code;

const hasMoreCode =
previewEnabled && code.split('\n').length > normalizedPreviewLines;

const displayedCode = isExpanded ? code : previewCode;

return (
<DetailedCodeWrapper>
<DetailedCodeBody
code={displayedCode}
language={language}
showFloatingBadge={hasMoreCode}
isExpanded={isExpanded}
setIsExpanded={setIsExpanded}
/>
{/* {hasMoreCode && (
<DetailedCodeButton
isExpanded={isExpanded}
setIsExpanded={setIsExpanded}
/>
)} */}
</DetailedCodeWrapper>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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;
}

export interface DetailedCodeButtonProps {
isExpanded?: boolean;
setIsExpanded?: React.Dispatch<React.SetStateAction<boolean>>;
}

export interface DetailedCodeBodyProps {
code: string;
language: string;
showFloatingBadge?: boolean;
isExpanded?: boolean;
setIsExpanded?: React.Dispatch<React.SetStateAction<boolean>>;
}
1 change: 1 addition & 0 deletions packages/styleguide/.storybook/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Canvas, Controls, Meta } from '@storybook/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',
Expand Down Expand Up @@ -40,75 +41,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 (
<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} preview />

## Variants

Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<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>
);
};`;
Loading