diff --git a/src/__snapshots__/storybook.test.ts.snap b/src/__snapshots__/storybook.test.ts.snap index 40f50642..d9fcd830 100644 --- a/src/__snapshots__/storybook.test.ts.snap +++ b/src/__snapshots__/storybook.test.ts.snap @@ -987,7 +987,7 @@ exports[`Storyshots Code Default 1`] = ` > -
-      
-        {
+        
+          
+            
+          
+        
+      
+      
+        
+          {
 	"dassana": {
 		"context": {
 			"attachedResources": {
@@ -1041,8 +1077,9 @@ exports[`Storyshots Code Default 1`] = `
 		}
 	}
 }
-      
-    
+
+
+ `; @@ -1056,7 +1093,7 @@ exports[`Storyshots Code HTML 1`] = ` > -
-      
-        <div>
+        
+          
+            
+          
+        
+      
+      
+        
+          <div>
   <p>Lorem Ipsum</p>
   <span>dolor sit amet</span>
 </div>
-      
-    
+
+
+ `; @@ -1089,13 +1163,13 @@ exports[`Storyshots ColoredDot Colored 1`] = ` className="light storyWrapper-0-2-2" >

Hover over colored dot to show tooltip.

@@ -1108,11 +1182,11 @@ exports[`Storyshots Filters Client Side 1`] = ` className="light storyWrapper-0-2-2" >
@@ -1159,14 +1233,14 @@ exports[`Storyshots Filters Default 1`] = ` className="light storyWrapper-0-2-2" >
  @@ -1208,12 +1282,12 @@ exports[`Storyshots IconButton Circle 1`] = ` className="light storyWrapper-0-2-2" >
  @@ -1438,7 +1512,7 @@ exports[`Storyshots Input Placeholder 1`] = ` >

Click on the bordered box to use keyboard shortcuts

Press Up, Down, Left, Right arrow keys to navigate. Press enter or alphabet keys to select/deselect.

A
CISO
B
Sr Leadership
C
SecOps
D
Cloud Architect
E
DevOps
F
NetSec
G
AppDev
H
Compliance
I
Other @@ -1777,24 +1851,24 @@ exports[`Storyshots MultipleChoice Single 1`] = ` className="light storyWrapper-0-2-2" >

Click on the bordered box to use keyboard shortcuts

Press Up, Down, Left, Right arrow keys to navigate. Press enter or alphabet keys to select/deselect.

A
CISO
B
Sr Leadership
C
SecOps
D
Cloud Architect
E
DevOps
F
NetSec
G
AppDev
H
Compliance
I
Other @@ -2013,24 +2087,24 @@ exports[`Storyshots MultipleChoice Single Column 1`] = ` className="light storyWrapper-0-2-2" >

Click on the bordered box to use keyboard shortcuts

Press Up, Down, Left, Right arrow keys to navigate. Press enter or alphabet keys to select/deselect.

