Skip to content

Commit

Permalink
feat(core): add code block
Browse files Browse the repository at this point in the history
  • Loading branch information
romelperez committed Feb 16, 2021
1 parent 11d3ee9 commit d863b7b
Show file tree
Hide file tree
Showing 11 changed files with 415 additions and 3 deletions.
1 change: 0 additions & 1 deletion packages/core/package.json
Expand Up @@ -32,7 +32,6 @@
"peerDependencies": {
"@emotion/css": "^11.1.3",
"@emotion/react": "^11.1.4",
"polished": "^4.1.0",
"prop-types": "*",
"react": "17.x"
},
Expand Down
81 changes: 81 additions & 0 deletions packages/core/src/CodeBlock/CodeBlock.animator.ts
@@ -0,0 +1,81 @@
import rgba from 'polished/lib/color/rgba';
import anime from 'animejs';
import { MutableRefObject } from 'react';
import { AnimatorClassSettings, AnimatorRef } from '@arwes/animation';
import { Bleeps } from '@arwes/sounds';

import { ArwesTheme } from '../ArwesThemeProvider';

type RootRef = MutableRefObject<HTMLElement>;

const playBleeps = (bleeps: Bleeps): void => {
bleeps.readout?.play();
};

const stopBleeps = (bleeps: Bleeps): void => {
if (bleeps.readout?.getIsPlaying()) {
bleeps.readout?.stop();
}
};

const stopCodeBlockAnimation = (animator: AnimatorRef, ref: RootRef): void => {
if (ref.current) {
const root = ref.current;
const lines = root.querySelectorAll('.arwes-code-block__line');

anime.remove(root);
anime.remove(lines);
}
};

const startCodeBlockAnimation = (animator: AnimatorRef, ref: RootRef, theme: ArwesTheme): void => {
stopCodeBlockAnimation(animator, ref);

const { duration, flow } = animator;
const isEntering = flow.entering || flow.entered;
const { palette } = theme;

const root = ref.current;
const lines = root.querySelectorAll('.arwes-code-block__line');

anime({
targets: root,
duration: isEntering ? duration.enter : duration.exit,
easing: isEntering ? 'easeOutSine' : 'easeInSine',
backgroundColor: isEntering ? rgba(palette.primary.light2, 0.05) : 'rgba(0,0,0,0)'
});

anime({
targets: lines,
duration: isEntering ? duration.enter : duration.exit,
easing: isEntering ? 'easeOutSine' : 'easeInSine',
width: isEntering ? [0, '100%'] : ['100%', 0]
});
};

const useAnimateEntering = (animator: AnimatorRef, ref: RootRef, theme: ArwesTheme, bleeps: Bleeps): void => {
startCodeBlockAnimation(animator, ref, theme);
playBleeps(bleeps);
};

const useAnimateEntered = (animator: AnimatorRef, ref: RootRef, theme: ArwesTheme, bleeps: Bleeps): void => {
stopBleeps(bleeps);
};

const useAnimateExiting = (animator: AnimatorRef, ref: RootRef, theme: ArwesTheme): void => {
startCodeBlockAnimation(animator, ref, theme);
};

const useAnimateUnmount = (animator: AnimatorRef, ref: RootRef, theme: ArwesTheme, bleeps: Bleeps): void => {
stopCodeBlockAnimation(animator, ref);
stopBleeps(bleeps);
};

const animator: AnimatorClassSettings = {
useAnimateEntering,
useAnimateEntered,
useAnimateExiting,
useAnimateUnmount
};

export { animator };
10 changes: 10 additions & 0 deletions packages/core/src/CodeBlock/CodeBlock.bleeps.ts
@@ -0,0 +1,10 @@
import { BleepsSettings } from '@arwes/sounds';

const bleepsSettings: BleepsSettings = {
readout: {
player: 'readout',
category: 'transition'
}
};

