From 47d94bab3620bcea4d1453a8a9c81d6fa49bee05 Mon Sep 17 00:00:00 2001 From: atanasster Date: Sun, 31 May 2020 00:13:15 -0400 Subject: [PATCH] feat: screen resizer --- .../src/components/Layout.tsx | 67 ++++++--- .../src/components/Sidebar.tsx | 2 +- .../storybook/src/docs-page/DocsContainer.tsx | 7 +- ui/app-components/README.md | 1 + ui/app-components/package.json | 1 + ui/app-components/src/Header/Header.tsx | 14 +- ui/app-components/src/Navmenu/Navmenu.tsx | 133 +++++++++++------- .../src/Resizer/Resizer.stories.tsx | 35 +++++ ui/app-components/src/Resizer/Resizer.tsx | 58 ++++++++ ui/app-components/src/Resizer/index.ts | 1 + ui/app-components/src/Sidebar/Sidebar.tsx | 23 ++- ui/app-components/src/index.ts | 1 + ui/blocks/src/PageContainer/PageContainer.tsx | 11 +- ui/blocks/src/Playground/Playground.tsx | 4 +- yarn.lock | 7 + 15 files changed, 261 insertions(+), 104 deletions(-) create mode 100644 ui/app-components/src/Resizer/Resizer.stories.tsx create mode 100644 ui/app-components/src/Resizer/Resizer.tsx create mode 100644 ui/app-components/src/Resizer/index.ts diff --git a/integrations/gatsby-theme-stories/src/components/Layout.tsx b/integrations/gatsby-theme-stories/src/components/Layout.tsx index e5f526107..31d6669c3 100644 --- a/integrations/gatsby-theme-stories/src/components/Layout.tsx +++ b/integrations/gatsby-theme-stories/src/components/Layout.tsx @@ -1,6 +1,6 @@ /** @jsx jsx */ -import React, { FC } from 'react'; -import { jsx, Flex, Container } from 'theme-ui'; +import React, { FC, useState } from 'react'; +import { jsx, Container } from 'theme-ui'; import { Global } from '@emotion/core'; import { ThemeProvider, @@ -9,7 +9,11 @@ import { TabList, TabPanel, } from '@component-controls/components'; -import { SidebarContextProvider } from '@component-controls/app-components'; +import { + SidebarContextProvider, + SidebarContext, + Resizer, +} from '@component-controls/app-components'; import { PageContainer } from '@component-controls/blocks'; import { Store } from '@component-controls/store'; import { SEO } from './SEO'; @@ -43,28 +47,45 @@ export const Layout: FC = ({ /> - - - - -
- {pages.length > 1 && ( - + + {({ collapsed }) => { + const content = ( + + +
+ {pages.length > 1 && ( + + {pages.map(page => ( + {page.title} + ))} + + )} +
+ + {pages.map(page => ( - {page.title} + + {page.render()} + ))} -
- )} -
- - - {pages.map(page => ( - {page.render()} - ))} - -
-
-
+ + + + ); + return collapsed ? ( + content + ) : ( + + + {content} + + ); + }} +
); diff --git a/integrations/gatsby-theme-stories/src/components/Sidebar.tsx b/integrations/gatsby-theme-stories/src/components/Sidebar.tsx index 46bfd090a..a411647e7 100644 --- a/integrations/gatsby-theme-stories/src/components/Sidebar.tsx +++ b/integrations/gatsby-theme-stories/src/components/Sidebar.tsx @@ -104,7 +104,7 @@ export const Sidebar: FC = ({ storyId }) => { const [search, setSearch] = useState(undefined); return ( - + {siteTitle} = ({ const isDark = useIsDark(); return ( - + {children} diff --git a/ui/app-components/README.md b/ui/app-components/README.md index 6ecf55d33..455cc8a7d 100644 --- a/ui/app-components/README.md +++ b/ui/app-components/README.md @@ -16,6 +16,7 @@ Third-party libraries used in no particular order: - [theme-ui](https://theme-ui.com) as the theming and components foundation. - [octicons](https://octicons.github.com) for icons used in the components. +- [react-simple-resizer](https://github.com/LeetCode-OpenSource/react-simple-resizer) for panels resizing. # List of components diff --git a/ui/app-components/package.json b/ui/app-components/package.json index 1257429b0..e19599fb1 100644 --- a/ui/app-components/package.json +++ b/ui/app-components/package.json @@ -34,6 +34,7 @@ "@primer/octicons-react": "^9.6.0", "react": "^16.8.3", "react-dom": "^16.8.3", + "react-simple-resizer": "^2.1.0", "theme-ui": "^0.3.1", "@theme-ui/match-media": "^0.3.1" }, diff --git a/ui/app-components/src/Header/Header.tsx b/ui/app-components/src/Header/Header.tsx index 0bb5827d6..2cba88cdf 100644 --- a/ui/app-components/src/Header/Header.tsx +++ b/ui/app-components/src/Header/Header.tsx @@ -15,8 +15,8 @@ export interface HeaderProps { */ export const Header: FC = ({ children, - zIndex, - position, + zIndex = 10, + position = 'sticky', ...rest }) => ( = ({ zIndex, } : undefined), - background: 'primary', - p: 2, + position, + backgroundColor: 'background', + px: 2, mb: 1, justifyItems: 'between', alignItems: 'center', @@ -44,8 +45,3 @@ export const Header: FC = ({ {children} ); - -Header.defaultProps = { - position: 'relative', - zIndex: 10, -}; diff --git a/ui/app-components/src/Navmenu/Navmenu.tsx b/ui/app-components/src/Navmenu/Navmenu.tsx index b8d65311b..7e65442da 100644 --- a/ui/app-components/src/Navmenu/Navmenu.tsx +++ b/ui/app-components/src/Navmenu/Navmenu.tsx @@ -1,6 +1,15 @@ /** @jsx jsx */ import React, { FC, useEffect, useState } from 'react'; -import { jsx, Box, Flex, Button, ButtonProps, LinkProps, Text } from 'theme-ui'; +import { + jsx, + Box, + Flex, + Button, + ButtonProps, + LinkProps, + Text, + Theme, +} from 'theme-ui'; import Octicon, { ChevronDown, ChevronUp } from '@primer/octicons-react'; import { Keyboard, @@ -39,10 +48,8 @@ export interface NavMenuProps { /** Array of menu items */ items: MenuItems; /** Initially active menu item */ - activeItem?: { - id?: string; - label?: string; - }; + activeItem?: Pick; + /** Custom class to use for the button instead of Button */ buttonClass?: ButtonClassType; @@ -55,22 +62,28 @@ export interface NavMenuProps { /** If specified, will filter the items by the search terms */ search?: string; } -const isActive = (active: MenuItem, item: MenuItem): boolean => - item.id === active.id || item.label === active.label; +const isActive = ( + item: MenuItem, + active?: Pick, +): boolean => + active ? item.id === active.id || item.label === active.label : false; -const hasActiveChidlren = (active: MenuItem, item: MenuItem): boolean => { - if (isActive(active, item)) { +const hasActiveChidlren = ( + item: MenuItem, + active?: Pick, +): boolean => { + if (isActive(item, active)) { return true; } return item.items - ? item.items.some(t => hasActiveChidlren(active, t)) + ? item.items.some(t => hasActiveChidlren(t, active)) : false; }; const getExpandedItems = (children: MenuItems, active?: MenuItem): MenuItems => children.reduce((expandedItems: MenuItems, item: MenuItem) => { const { items, expanded } = item; - if (expanded || (active && hasActiveChidlren(active, item))) { + if (expanded || hasActiveChidlren(item, active)) { expandedItems.push(item); } if (items) { @@ -156,6 +169,36 @@ const filterItems = (items: MenuItems, search?: string): MenuItems => { return items; }; +const stateFromProps = ({ + items, + expandAll, + activeItem, + search, +}: Pick) => { + const filteredItems = filterItems(items, search); + const collapsibleItems = getCollapsibleItems(filteredItems); + let expandedItems; + if (expandAll || (search && search.length)) { + expandedItems = collapsibleItems; + } else { + expandedItems = getExpandedItems(filteredItems, activeItem); + } + + const allExpanded = + typeof expandAll !== 'undefined' + ? expandAll + : collapsibleItems.length === expandedItems.length; + return { + expandedItems, + items, + filteredItems, + search, + collapsibleItems, + allExpanded, + expandAll, + originalExpandAll: expandAll, + }; +}; interface NavMenuState { expandedItems?: MenuItems; originalExpandAll?: boolean; @@ -170,50 +213,33 @@ interface NavMenuState { * Hierarchical collapsible menu */ -export const Navmenu: FC = props => { - const stateFromProps = ({ - items, - expandAll, - activeItem, - search, - }: Pick) => { - const filteredItems = filterItems(items, search); - const collapsibleItems = getCollapsibleItems(filteredItems); - let expandedItems; - if (expandAll || (search && search.length)) { - expandedItems = collapsibleItems; - } else { - expandedItems = getExpandedItems(filteredItems, activeItem); - } - - const allExpanded = - typeof expandAll !== 'undefined' - ? expandAll - : collapsibleItems.length === expandedItems.length; - return { - expandedItems, +export const Navmenu: FC = ({ + items, + expandAll, + activeItem, + search, + onSelect, + buttonClass, +}) => { + const [state, setState] = useState( + stateFromProps({ items, - filteredItems, - search, - collapsibleItems, - allExpanded, expandAll, - originalExpandAll: expandAll, - }; - }; - - const [state, setState] = useState(stateFromProps(props)); + activeItem, + search, + }), + ); useEffect(() => { setState( stateFromProps({ - items: props.items, - expandAll: props.expandAll, - activeItem: props.activeItem, - search: props.search, + items, + expandAll, + activeItem, + search, }), ); - }, [props.items, props.expandAll, props.activeItem, props.search]); + }, [items, expandAll, activeItem, search]); const onMenuChange = (item: MenuItem, expanded: boolean) => { const { expandedItems, filteredItems } = state; @@ -243,7 +269,6 @@ export const Navmenu: FC = props => { }; const renderItem = (item: MenuItem, level: number = 1) => { - const { activeItem, onSelect, buttonClass } = props; const { expandedItems } = state; // eslint-disable-next-line @typescript-eslint/no-unused-vars const { items, id, label, widget, icon, onClick, ...rest } = item; @@ -258,9 +283,14 @@ export const Navmenu: FC = props => { if (activeItem && activeItem.id === id) { background = 'active'; } - + const isActiveParent = hasActiveChidlren(item, activeItem); const content = ( - + = props => { background: 'none', textDecoration: 'none', cursor: 'pointer', + borderLeft: (t: Theme) => + isActiveParent ? `6px solid ${t.colors?.accent}` : 'none', }} onClick={() => { if (items) { @@ -285,7 +317,6 @@ export const Navmenu: FC = props => { flexDirection: 'row', alignItems: 'center', position: 'relative', - ml: level, '& strong': { color: 'text', }, diff --git a/ui/app-components/src/Resizer/Resizer.stories.tsx b/ui/app-components/src/Resizer/Resizer.stories.tsx new file mode 100644 index 000000000..7e7b5fc5f --- /dev/null +++ b/ui/app-components/src/Resizer/Resizer.stories.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Resizer } from '.'; + +export default { + title: 'App components/Resizer', + component: Resizer, +}; + +export const overview = () => ( + +
+
+ +); + +export const minSize = () => ( + +
+
+ +); + +export const barSize = () => ( + +
+
+ +); + +export const onAfterResize = () => ( + alert('After resize') }}> +
+
+ +); diff --git a/ui/app-components/src/Resizer/Resizer.tsx b/ui/app-components/src/Resizer/Resizer.tsx new file mode 100644 index 000000000..2ea077c00 --- /dev/null +++ b/ui/app-components/src/Resizer/Resizer.tsx @@ -0,0 +1,58 @@ +/** @jsx jsx */ +import { Children, FC } from 'react'; +import { jsx } from 'theme-ui'; +import { + Container, + ContainerProps, + Section, + SectionProps, + Bar, + BarProps, +} from 'react-simple-resizer'; + +export interface ResizerProps { + /** + * contaoner props + */ + containerProps?: ContainerProps; + /** + * section 1 props + */ + sectionOneProps?: SectionProps; + /** + * section 2 props + */ + sectionTwoProps?: SectionProps; + + /** + * bar props + */ + barProps?: BarProps; +} + +/** + * resizing screen areas component + */ +export const Resizer: FC = ({ + children, + containerProps, + sectionOneProps, + sectionTwoProps, + barProps, +}) => { + const childArr = Children.toArray(children); + if (childArr.length !== 2) { + throw new Error('Resizer should have exactly two children'); + } + return ( + +
{childArr[0]}
+ +
{childArr[1]}
+
+ ); +}; diff --git a/ui/app-components/src/Resizer/index.ts b/ui/app-components/src/Resizer/index.ts new file mode 100644 index 000000000..03bce537b --- /dev/null +++ b/ui/app-components/src/Resizer/index.ts @@ -0,0 +1 @@ +export * from './Resizer'; diff --git a/ui/app-components/src/Sidebar/Sidebar.tsx b/ui/app-components/src/Sidebar/Sidebar.tsx index 7975d0031..80210715e 100644 --- a/ui/app-components/src/Sidebar/Sidebar.tsx +++ b/ui/app-components/src/Sidebar/Sidebar.tsx @@ -2,7 +2,6 @@ import React, { FC, useContext } from 'react'; import { jsx, Box, Flex, BoxProps, Heading } from 'theme-ui'; import { useBreakpointIndex } from '@theme-ui/match-media'; -import { Collapsible, CollapsibleProps } from '@component-controls/components'; import { SidebarContext } from './SidebarContext'; @@ -24,11 +23,6 @@ export interface SidebarProps { children content elements to be displayed in Sidebar */ children: React.ReactNode; - - /** - * collapsible animate height props - */ - animate?: Omit; } /** @@ -36,9 +30,8 @@ export interface SidebarProps { */ export const Sidebar: FC = ({ title, - width, + width = '100%', children, - animate, ...rest }) => { const toggleContext = useContext(SidebarContext); @@ -46,12 +39,12 @@ export const Sidebar: FC = ({ const size: number = useBreakpointIndex(); const isCollapsed = (collapsible && size <= 1 && collapsed === undefined) || collapsed === true; - return ( - = ({ {children} - + ); }; diff --git a/ui/app-components/src/index.ts b/ui/app-components/src/index.ts index d270e8f70..6b6e07555 100644 --- a/ui/app-components/src/index.ts +++ b/ui/app-components/src/index.ts @@ -2,4 +2,5 @@ export * from './ColorMode'; export * from './Header'; export * from './Keyboard'; export * from './Navmenu'; +export * from './Resizer'; export * from './Sidebar'; diff --git a/ui/blocks/src/PageContainer/PageContainer.tsx b/ui/blocks/src/PageContainer/PageContainer.tsx index 77a0fc085..5795327b3 100644 --- a/ui/blocks/src/PageContainer/PageContainer.tsx +++ b/ui/blocks/src/PageContainer/PageContainer.tsx @@ -29,6 +29,11 @@ export interface PageContainerProps { * components to customize the markdown display. */ components?: MDXProviderComponents; + + /** + * limit the max width of the page + */ + maxWidth?: number | string; } /** @@ -42,6 +47,7 @@ export const PageContainer: FC = ({ store, options, components = {}, + maxWidth, }) => { let scrollId: string | undefined; try { @@ -76,13 +82,14 @@ export const PageContainer: FC = ({ position: 'relative', display: 'flex', justifyContent: 'center', - padding: '4rem 20px', + px: 4, + py: 4, bg: 'background', color: 'text', fontFamily: 'body', }} > - + {store && storyId ? ( = ({ ], [scale], ); - const childArr = React.Children.toArray(children); + const childArr = Children.toArray(children); const isDark = dark === undefined ? theme.initialColorModeName === 'dark' : dark; diff --git a/yarn.lock b/yarn.lock index 06b895756..010500888 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17074,6 +17074,13 @@ react-side-effect@^2.1.0: resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.0.tgz#1ce4a8b4445168c487ed24dab886421f74d380d3" integrity sha512-IgmcegOSi5SNX+2Snh1vqmF0Vg/CbkycU9XZbOHJlZ6kMzTmi3yc254oB1WCkgA7OQtIAoLmcSFuHTc/tlcqXg== +react-simple-resizer@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-simple-resizer/-/react-simple-resizer-2.1.0.tgz#9645d2bc2f1ee043435804619826cad0bf2511d7" + integrity sha512-iiPER+vuKsW5+6+HroNnahc2Cah6UpO4w9SeuGw1pf/6p/F/vAD4+4288yJS5fLY4xJpDNEiOiRBk2kLUui6nw== + dependencies: + rxjs "^6.3.3" + react-sizeme@^2.6.7: version "2.6.12" resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-2.6.12.tgz#ed207be5476f4a85bf364e92042520499455453e"