Skip to content
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

feat #299 - Code & Tabs components enhancements #301

Merged
merged 6 commits into from
Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
560 changes: 299 additions & 261 deletions src/__snapshots__/storybook.test.ts.snap

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions src/components/Code/CodeControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import cn from 'classnames'
import { createUseStyles } from 'react-jss'
import { faCopy } from '@fortawesome/pro-regular-svg-icons'
import { generateThemedControlsStyles } from './utils'
import { IconButton } from 'components/IconButton'
import { Tooltip } from 'components/Tooltip'
import React, { FC } from 'react'
import { styleguide, ThemeType } from 'components/assets/styles'

const { borderRadius, spacing } = styleguide

const { light, dark } = ThemeType

const useStyles = createUseStyles({
codeControls: {
...generateThemedControlsStyles(light),
borderRadius,
padding: `${spacing.xs}px ${spacing.s}px`,
position: 'absolute',
right: spacing['s+'],
top: spacing['s+'],
zIndex: 1
},
iconButton: {
padding: {
left: spacing.s,
right: spacing.s
}
},
// eslint-disable-next-line sort-keys
'@global': {
[`.${dark}`]: {
'& $codeControls': generateThemedControlsStyles(dark)
}
}
})

// This is left as an object so more controls (e.g. download) can be added later
export interface DisplayCodeControls {
copy?: boolean
}

interface Props {
classes?: string[]
displayControls?: DisplayCodeControls
isCopied?: boolean
onClickCopyCode: () => void
}

export const CodeControls: FC<Props> = ({
classes = [],
displayControls = {},
isCopied = false,
onClickCopyCode
}: Props) => {
const compClasses = useStyles()

const { copy = true } = displayControls

return (
<div className={cn(compClasses.codeControls, classes)}>
{copy && (
<Tooltip placement='top' title={isCopied ? 'Copied!' : 'Copy'}>
<IconButton
classes={[compClasses.iconButton]}
icon={faCopy}
onClick={onClickCopyCode}
size={14}
/>
</Tooltip>
)}
</div>
)
}
49 changes: 36 additions & 13 deletions src/components/Code/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ import './prism.css'
import cn from 'classnames'
import Mark from 'mark.js'
import Prism from 'prismjs'
import { useStyles } from './utils'
import { CodeControls, DisplayCodeControls } from './CodeControls'
import { CodeType, copyToClipboard, stringifyCode, useStyles } from './utils'
import { Input, InputProps } from 'components/Input'
import React, { ChangeEvent, FC, useEffect, useRef } from 'react'
import React, { ChangeEvent, FC, useEffect, useRef, useState } from 'react'

require('prismjs/plugins/line-numbers/prism-line-numbers')
require('prismjs/components/prism-json')

