diff --git a/src/Button/Button.tsx b/src/Button/Button.tsx index df0a918..6cbf6aa 100644 --- a/src/Button/Button.tsx +++ b/src/Button/Button.tsx @@ -6,7 +6,6 @@ import { createDisabledTextStyles, createFlatBoxStyles, createHatchedBackground, - createWellBorderStyles, focusOutline } from '../common'; import { blockSizes } from '../common/system'; @@ -107,12 +106,14 @@ export const StyledButton = styled.button` border: 2px solid transparent; &:hover, &:focus { - ${!disabled && !active && createWellBorderStyles(false)} + ${!disabled && + !active && + createBorderStyles({ style: 'buttonThin' })} } &:active { - ${!disabled && createWellBorderStyles(true)} + ${!disabled && createBorderStyles({ style: 'buttonThinPressed' })} } - ${active && createWellBorderStyles(true)} + ${active && createBorderStyles({ style: 'buttonThinPressed' })} ${disabled && createDisabledTextStyles()} ` : css` diff --git a/src/Counter/Counter.stories.tsx b/src/Counter/Counter.stories.tsx index 7890db9..a755324 100644 --- a/src/Counter/Counter.stories.tsx +++ b/src/Counter/Counter.stories.tsx @@ -1,6 +1,6 @@ import { ComponentMeta } from '@storybook/react'; import React, { useState } from 'react'; -import { Button, Counter, Panel } from 'react95'; +import { Button, Counter, Frame } from 'react95'; import styled from 'styled-components'; const Wrapper = styled.div` @@ -29,14 +29,14 @@ export function Default() { const [count, setCount] = useState(13); const handleClick = () => setCount(count + 1); return ( - +
-
+ ); } diff --git a/src/Counter/Counter.tsx b/src/Counter/Counter.tsx index 55dfec8..7c2b5c2 100644 --- a/src/Counter/Counter.tsx +++ b/src/Counter/Counter.tsx @@ -1,7 +1,7 @@ import React, { forwardRef, useMemo } from 'react'; import styled from 'styled-components'; -import { createWellBorderStyles } from '../common'; +import { createBorderStyles } from '../common'; import { CommonStyledProps, Sizes } from '../types'; import { Digit } from './Digit'; @@ -13,7 +13,7 @@ type CounterProps = { CommonStyledProps; const CounterWrapper = styled.div` - ${createWellBorderStyles(true)} + ${createBorderStyles({ style: 'status' })} display: inline-flex; background: #000000; `; diff --git a/src/Frame/Frame.spec.tsx b/src/Frame/Frame.spec.tsx new file mode 100644 index 0000000..2cabe12 --- /dev/null +++ b/src/Frame/Frame.spec.tsx @@ -0,0 +1,41 @@ +import { render } from '@testing-library/react'; +import React from 'react'; + +import { Frame } from './Frame'; + +describe('', () => { + it('should render frame', () => { + const { container } = render(); + const frame = container.firstElementChild; + + expect(frame).toBeInTheDocument(); + }); + + it('should render custom styles', () => { + const { container } = render( + + ); + const frame = container.firstElementChild; + + expect(frame).toHaveAttribute('style', 'background-color: papayawhip;'); + }); + + it('should render children', async () => { + const { findByText } = render( + + Cool frame + + ); + const content = await findByText(/cool frame/i); + + expect(content).toBeInTheDocument(); + }); + + it('should render custom props', () => { + const customProps = { title: 'frame' }; + const { container } = render(); + const frame = container.firstElementChild; + + expect(frame).toHaveAttribute('title', 'frame'); + }); +}); diff --git a/src/Panel/Panel.stories.tsx b/src/Frame/Frame.stories.tsx similarity index 54% rename from src/Panel/Panel.stories.tsx rename to src/Frame/Frame.stories.tsx index 930fb96..dd19c23 100644 --- a/src/Panel/Panel.stories.tsx +++ b/src/Frame/Frame.stories.tsx @@ -1,6 +1,6 @@ import { ComponentMeta } from '@storybook/react'; import React from 'react'; -import { Panel } from 'react95'; +import { Frame } from 'react95'; import styled from 'styled-components'; const Wrapper = styled.div` @@ -19,28 +19,30 @@ const Wrapper = styled.div` `; export default { - title: 'Panel', - component: Panel, + title: 'Layout/Frame', + component: Frame, decorators: [story => {story()}] -} as ComponentMeta; +} as ComponentMeta; export function Default() { return ( -

- Notice the subtle difference in borders. The lightest border is not on - the edge of this panel. + This is a frame of the 'window' variant, the default. Notice + the subtle difference in borders. The lightest border is not on the edge + of this frame.

- - This panel on the other hand has the lightest border on the edge. Use - this panel inside 'outside' panels. + + This frame of the 'button' variant on the other hand has the + lightest border on the edge. Use this frame inside 'window' + frames.
- - Put some content here - -
- + + - The 'well' variant of a panel is often used as a window - footer. - -
+ The 'status' variant of a frame is often used as a status bar + at the end of the window. + + ); } diff --git a/src/Frame/Frame.tsx b/src/Frame/Frame.tsx new file mode 100644 index 0000000..440eddd --- /dev/null +++ b/src/Frame/Frame.tsx @@ -0,0 +1,58 @@ +import React, { forwardRef } from 'react'; +import styled, { css } from 'styled-components'; +import { createBorderStyles, createBoxStyles } from '../common'; +import { CommonStyledProps } from '../types'; + +type FrameProps = { + children?: React.ReactNode; + shadow?: boolean; + variant?: 'outside' | 'field' | 'inside' | 'well'; +} & React.HTMLAttributes & + CommonStyledProps; + +const createFrameStyles = (variant: FrameProps['variant']) => { + switch (variant) { + case 'well': + return css` + ${createBorderStyles({ style: 'status' })} + `; + case 'outside': + return css` + ${createBorderStyles({ style: 'window' })} + `; + case 'field': + return css` + ${createBorderStyles({ style: 'field' })} + `; + default: + return css` + ${createBorderStyles()} + `; + } +}; + +const StyledFrame = styled.div>>` + position: relative; + font-size: 1rem; + ${({ variant }) => createFrameStyles(variant)} + ${({ variant }) => + createBoxStyles( + variant === 'field' + ? { background: 'canvas', color: 'canvasText' } + : undefined + )} +`; + +const Frame = forwardRef( + ({ children, shadow = false, variant = 'window', ...otherProps }, ref) => { + return ( + + {children} + + ); + } +); + +Frame.displayName = 'Frame'; + +export { Frame, FrameProps }; diff --git a/src/MenuList/MenuList.tsx b/src/MenuList/MenuList.tsx index 9304c6e..57b5784 100644 --- a/src/MenuList/MenuList.tsx +++ b/src/MenuList/MenuList.tsx @@ -17,7 +17,7 @@ const MenuList = styled.ul.attrs(() => ({ box-sizing: border-box; width: ${props => (props.fullWidth ? '100%' : 'auto')}; padding: 4px; - ${createBorderStyles({ windowBorders: true })} + ${createBorderStyles({ style: 'window' })} ${createBoxStyles()} ${props => props.inline && diff --git a/src/Panel/Panel.spec.tsx b/src/Panel/Panel.spec.tsx deleted file mode 100644 index 90d7e4a..0000000 --- a/src/Panel/Panel.spec.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { render } from '@testing-library/react'; -import React from 'react'; - -import { Panel } from './Panel'; - -describe('', () => { - it('should render panel', () => { - const { container } = render(); - const panel = container.firstElementChild; - - expect(panel).toBeInTheDocument(); - }); - - it('should render custom styles', () => { - const { container } = render( - - ); - const panel = container.firstElementChild; - - expect(panel).toHaveAttribute('style', 'background-color: papayawhip;'); - }); - - it('should render children', async () => { - const { findByText } = render( - - Cool panel - - ); - const content = await findByText(/cool panel/i); - - expect(content).toBeInTheDocument(); - }); - - it('should render custom props', () => { - const customProps = { title: 'panel' }; - const { container } = render(); - const panel = container.firstElementChild; - - expect(panel).toHaveAttribute('title', 'panel'); - }); -}); diff --git a/src/Panel/Panel.tsx b/src/Panel/Panel.tsx deleted file mode 100644 index 58af24d..0000000 --- a/src/Panel/Panel.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React, { forwardRef } from 'react'; -import styled, { css } from 'styled-components'; -import { - createBorderStyles, - createBoxStyles, - createWellBorderStyles -} from '../common'; -import { CommonStyledProps } from '../types'; - -type PanelProps = { - children?: React.ReactNode; - shadow?: boolean; - variant?: 'outside' | 'inside' | 'well'; -} & React.HTMLAttributes & - CommonStyledProps; - -const createPanelStyles = (variant: PanelProps['variant']) => { - switch (variant) { - case 'well': - return css` - ${createWellBorderStyles(true)} - `; - case 'outside': - return css` - ${createBorderStyles({ windowBorders: true })} - `; - default: - return css` - ${createBorderStyles()} - `; - } -}; - -const StyledPanel = styled.div>>` - position: relative; - font-size: 1rem; - ${({ variant }) => createPanelStyles(variant)} - ${createBoxStyles()} -`; - -const Panel = forwardRef(function Panel( - { children, shadow = false, variant = 'outside', ...otherProps }, - ref -) { - return ( - - {children} - - ); -}); - -export { Panel, PanelProps }; diff --git a/src/TableHeadCell/TableHeadCell.tsx b/src/TableHeadCell/TableHeadCell.tsx index dd4c359..81daf51 100644 --- a/src/TableHeadCell/TableHeadCell.tsx +++ b/src/TableHeadCell/TableHeadCell.tsx @@ -37,7 +37,7 @@ const StyledHeadCell = styled.th<{ $disabled: boolean }>` css` &:active { &:before { - ${createBorderStyles({ invert: true, windowBorders: true })} + ${createBorderStyles({ invert: true, style: 'window' })} border-left: none; border-top: none; padding-top: 2px; diff --git a/src/Window/Window.stories.tsx b/src/Window/Window.stories.tsx index 050a044..e27b4c6 100644 --- a/src/Window/Window.stories.tsx +++ b/src/Window/Window.stories.tsx @@ -2,7 +2,7 @@ import { ComponentMeta } from '@storybook/react'; import React from 'react'; import { Button, - Panel, + Frame, Toolbar, Window, WindowContent, @@ -97,9 +97,9 @@ export function Default() { you tho!)

- + Put some useful information here - + diff --git a/src/Window/Window.tsx b/src/Window/Window.tsx index a8b10ea..a1c2d6b 100644 --- a/src/Window/Window.tsx +++ b/src/Window/Window.tsx @@ -15,7 +15,7 @@ const StyledWindow = styled.div` position: relative; padding: 4px; font-size: 1rem; - ${createBorderStyles({ windowBorders: true })} + ${createBorderStyles({ style: 'window' })} ${createBoxStyles()} `; diff --git a/src/common/index.ts b/src/common/index.ts index 9f90ae7..6789ed1 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -1,5 +1,5 @@ import { css } from 'styled-components'; -import { Color, CommonThemeProps } from '../types'; +import { Color, CommonThemeProps, Theme } from '../types'; export const shadow = '4px 4px 10px 0 rgba(0, 0, 0, 0.35)'; export const insetShadow = 'inset 2px 2px 3px rgba(0,0,0,0.2)'; @@ -11,11 +11,17 @@ export const createDisabledTextStyles = () => css` /* filter: grayscale(100%); */ `; -export const createBoxStyles = () => css` +export const createBoxStyles = ({ + background = 'material', + color = 'materialText' +}: { + background?: keyof Theme; + color?: keyof Theme; +} = {}) => css` box-sizing: border-box; display: inline-block; - background: ${({ theme }) => theme.material}; - color: ${({ theme }) => theme.materialText}; + background: ${({ theme }) => theme[background]}; + color: ${({ theme }) => theme[color]}; `; // TODO for flat box styles add checkered background when disabled (not solid color) @@ -57,56 +63,137 @@ export const createFlatBoxStyles = () => css` outline-offset: -4px; `; +export type BorderStyles = + | 'button' + | 'buttonPressed' + | 'buttonThin' + | 'buttonThinPressed' + | 'field' + | 'grouping' + | 'status' + | 'window'; + +type BorderStyle = { + topLeftOuter: keyof Theme; + topLeftInner: keyof Theme | null; + bottomRightInner: keyof Theme | null; + bottomRightOuter: keyof Theme; +}; + +const borderStyles: Record = { + button: { + topLeftOuter: 'borderLightest', + topLeftInner: 'borderLight', + bottomRightInner: 'borderDark', + bottomRightOuter: 'borderDarkest' + }, + buttonPressed: { + topLeftOuter: 'borderDarkest', + topLeftInner: 'borderDark', + bottomRightInner: 'borderLight', + bottomRightOuter: 'borderLightest' + }, + buttonThin: { + topLeftOuter: 'borderLightest', + topLeftInner: null, + bottomRightInner: null, + bottomRightOuter: 'borderDark' + }, + buttonThinPressed: { + topLeftOuter: 'borderDark', + topLeftInner: null, + bottomRightInner: null, + bottomRightOuter: 'borderLightest' + }, + field: { + topLeftOuter: 'borderDark', + topLeftInner: 'borderDarkest', + bottomRightInner: 'borderLight', + bottomRightOuter: 'borderLightest' + }, + grouping: { + topLeftOuter: 'borderDark', + topLeftInner: 'borderLightest', + bottomRightInner: 'borderDark', + bottomRightOuter: 'borderLightest' + }, + status: { + topLeftOuter: 'borderDark', + topLeftInner: null, + bottomRightInner: null, + bottomRightOuter: 'borderLightest' + }, + window: { + topLeftOuter: 'borderLight', + topLeftInner: 'borderLightest', + bottomRightInner: 'borderDark', + bottomRightOuter: 'borderDarkest' + } +}; + +export const createInnerBorderWithShadow = ({ + theme, + topLeftInner, + bottomRightInner, + hasShadow = false, + hasInsetShadow = false +}: { + theme: Theme; + topLeftInner: keyof Theme | null; + bottomRightInner: keyof Theme | null; + hasShadow?: boolean; + hasInsetShadow?: boolean; +}) => + [ + hasShadow ? shadow : false, + hasInsetShadow ? insetShadow : false, + topLeftInner !== null + ? `inset 1px 1px 0px 1px ${theme[topLeftInner]}` + : false, + bottomRightInner !== null + ? `inset -1px -1px 0 1px ${theme[bottomRightInner]}` + : false + ] + .filter(Boolean) + .join(', '); + export const createBorderStyles = ({ invert = false, - windowBorders = false -} = {}) => - invert - ? css` - border-style: solid; - border-width: 2px; - border-left-color: ${({ theme }) => theme.borderDarkest}; - border-top-color: ${({ theme }) => theme.borderDarkest}; - border-right-color: ${({ theme }) => theme.borderLightest}; - border-bottom-color: ${({ theme }) => theme.borderLightest}; - box-shadow: ${props => props.shadow && `${shadow}, `} inset 1px 1px 0px - 1px ${({ theme }) => theme.borderDark}, - inset -1px -1px 0 1px ${({ theme }) => theme.borderLight}; - ` - : css` - border-style: solid; - border-width: 2px; - border-left-color: ${({ theme }) => - windowBorders ? theme.borderLight : theme.borderLightest}; - border-top-color: ${({ theme }) => - windowBorders ? theme.borderLight : theme.borderLightest}; - border-right-color: ${({ theme }) => theme.borderDarkest}; - border-bottom-color: ${({ theme }) => theme.borderDarkest}; - box-shadow: ${props => props.shadow && `${shadow}, `} inset 1px 1px 0px - 1px - ${({ theme }) => - windowBorders ? theme.borderLightest : theme.borderLight}, - inset -1px -1px 0 1px ${({ theme }) => theme.borderDark}; - `; + style = 'button' +}: { invert?: boolean; style?: BorderStyles } = {}) => { + const borders = { + topLeftOuter: invert ? 'bottomRightOuter' : 'topLeftOuter', + topLeftInner: invert ? 'bottomRightInner' : 'topLeftInner', + bottomRightInner: invert ? 'topLeftInner' : 'bottomRightInner', + bottomRightOuter: invert ? 'topLeftOuter' : 'bottomRightOuter' + } as const; + return css` + border-style: solid; + border-width: 2px; + border-left-color: ${({ theme }) => + theme[borderStyles[style][borders.topLeftOuter]]}; + border-top-color: ${({ theme }) => + theme[borderStyles[style][borders.topLeftOuter]]}; + border-right-color: ${({ theme }) => + theme[borderStyles[style][borders.bottomRightOuter]]}; + border-bottom-color: ${({ theme }) => + theme[borderStyles[style][borders.bottomRightOuter]]}; + box-shadow: ${({ theme, shadow: hasShadow }) => + createInnerBorderWithShadow({ + theme, + topLeftInner: borderStyles[style][borders.topLeftInner], + bottomRightInner: borderStyles[style][borders.bottomRightInner], + hasShadow + })}; + `; +}; +/** @deprecated Use `createBorderStyles` instead */ export const createWellBorderStyles = (invert = false) => - invert - ? css` - border-style: solid; - border-width: 2px; - border-left-color: ${({ theme }) => theme.borderDark}; - border-top-color: ${({ theme }) => theme.borderDark}; - border-right-color: ${({ theme }) => theme.borderLightest}; - border-bottom-color: ${({ theme }) => theme.borderLightest}; - ` - : css` - border-style: solid; - border-width: 2px; - border-left-color: ${({ theme }) => theme.borderLightest}; - border-top-color: ${({ theme }) => theme.borderLightest}; - border-right-color: ${({ theme }) => theme.borderDark}; - border-bottom-color: ${({ theme }) => theme.borderDark}; - `; + createBorderStyles({ + invert: !invert, + style: 'status' + }); export const focusOutline = () => css` outline: 2px dotted ${({ theme }) => theme.materialText}; @@ -141,7 +228,7 @@ export const createScrollbars = (variant = 'default') => css` ${createBoxStyles()} ${variant === 'flat' ? createFlatBoxStyles() - : createBorderStyles({ windowBorders: true })} + : createBorderStyles({ style: 'window' })} outline-offset: -2px; } @@ -152,7 +239,7 @@ export const createScrollbars = (variant = 'default') => css` ${createBoxStyles()} ${variant === 'flat' ? createFlatBoxStyles() - : createBorderStyles({ windowBorders: true })} + : createBorderStyles({ style: 'window' })} display: block; outline-offset: -2px; height: 26px; @@ -165,7 +252,7 @@ export const createScrollbars = (variant = 'default') => css` ::-webkit-scrollbar-button:active { background-position: 0 1px; ${variant === 'default' - ? createBorderStyles({ windowBorders: true, invert: true }) + ? createBorderStyles({ style: 'window', invert: true }) : ''} } diff --git a/src/index.ts b/src/index.ts index 93e0ea6..04e603e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,13 +11,13 @@ export * from './Checkbox/Checkbox'; export * from './ColorInput/ColorInput'; export * from './Counter/Counter'; export * from './DatePicker/DatePicker'; +export * from './Frame/Frame'; export * from './GroupBox/GroupBox'; export * from './Handle/Handle'; export * from './Hourglass/Hourglass'; export * from './MenuList/MenuList'; export * from './Monitor/Monitor'; export * from './NumberInput/NumberInput'; -export * from './Panel/Panel'; export * from './ProgressBar/ProgressBar'; export * from './Radio/Radio'; export * from './ScrollView/ScrollView'; @@ -48,4 +48,5 @@ export * from './legacy/Fieldset'; export * from './legacy/List'; export * from './legacy/ListItem'; export * from './legacy/NumberField'; +export * from './legacy/Panel'; export * from './legacy/Progress'; diff --git a/src/legacy/Panel.tsx b/src/legacy/Panel.tsx new file mode 100644 index 0000000..b317f56 --- /dev/null +++ b/src/legacy/Panel.tsx @@ -0,0 +1,7 @@ +import { Frame, FrameProps } from '../Frame/Frame'; + +/** @deprecated Use `FrameProps` */ +export type PanelProps = FrameProps; + +/** @deprecated Use `Frame` */ +export const Panel = Frame;