export { bleepsSettings };
112 changes: 112 additions & 0 deletions packages/core/src/CodeBlock/CodeBlock.component.tsx
@@ -0,0 +1,112 @@
// TODO: Transitioning scroll changes the size of the container.
// TODO: Setup proper bleep.

/* @jsx jsx */
import { FC, MutableRefObject, useRef, useMemo, useCallback } from 'react';
import PropTypes from 'prop-types';
import { jsx, useTheme } from '@emotion/react';
import { WithAnimatorInputProps } from '@arwes/animation';
import { WithBleepsInputProps } from '@arwes/sounds';

import { Text, TextProps } from '../Text';
import { generateStyles } from './CodeBlock.styles';

interface CodeBlockProps {
lang?: string
contentTextProps?: TextProps
rootRef?: MutableRefObject<HTMLElement> | Function
}

const CodeBlock: FC<CodeBlockProps & WithAnimatorInputProps & WithBleepsInputProps> = props => {
const {
animator,
bleeps,
lang,
contentTextProps,
children,
rootRef: externalRootRef
} = props;
const { animate } = animator;

const theme = useTheme();
const styles = useMemo(
() => generateStyles(theme, { animate }),
[theme, animator.animate]
);

// TODO: Modularize the way to join multiple references into one.
const internalRootRef = useRef<HTMLDivElement | null>(null);
const rootRef = useCallback(node => {
internalRootRef.current = node;

if (typeof externalRootRef === 'function') {
externalRootRef(node);
}
else if (externalRootRef) {
externalRootRef.current = node;
}
}, []);

animator.setupAnimateRefs(internalRootRef, theme, bleeps);

return (
<div
className='arwes-code-block'
css={styles.root}
ref={rootRef}
>
<div
className='arwes-code-block__line arwes-code-block__line-top'
css={[styles.line, styles.lineTop]}
/>
{!!lang && (
<div
className='arwes-code-block__lang'
css={styles.lang}
>
<Text blink={false}>
{lang}
</Text>
<div
className='arwes-code-block__line arwes-code-block__line-lang'
css={[styles.line, styles.lineLang]}
/>
</div>
)}
<div
className='arwes-code-block__container'
css={[
styles.container,
!animator.flow.entered && styles.containerIsTransitioning
]}
>
<Text
{...contentTextProps}
className='arwes-code-block__content'
css={styles.content}
>
{children}
</Text>
</div>
<div
className='arwes-code-block__line arwes-code-block__line-bottom'
css={[styles.line, styles.lineBottom]}
/>
</div>
);
};

CodeBlock.propTypes = {
lang: PropTypes.string,
contentTextProps: PropTypes.object,
rootRef: PropTypes.any
};

CodeBlock.defaultProps = {
contentTextProps: {
as: 'pre',
blink: false
}
};

export { CodeBlockProps, CodeBlock };
68 changes: 68 additions & 0 deletions packages/core/src/CodeBlock/CodeBlock.styles.ts
@@ -0,0 +1,68 @@
import rgba from 'polished/lib/color/rgba';
import { Interpolation } from '@emotion/react';

import { ArwesTheme } from '../ArwesThemeProvider';

const generateStyles = (
theme: ArwesTheme,
options: { animate: boolean }
): Record<string, Interpolation<ArwesTheme>> => {
const { palette, space, outline, shadow } = theme;
const { animate } = options;

return {
root: {
position: 'relative',
display: 'flex',
margin: `0 0 ${space(4)}px`,
backgroundColor: animate ? undefined : rgba(palette.primary.light2, 0.05)
},
lang: {
zIndex: 1,
position: 'absolute',
right: 0,
top: outline(1),
padding: `${space(0.5)}px ${space(1.5)}px`,
color: palette.secondary.main,
textShadow: `0 0 ${shadow.blur(1)}px ${palette.secondary.main}`,
textTransform: 'uppercase',
backgroundColor: animate ? undefined : palette.primary.dark3
},
container: {
flex: 1,
overflow: 'auto',
padding: space(4)
},
containerIsTransitioning: {
overflow: 'hidden'
},
content: {
display: 'block',
margin: 0,
border: 'none',
padding: 0,
backgroundColor: 'transparent'
},
line: {
position: 'absolute',
left: 0,
width: animate ? 0 : '100%',
height: outline(1),
backgroundColor: palette.primary.dark1,
boxShadow: `0 0 ${outline(1)}px ${palette.primary.dark1}`
},
lineTop: {
top: 0
},
lineBottom: {
bottom: 0
},
lineLang: {
bottom: 0,
backgroundColor: palette.secondary.dark1,
boxShadow: `0 0 ${outline(1)}px ${palette.secondary.dark1}`
}
};
};