A
SecOps
B
Cloud Architect
C
DevOps
D
NetSec
E
AppDev
F
Compliance
G
Other @@ -2198,7 +2272,7 @@ exports[`Storyshots Notification Error 1`] = `
`; @@ -2220,7 +2294,7 @@ exports[`Storyshots Notification Info 1`] = `
`; @@ -2242,7 +2316,7 @@ exports[`Storyshots Notification Success 1`] = `
`; @@ -2264,7 +2338,7 @@ exports[`Storyshots Notification Warning 1`] = `
`; @@ -2350,7 +2424,7 @@ exports[`Storyshots PageLoader Default 1`] = ` className="light storyWrapper-0-2-2" >
loading_lightbulb.svg @@ -2524,31 +2598,31 @@ exports[`Storyshots Radio Group Loading 1`] = ` className="light storyWrapper-0-2-2" >
 
 
  @@ -2676,7 +2750,7 @@ exports[`Storyshots Select Default 1`] = ` className="decorator-0-2-3" >
Lorem @@ -2886,7 +2960,7 @@ exports[`Storyshots Select Icon 1`] = ` className="decorator-0-2-3" >
AWS @@ -3008,7 +3082,7 @@ exports[`Storyshots Select Search 1`] = ` className="decorator-0-2-3" >
press enter @@ -3236,19 +3310,19 @@ exports[`Storyshots ShortcutMicrocopy Icon Only 1`] = ` className="light storyWrapper-0-2-2" >
press @@ -3262,24 +3336,24 @@ exports[`Storyshots ShortcutMicrocopy Mixed 1`] = ` className="light storyWrapper-0-2-2" >
press cmd @@ -3287,12 +3361,12 @@ exports[`Storyshots ShortcutMicrocopy Mixed 1`] = ` + Enter
press cmd @@ -3349,12 +3423,12 @@ exports[`Storyshots ShortcutMicrocopy Predefined Keys 1`] = ` + enter @@ -3368,7 +3442,7 @@ exports[`Storyshots Skeleton Circle 1`] = ` className="light storyWrapper-0-2-2" >   @@ -3380,27 +3454,27 @@ exports[`Storyshots Skeleton Count 1`] = ` className="light storyWrapper-0-2-2" >           @@ -3412,7 +3486,7 @@ exports[`Storyshots Skeleton Default 1`] = ` className="light storyWrapper-0-2-2" >   @@ -3424,20 +3498,20 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = ` className="light storyWrapper-0-2-2" >
vpc-flow-logs-are-disabled @@ -3776,10 +3850,10 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = ` style={Object {}} >
FlowLog @@ -3793,10 +3867,10 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = ` style={Object {}} >
visibility @@ -3805,7 +3879,7 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = `
instance-is-exposed-to-internet @@ -3849,10 +3923,10 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = ` style={Object {}} >
instance @@ -3866,10 +3940,10 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = ` style={Object {}} >
network @@ -3878,7 +3952,7 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = `
default-security-group-should-not-allow-traffic @@ -3922,10 +3996,10 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = ` style={Object {}} >
security-group @@ -3939,10 +4013,10 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = ` style={Object {}} >
networking @@ -3951,7 +4025,7 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = `
ssh-from-internet @@ -3995,10 +4069,10 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = ` style={Object {}} >
security-group @@ -4012,10 +4086,10 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = ` style={Object {}} >
network @@ -4024,7 +4098,7 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = `
ebs-volume-is-not-encrypted @@ -4068,10 +4142,10 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = ` style={Object {}} >
volume @@ -4085,10 +4159,10 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = ` style={Object {}} >
cryptography @@ -4212,42 +4286,6 @@ exports[`Storyshots TableDrawer Simple Drawer 1`] = `
`; -exports[`Storyshots Tabs Default 1`] = ` -
-
-
-
  • - Lorem -
  • -
  • - Ipsum -
  • -
    -
    -
    - Banana mochi muffin -
    -
    -
    -
    -`; - exports[`Storyshots Tag Colored 1`] = `
    void +} + +export const CodeControls: FC = ({ + classes = [], + displayControls = {}, + isCopied = false, + onClickCopyCode +}: Props) => { + const compClasses = useStyles() + + const { copy = true } = displayControls + + return ( +
    + {copy && ( + + + + )} +
    + ) +} diff --git a/src/components/Code/index.tsx b/src/components/Code/index.tsx index b08fbac5..8e793140 100644 --- a/src/components/Code/index.tsx +++ b/src/components/Code/index.tsx @@ -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 + code: CodeType + displayControls?: DisplayCodeControls | false language?: 'css' | 'html' | 'javascript' | 'json' lineNumbers?: boolean readOnly?: boolean @@ -22,6 +25,7 @@ export interface CodeProps { export const Code: FC = ({ classes = [], code, + displayControls = {}, language = 'json', lineNumbers = true, readOnly = true, @@ -30,6 +34,16 @@ export const Code: FC = ({ const compClasses = useStyles() const codeRef = useRef(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, @@ -71,16 +85,25 @@ export const Code: FC = ({ return (
    {search && } -
    -				
    -					{typeof code === 'string'
    -						? code
    -						: JSON.stringify(code, null, '\t')}
    -				
    -			
    +
    + {displayControls && ( + + )} +
    +					
    +						{stringifyCode(code)}
    +					
    +				
    +
    ) } diff --git a/src/components/Code/utils.ts b/src/components/Code/utils.ts index 4aeab3a8..45dba7d0 100644 --- a/src/components/Code/utils.ts +++ b/src/components/Code/utils.ts @@ -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) + const codePalette = { [dark]: { background: blacks['darken-20'] @@ -19,6 +32,13 @@ const codePalette = { } } +export type CodeType = string | Record + +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] @@ -32,6 +52,65 @@ 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] @@ -39,27 +118,8 @@ const generateThemedPreCodeStyles = (themeType: ThemeType) => { 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}`]: { @@ -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': { @@ -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%' } }) diff --git a/src/components/Tabs/TabPane.tsx b/src/components/Tabs/TabPane.tsx new file mode 100644 index 00000000..ad9f544d --- /dev/null +++ b/src/components/Tabs/TabPane.tsx @@ -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 = ({ + isActive, + tabConfigItem: { classes = [], render } +}: TabPaneProps) => { + const compClasses = useStyles({ isActive }) + + return ( + +
    {render()}
    +
    + ) +} + +export default TabPane diff --git a/src/components/Tabs/Tabs.stories.tsx b/src/components/Tabs/Tabs.stories.tsx index c6e6deff..1c460236 100644 --- a/src/components/Tabs/Tabs.stories.tsx +++ b/src/components/Tabs/Tabs.stories.tsx @@ -28,6 +28,9 @@ export default { tabConfig: { control: { disable: true } } }, component: Tabs, + parameters: { + storyshots: { disable: true } + }, title: 'Tabs' } as Meta diff --git a/src/components/Tabs/index.tsx b/src/components/Tabs/index.tsx index cecc0506..199c3693 100644 --- a/src/components/Tabs/index.tsx +++ b/src/components/Tabs/index.tsx @@ -2,6 +2,7 @@ import cn from 'classnames' import { createUseStyles } from 'react-jss' import { generateThemedTabsListStyles } from './utils' import Tab from './Tab' +import TabPane from './TabPane' import React, { FC, ReactNode, useState } from 'react' import { styleguide, ThemeType } from 'components/assets/styles' @@ -58,29 +59,13 @@ export const Tabs: FC = ({ const [activeIndex, setActiveIndex] = useState( tabConfig[defaultActiveIndex] ? defaultActiveIndex : 0 ) - const [tabSwitching, setTabSwitching] = useState(false) const tabsClasses = useStyles() - const tabPaneClasses = cn( - tabsClasses.tabPane, - tabConfig[activeIndex].classes - ) - const onClickTab = (tabIndex: number) => { if (onTabChange) onTabChange(tabConfig[tabIndex]) - setTabSwitching(true) setActiveIndex(tabIndex) - - /** - * Toggling the state of tabSwitching ensures a full unmount of the previously rendered component. - * If two tabs render the same component but with different props, React does not unmount the component - * which leads to unexpected behavior and potentially stale state for the newly rendered component. - * This can be also be solved by adding a "key" property to the tab panes rendered with - * tabConfig[activeIndex].render() but because there is no way to enforce this, this is a fallback. - */ - setTimeout(() => setTabSwitching(false)) } const renderTabItems = () => @@ -94,6 +79,15 @@ export const Tabs: FC = ({ /> )) + const renderTabPanes = () => + tabConfig.map((tabConfigItem, i) => ( + + )) + if (!tabConfig.length) { throw new Error('Tab config should have at least one item in the array') } @@ -101,9 +95,7 @@ export const Tabs: FC = ({ return (
    {renderTabItems()}
    -
    - {!tabSwitching && tabConfig[activeIndex].render()} -
    + {renderTabPanes()}
    ) } diff --git a/src/components/Timeline/Separator.tsx b/src/components/Timeline/Separator.tsx index 25fca115..5b65dbc7 100644 --- a/src/components/Timeline/Separator.tsx +++ b/src/components/Timeline/Separator.tsx @@ -13,7 +13,7 @@ import { styleguide, ThemeType } from '../assets/styles' const { alwaysExpanded, collapsed, expanded } = TimelineState const { dark, light } = ThemeType -const { flexDown, font } = styleguide +const { flexDown, font, spacing } = styleguide const useStyles = createUseStyles({ chevron: { @@ -39,7 +39,8 @@ const useStyles = createUseStyles({ }, separator: { ...flexDown, - alignItems: 'center' + alignItems: 'center', + paddingRight: spacing['m+'] }, // eslint-disable-next-line sort-keys '@global': { diff --git a/src/components/Timeline/index.tsx b/src/components/Timeline/index.tsx index 8cb68999..5702601f 100644 --- a/src/components/Timeline/index.tsx +++ b/src/components/Timeline/index.tsx @@ -25,8 +25,7 @@ const useStyles = createUseStyles({ ...generateThemedTimelineItemStyles(light), borderRadius, flexGrow: 1, - height: '100%', - marginLeft: spacing['m+'] + height: '100%' }, wrapper: { ...generateThemedWrapperStyles(light),