export interface CodeProps {
classes?: string[]
code: string | Record<string, any>
code: CodeType
displayControls?: DisplayCodeControls | false
language?: 'css' | 'html' | 'javascript' | 'json'
lineNumbers?: boolean
readOnly?: boolean
Expand All @@ -22,6 +25,7 @@ export interface CodeProps {
export const Code: FC<CodeProps> = ({
classes = [],
code,
displayControls = {},
language = 'json',
lineNumbers = true,
readOnly = true,
Expand All @@ -30,6 +34,16 @@ export const Code: FC<CodeProps> = ({
const compClasses = useStyles()
const codeRef = useRef<HTMLElement>(null)

const [isCopied, setIsCopied] = useState(false)

const copyCode = () => {
copyToClipboard(stringifyCode(code), () => setIsCopied(true))
}

useEffect(() => {
if (isCopied) setTimeout(() => setIsCopied(false), 1250)
}, [isCopied])

useEffect(() => {
/**
* We want to highlight the code after it is rendered onto the page,
Expand Down Expand Up @@ -71,16 +85,25 @@ export const Code: FC<CodeProps> = ({
return (
<div className={cn(classes)}>
{search && <Input {...searchProps} onChange={onSearch} />}
<pre
className={cn({ 'line-numbers': lineNumbers })}
contentEditable={!readOnly}
>
<code className={`language-${language}`} ref={codeRef}>
{typeof code === 'string'
? code
: JSON.stringify(code, null, '\t')}
</code>
</pre>
<div className={compClasses.wrapper}>
{displayControls && (
<CodeControls
classes={[compClasses.controls]}
isCopied={isCopied}
onClickCopyCode={copyCode}
/>
)}
<pre
className={cn({
'line-numbers': lineNumbers
})}
contentEditable={!readOnly}
>
<code className={`language-${language}`} ref={codeRef}>
{stringifyCode(code)}
</code>
</pre>
</div>
</div>
)
}
115 changes: 93 additions & 22 deletions src/components/Code/utils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import { createUseStyles } from 'react-jss'
import { styleguide, themedStyles, ThemeType } from 'components/assets/styles'
import { isObject } from 'lodash'
import { ColorManipulationTypes, manipulateColor } from 'components/utils'
import {
styleguide,
themedStyles,
themes,
ThemeType
} from 'components/assets/styles'

const { dark, light } = ThemeType

const {
colors: { blacks, grays },
colors: { blacks, grays, greens, reds, oranges },
font,
fontWeight,
spacing
} = styleguide

interface CopyToClipboard {
(str: string, callback?: () => void): void
}
export const copyToClipboard: CopyToClipboard = (str, callback) =>
navigator.clipboard.writeText(str).then(callback)
sam-dassana marked this conversation as resolved.
Show resolved Hide resolved

const codePalette = {
[dark]: {
background: blacks['darken-20']
Expand All @@ -19,6 +32,13 @@ const codePalette = {
}
}

export type CodeType = string | Record<string, any>

export const stringifyCode = (code: CodeType): string =>
isObject(code) ? JSON.stringify(code, null, '\t') : code

/* -x-x-x-x-x-x-x-x- Styles Related -x-x-x-x-x-x-x-x- */

const generateThemedCodeStyles = (themeType: ThemeType) => {
const { background } = codePalette[themeType]

Expand All @@ -32,34 +52,74 @@ const generateThemedCodeStyles = (themeType: ThemeType) => {
}
}

export const generateThemedControlsStyles = (themeType: ThemeType) => {
const {
base: { borderColor }
} = themedStyles[themeType]

return {
background: themes[themeType].background.secondary,
border: `1px solid ${borderColor}`
}
}

/* -------------- Prism Colors -------------- */

const darkCommonColor = themedStyles[dark].base.color
const lightCommonColor = themedStyles[light].base.color

const { shade } = ColorManipulationTypes

const prismColors = {
[dark]: {
boolean: reds.base,
char: darkCommonColor,
className: darkCommonColor,
comment: darkCommonColor,
function: darkCommonColor,
keyword: darkCommonColor,
lineHighlight: darkCommonColor,
method: darkCommonColor,
number: reds.base,
operator: darkCommonColor,
primitive: darkCommonColor,
property: oranges.base,
punctuation: darkCommonColor,
string: manipulateColor(greens.base, 20, shade),
tag: darkCommonColor,
variable: darkCommonColor
},
[light]: {
boolean: oranges.base,
char: lightCommonColor,
className: lightCommonColor,
comment: lightCommonColor,
function: lightCommonColor,
keyword: lightCommonColor,
lineHighlight: lightCommonColor,
method: lightCommonColor,
number: oranges.base,
operator: lightCommonColor,
primitive: lightCommonColor,
property: reds.base,
punctuation: lightCommonColor,
string: manipulateColor(greens.base, 10, shade),
tag: lightCommonColor,
variable: lightCommonColor
}
}

/* ------------------------------------------ */

const generateThemedPreCodeStyles = (themeType: ThemeType) => {
const { background } = codePalette[themeType]

const {
base: { color }
} = themedStyles[themeType]

const prismColors = {
boolean: color,
char: color,
className: color,
comment: color,
function: color,
keyword: color,
lineHighlight: color,
method: color,
number: color,
operator: color,
primitive: color,
property: color,
punctuation: color,
string: color,
tag: color,
variable: color
}

return {
...Object.entries(prismColors).reduce(
...Object.entries(prismColors[themeType]).reduce(
(acc, [key, val]) => ({
...acc,
[`& .token.${key}`]: {
Expand Down Expand Up @@ -96,6 +156,8 @@ const preCodeSelector = "pre[class*='language-']"
/* eslint-enable quotes */

export const useStyles = createUseStyles({
controls: { opacity: 0 },
// eslint-disable-next-line sort-keys
'@global': {
[preCodeSelector]: {
'& .line-numbers-rows': {
Expand Down Expand Up @@ -125,5 +187,14 @@ export const useStyles = createUseStyles({
},
search: {
marginBottom: spacing.m
},
wrapper: {
'&:hover': {
'& $controls': { opacity: 1 }
},
height: '100%',
overflow: 'auto',
position: 'relative',
width: '100%'
}
})
45 changes: 45 additions & 0 deletions src/components/Tabs/TabPane.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import cn from 'classnames'
import { createUseStyles } from 'react-jss'
import { motion } from 'framer-motion'
import { styleguide } from 'components/assets/styles'
import { TabConfig } from '.'
import React, { FC } from 'react'

const { font, spacing } = styleguide

const useStyles = createUseStyles({
tabPane: {
...font.body,
display: ({ isActive }) => (isActive ? 'block' : 'none'),
padding: `${spacing.m}px ${spacing.l}px`
}
})

interface TabPaneProps {
isActive: boolean
tabConfigItem: TabConfig
}

const [ACTIVE, INACTIVE] = ['active', 'inactive']

const TabPane: FC<TabPaneProps> = ({
isActive,
tabConfigItem: { classes = [], render }
}: TabPaneProps) => {
const compClasses = useStyles({ isActive })

return (
<motion.section
animate={isActive ? ACTIVE : INACTIVE}
transition={{ duration: 0.5 }}
variants={{
[ACTIVE]: { opacity: 1 },
[INACTIVE]: { opacity: 0 }
}}
>
<div className={cn(compClasses.tabPane, classes)}>{render()}</div>
</motion.section>
)
}

export default TabPane
3 changes: 3 additions & 0 deletions src/components/Tabs/Tabs.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export default {
tabConfig: { control: { disable: true } }
},
component: Tabs,
parameters: {
storyshots: { disable: true }
},
title: 'Tabs'
} as Meta

Expand Down
Loading