export { generateStyles };
69 changes: 69 additions & 0 deletions packages/core/src/CodeBlock/basic.sandbox.md
@@ -0,0 +1,69 @@
```jsx
const SOUND_TYPING_URL = '/assets/sounds/typing.mp3';
const SOUND_READOUT_URL = '/assets/sounds/readout.mp3';

const rootFontFamily = '"Titillium Web", sans-serif';
const codeFontFamily = '"Source Code Pro", monospace';
const players = {
typing: { src: [SOUND_TYPING_URL], loop: true },
readout: { src: [SOUND_READOUT_URL], loop: true }
};

const Sandbox = () => {
const [activate, setActivate] = React.useState(true);

React.useEffect(() => {
const timeout = setTimeout(() => setActivate(!activate), 2000);
return () => clearTimeout(timeout);
}, [activate]);

return (
<ArwesThemeProvider>
<BleepsProvider players={players}>
<StylesBaseline styles={{
'html, body': { fontFamily: rootFontFamily },
'code, pre': { fontFamily: codeFontFamily }
}} />
<AnimatorGeneralProvider
animator={{ duration: { enter: 300, exit: 300 } }}
>
<CodeBlock animator={{ activate }} lang='tsx'>
{`const startCodeBlockAnimation = (
animator: AnimatorRef,
ref: RootRef,
theme: ArwesTheme
): void => {
stopCodeBlockAnimation(animator, ref);
const { duration, flow } = animator;
const isEntering = flow.entering || flow.entered;
const { palette } = theme;
const root = ref.current;
const lines = root.querySelectorAll('.arwes-code-block__line');
anime({
targets: root,
duration: isEntering ? duration.enter : duration.exit,
easing: isEntering ? 'easeOutSine' : 'easeInSine',
backgroundColor: isEntering
? rgba(palette.primary.light2, 0.05)
: 'rgba(0,0,0,0)'
});
anime({
targets: lines,
duration: isEntering ? duration.enter : duration.exit,
easing: isEntering ? 'easeOutSine' : 'easeInSine',
width: isEntering ? [0, '100%'] : ['100%', 0]
});
};`}
</CodeBlock>
</AnimatorGeneralProvider>
</BleepsProvider>
</ArwesThemeProvider>
);
};

render(<Sandbox />);
```
14 changes: 14 additions & 0 deletions packages/core/src/CodeBlock/index.ts
@@ -0,0 +1,14 @@
import { FC } from 'react';
import { WithAnimatorOutputProps, withAnimator } from '@arwes/animation';
import { WithBleepsOutputProps, withBleeps } from '@arwes/sounds';

import { CodeBlockProps, CodeBlock as Component } from './CodeBlock.component';
import { animator } from './CodeBlock.animator';
import { bleepsSettings } from './CodeBlock.bleeps';

const CodeBlock: FC<CodeBlockProps & WithAnimatorOutputProps & WithBleepsOutputProps> =
withAnimator(animator)(
withBleeps(bleepsSettings)(Component as any) as any
) as any;

export { CodeBlockProps, CodeBlock };

0 comments on commit d863b7b

Please sign in to comment.