diff --git a/README.md b/README.md index 2b839e7ac..d299fa85f 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ - [@component-controls/blocks](#component-controlsblocks) - [@component-controls/components](#component-controlscomponents) - [@component-controls/editors](#component-controlseditors) + - [@component-controls/app-components](#component-controlsapp-components) - [Props info](#props-info) - [@component-controls/react-docgen-info](#component-controlsreact-docgen-info) - [@component-controls/react-docgen-typescript-info](#component-controlsreact-docgen-typescript-info) @@ -312,6 +313,23 @@ Some of the guiding design goals for this library: + + + + +## [@component-controls/app-components](https://github.com/ccontrols/component-controls/blob/master/ui/app-components) + +Standalone application components. + +Application components to create standaline user interface for component-controls. + +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. + + + # Props info The props-info packages are designed to solidify prop tables extraction for components. Every props-info package must export a props extractor factory that is initialized with the options for the specific library and returns a function that can parse a file and extract prop tables. diff --git a/core/store/README.md b/core/store/README.md index 511fd3ee6..fcdc7ba4d 100644 --- a/core/store/README.md +++ b/core/store/README.md @@ -29,7 +29,7 @@ $ npm install @component-controls/store --save-dev Store class used to query the stories and exchange information between processes -_defined in [@component-controls/store/src/Store/Store.ts](https://github.com/ccontrols/component-controls/tree/master/core/store/src/Store/Store.ts#L28)_ +_defined in [@component-controls/store/src/Store/Store.ts](https://github.com/ccontrols/component-controls/tree/master/core/store/src/Store/Store.ts#L27)_ @@ -47,7 +47,7 @@ _defined in [@component-controls/store/src/Store/Store.ts](https://github.com/cc ## StoreOptions -_defined in [@component-controls/store/src/Store/Store.ts](https://github.com/ccontrols/component-controls/tree/master/core/store/src/Store/Store.ts#L15)_ +_defined in [@component-controls/store/src/Store/Store.ts](https://github.com/ccontrols/component-controls/tree/master/core/store/src/Store/Store.ts#L14)_ diff --git a/core/webpack-compile/README.md b/core/webpack-compile/README.md index ba9b96029..162b38768 100644 --- a/core/webpack-compile/README.md +++ b/core/webpack-compile/README.md @@ -40,7 +40,7 @@ $ npm install @component-controls/webpack-compile --save-dev compile the stories with webpack returns the stories store object -_defined in [@component-controls/webpack-compile/src/index.ts](https://github.com/ccontrols/component-controls/tree/master/core/webpack-compile/src/index.ts#L9)_ +_defined in [@component-controls/webpack-compile/src/index.ts](https://github.com/ccontrols/component-controls/tree/master/core/webpack-compile/src/index.ts#L8)_ **function** compile(`__namedParameters`\*: **configPath**: string**presets**: string | [RuleOptions](#ruleoptions)\[]**webPack**: [Configuration](#configuration)): Promise<[CompileResults](#compileresults)>; @@ -56,7 +56,7 @@ _defined in [@component-controls/webpack-compile/src/index.ts](https://github.co compile the stories with webpack and launch watching for changes returns the stories store object -_defined in [@component-controls/webpack-compile/src/index.ts](https://github.com/ccontrols/component-controls/tree/master/core/webpack-compile/src/index.ts#L26)_ +_defined in [@component-controls/webpack-compile/src/index.ts](https://github.com/ccontrols/component-controls/tree/master/core/webpack-compile/src/index.ts#L25)_ **function** watch(`__namedParameters`\*: **configPath**: string**presets**: string | [RuleOptions](#ruleoptions)\[]**watchOptions**: [WatchOptions](#watchoptions)**webPack**: [Configuration](#configuration)): Promise<[CompileResults](#compileresults)>; diff --git a/examples/storybook-6-no-docs/.storybook/main.js b/examples/storybook-6-no-docs/.storybook/main.js index b04a51385..2be7bf609 100644 --- a/examples/storybook-6-no-docs/.storybook/main.js +++ b/examples/storybook-6-no-docs/.storybook/main.js @@ -28,6 +28,7 @@ module.exports = { stories: [ '../../../ui/editors/src/**/*.stories.(js|jsx|tsx|mdx)', '../../../ui/components/src/**/*.stories.(js|jsx|tsx|mdx)', + '../../../ui/app-components/src/**/*.stories.(js|jsx|tsx|mdx)', '../../../ui/blocks/src/**/*.stories.(js|jsx|tsx|mdx)', '../../../core/specification/src/stories/**/*.stories.(js|jsx|tsx|mdx)', '../../../plugins/axe-plugin/src/stories/**/*.stories.(js|jsx|tsx|mdx)', diff --git a/examples/storybook-6-no-docs/.storybook/preview.js b/examples/storybook-6-no-docs/.storybook/preview.js index 4fa89bcb2..cdc932b73 100644 --- a/examples/storybook-6-no-docs/.storybook/preview.js +++ b/examples/storybook-6-no-docs/.storybook/preview.js @@ -5,7 +5,7 @@ import { ThemeProvider } from '@component-controls/storybook'; addDecorator(story => ( {story()} )); -const categories = ['Introduction', 'Controls','Blocks', 'Editors', 'Components', 'Plugins'] +const categories = ['Introduction', 'Controls','Blocks', 'Editors', 'Components', 'App components', 'Plugins'] addParameters({ dependencies: { hideEmpty: true }, options: { diff --git a/examples/storybook-6-no-docs/stories/mdx.stories.mdx b/examples/storybook-6-no-docs/stories/mdx.stories.mdx index 893bbb44c..8af50c97d 100644 --- a/examples/storybook-6-no-docs/stories/mdx.stories.mdx +++ b/examples/storybook-6-no-docs/stories/mdx.stories.mdx @@ -42,13 +42,6 @@ import { customStory as customStoryDeconstructedProps } from '../../stories/src/ - - - {customStory()} - - - - {props => customStoryProps(props)} diff --git a/examples/storybook-6/.storybook/main.js b/examples/storybook-6/.storybook/main.js index 47b4c1160..71f4b2909 100644 --- a/examples/storybook-6/.storybook/main.js +++ b/examples/storybook-6/.storybook/main.js @@ -5,6 +5,7 @@ module.exports = { stories: [ '../../../core/specification/src/stories/**/*.stories.(js|jsx|tsx|mdx)', '../../../ui/editors/src/**/*.stories.(js|jsx|tsx|mdx)', + '../../../ui/app-components/src/**/*.stories.(js|jsx|tsx|mdx)', '../../../ui/components/src/**/*.stories.(js|jsx|tsx|mdx)', '../../../ui/blocks/src/**/*.stories.(js|jsx|tsx|mdx)', '../../../plugins/axe-plugin/src/stories/**/*.stories.(js|jsx|tsx|mdx)', diff --git a/examples/storybook-6/.storybook/preview.js b/examples/storybook-6/.storybook/preview.js index 4fa89bcb2..cdc932b73 100644 --- a/examples/storybook-6/.storybook/preview.js +++ b/examples/storybook-6/.storybook/preview.js @@ -5,7 +5,7 @@ import { ThemeProvider } from '@component-controls/storybook'; addDecorator(story => ( {story()} )); -const categories = ['Introduction', 'Controls','Blocks', 'Editors', 'Components', 'Plugins'] +const categories = ['Introduction', 'Controls','Blocks', 'Editors', 'Components', 'App components', 'Plugins'] addParameters({ dependencies: { hideEmpty: true }, options: { diff --git a/integrations/gatsby-theme-stories/.eslintignore b/integrations/gatsby-theme-stories/.eslintignore new file mode 100644 index 000000000..53c37a166 --- /dev/null +++ b/integrations/gatsby-theme-stories/.eslintignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/integrations/gatsby-theme-stories/README.md b/integrations/gatsby-theme-stories/README.md index 1db9a9568..0b3d21a27 100644 --- a/integrations/gatsby-theme-stories/README.md +++ b/integrations/gatsby-theme-stories/README.md @@ -9,7 +9,13 @@ - [Examples](#examples) - [Simple page](#simple-page) - [API](#api) - - [AxeAllyBlock](#insaxeallyblockins) + - [Store](#insstoreins) + - [Header](#insheaderins) + - [Layout](#inslayoutins) + - [SEO](#insseoins) + - [Sidebar](#inssidebarins) + - [pages](#inspagesins) + - [StoryPage](#insstorypageins) # In action @@ -113,22 +119,69 @@ export default { -## AxeAllyBlock +## Store -Story block container that displays displays the [axe](https://github.com/dequelabs/axe-core) ally test results +Store class used to query the stories and exchange information between processes -_AxeAllyBlock [source code](https:/github.com/ccontrols/component-controls/tree/master/plugins/axe-plugin/src/index.tsx)_ +_Store [source code](https:/github.com/ccontrols/component-controls/tree/master/integrations/gatsby-theme-stories/src/index.ts)_ + +## Header + +_Header [source code](https:/github.com/ccontrols/component-controls/tree/master/integrations/gatsby-theme-stories/src/components/Header.tsx)_ + +### properties + +| Name | Type | Description | +| ------- | -------- | ----------- | +| `title` | _string_ | | + +## Layout + +_Layout [source code](https:/github.com/ccontrols/component-controls/tree/master/integrations/gatsby-theme-stories/src/components/Layout.tsx)_ + +### properties + +| Name | Type | Description | +| ------------- | -------- | ----------- | +| `title` | _string_ | | +| `storyStore*` | _Store_ | | +| `storyId*` | _string_ | | + +## SEO + +_SEO [source code](https:/github.com/ccontrols/component-controls/tree/master/integrations/gatsby-theme-stories/src/components/SEO.tsx)_ + +### properties + +| Name | Type | Description | +| ------------- | -------- | ----------- | +| `title` | _string_ | | +| `description` | _string_ | | +| `pathname` | _string_ | | +| `image` | _string_ | | + +## Sidebar + +_Sidebar [source code](https:/github.com/ccontrols/component-controls/tree/master/integrations/gatsby-theme-stories/src/components/Sidebar.tsx)_ + +### properties + +| Name | Type | Description | +| --------- | -------- | ----------- | +| `storyId` | _string_ | | + +## pages + +_pages [source code](https:/github.com/ccontrols/component-controls/tree/master/integrations/gatsby-theme-stories/src/pages/index.tsx)_ + +## StoryPage + +_StoryPage [source code](https:/github.com/ccontrols/component-controls/tree/master/integrations/gatsby-theme-stories/src/templates/StoryPage.tsx)_ ### properties -| Name | Type | Description | -| ------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -| `axeOptions` | _Spec_ | | -| `id` | _string_ | id of the story optional id to be used for the block if no id is provided, one will be calculated automatically from the title. | -| `name` | _string_ | alternatively you can use the name of a story to load from an external file | -| `title` | _string_ | optional section title for the block. | -| `description` | _string_ | optional markdown description. | -| `collapsible` | _boolean_ | if false, will nothave a collapsible frame. | -| `sxStyle` | _SystemStyleObject_ | theme-ui styling object for Block Box | +| Name | Type | Description | +| -------------- | ---------------------------------------------------------------- | ----------- | +| `pathContext*` | _{ title: string; loadedStore: StoriesStore; storyId: string; }_ | | diff --git a/integrations/gatsby-theme-stories/package.json b/integrations/gatsby-theme-stories/package.json index a4b365df9..6349ed33f 100644 --- a/integrations/gatsby-theme-stories/package.json +++ b/integrations/gatsby-theme-stories/package.json @@ -36,6 +36,7 @@ }, "license": "MIT", "dependencies": { + "@component-controls/app-components": "^1.1.0", "@component-controls/components": "^1.1.0", "@component-controls/blocks": "^1.1.0", "@component-controls/loader": "^1.1.0", diff --git a/integrations/gatsby-theme-stories/src/components/ColorMode.tsx b/integrations/gatsby-theme-stories/src/components/ColorMode.tsx deleted file mode 100644 index 5345f8f1b..000000000 --- a/integrations/gatsby-theme-stories/src/components/ColorMode.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/** @jsx jsx */ -import { jsx } from 'theme-ui'; - -interface Props { - isDark: boolean; - toggle: (e: any) => void; -} - -// Adapted from: https://codepen.io/aaroniker/pen/KGpXZo and https://github.com/narative/gatsby-theme-novela/blob/714b6209c5bd61b220370e8a7ad84c0b1407946a/%40narative/gatsby-theme-novela/src/components/Navigation/Navigation.Header.tsx - -export const ColorMode = ({ isDark, toggle }: Props) => ( - -); diff --git a/integrations/gatsby-theme-stories/src/components/Header.tsx b/integrations/gatsby-theme-stories/src/components/Header.tsx index d18256ed4..a5db433c3 100644 --- a/integrations/gatsby-theme-stories/src/components/Header.tsx +++ b/integrations/gatsby-theme-stories/src/components/Header.tsx @@ -1,41 +1,43 @@ /** @jsx jsx */ -import { FC } from 'react'; -import { jsx, useColorMode, Heading } from 'theme-ui'; +import { FC, useContext } from 'react'; +import { jsx, Heading } from 'theme-ui'; import { Flex } from '@theme-ui/components'; -import { ColorMode } from './ColorMode'; +import { ColorMode, SidebarContext } from '@component-controls/app-components'; interface HeaderProps { title?: string; } export const Header: FC = ({ title }) => { - const [colorMode, setColorMode] = useColorMode(); - const isDark = colorMode === `dark`; - const toggleColorMode = (e: any) => { - e.preventDefault(); - setColorMode(isDark ? `light` : `dark`); - }; + const { SidebarToggle } = useContext(SidebarContext); return ( -
- - {title} - - -
+ - links -
+ + + {title} + + + links + + +
); }; diff --git a/integrations/gatsby-theme-stories/src/components/Layout.tsx b/integrations/gatsby-theme-stories/src/components/Layout.tsx index ecb9a1eac..41acd58fe 100644 --- a/integrations/gatsby-theme-stories/src/components/Layout.tsx +++ b/integrations/gatsby-theme-stories/src/components/Layout.tsx @@ -1,8 +1,10 @@ /** @jsx jsx */ import React, { FC } from 'react'; -import { jsx, Container } from 'theme-ui'; +import { jsx, Flex, Container } from 'theme-ui'; import { Global } from '@emotion/core'; import { ThemeProvider } from '@component-controls/components'; +import { SidebarContextProvider } from '@component-controls/app-components'; + import { PageContainer } from '@component-controls/blocks'; import { Store } from '@component-controls/store'; import { SEO } from './SEO'; @@ -35,21 +37,17 @@ export const Layout: FC = ({ })} /> -
- - -
- - {children} - - -
+ + + + +
+ + {children} + + + + ); }; diff --git a/integrations/gatsby-theme-stories/src/components/Sidebar.tsx b/integrations/gatsby-theme-stories/src/components/Sidebar.tsx index 11934ada8..f05bda3dc 100644 --- a/integrations/gatsby-theme-stories/src/components/Sidebar.tsx +++ b/integrations/gatsby-theme-stories/src/components/Sidebar.tsx @@ -1,19 +1,67 @@ /** @jsx jsx */ -import { Sidenav } from '@theme-ui/sidenav'; -import { jsx, useColorMode } from 'theme-ui'; -import { graphql, useStaticQuery } from 'gatsby'; +import { FC, useState, useMemo } from 'react'; +import { jsx, Input, LinkProps } from 'theme-ui'; +import { graphql, useStaticQuery, Link as GatsbyLink } from 'gatsby'; import { Story } from '@component-controls/specification'; +import { + Sidebar as AppSidebar, + Navmenu, + MenuItems, + MenuItem, +} from '@component-controls/app-components'; + import { useSiteMetadata } from '../hooks/use-site-metadata'; -import { ColorMode } from './ColorMode'; -export const Sidebar = () => { - const { siteTitle } = useSiteMetadata(); - const [colorMode, setColorMode] = useColorMode(); - const isDark = colorMode === `dark`; - const toggleColorMode = (e: any) => { - e.preventDefault(); - setColorMode(isDark ? `light` : `dark`); +const Link: FC = props => ( + //@ts-ignore + +); +export interface SidebarProps { + storyId?: string; +} + +interface ConsolidateKinds { + [kind: string]: Story[]; +} + +const createMenuItem = ( + levels: string[], + parent?: MenuItems, + item?: MenuItem, +): MenuItem => { + if (levels.length < 1) { + return item || {}; + } + const newItem: MenuItem = { + id: levels[0], + label: levels[0], }; + const sibling = parent && parent.find(i => i.id === newItem.id); + if (parent && !sibling) { + newItem.items = []; + parent.push(newItem); + } + return createMenuItem( + levels.slice(1), + sibling ? sibling.items : newItem.items, + newItem, + ); +}; +export const Sidebar: FC = ({ storyId }) => { + const { siteTitle } = useSiteMetadata(); const data = useStaticQuery(graphql` query { @@ -29,48 +77,46 @@ export const Sidebar = () => { } `); const stories = data.allStory.edges; + + const menuItems = useMemo(() => { + const kinds: ConsolidateKinds = stories.reduce( + (acc: ConsolidateKinds, { node }: { node: Required }) => { + if (acc[node.kind]) { + return { ...acc, [node.kind]: [...acc[node.kind], node] }; + } + return { ...acc, [node.kind]: [node] }; + }, + {}, + ); + const menuItems = Object.keys(kinds).reduce((acc: MenuItems, key) => { + const stories = kinds[key]; + const levels = key.split('/'); + const kind = createMenuItem(levels, acc); + kind.items = stories.map(story => ({ + id: story.id, + label: story.name, + to: `/stories/${story.id}`, + })); + return acc; + }, []); + return menuItems; + }, [stories]); + + const [search, setSearch] = useState(undefined); return ( -
-
- {siteTitle} -
- - li': { fontWeight: `bold`, a: { px: 0 } }, - 'ul > li > ul > li': { paddingLeft: 3 }, - p: 3, - pt: 2, - overflowY: [`auto`, `auto`, `initial`], - width: `initial`, - }} - > - {stories.map(({ node: story }: { node: Story }) => ( - //@ts-ignore -
  • - {story.name} -
  • - ))} -
    -
    + + {siteTitle} + setSearch(e.target.value)} + /> + + ); }; diff --git a/integrations/gatsby-theme-stories/src/gatsby-node.ts b/integrations/gatsby-theme-stories/src/gatsby-node.ts index 5fce2a105..a0b05906e 100644 --- a/integrations/gatsby-theme-stories/src/gatsby-node.ts +++ b/integrations/gatsby-theme-stories/src/gatsby-node.ts @@ -80,7 +80,7 @@ exports.createPages = async ({ graphql, actions }: CreatePagesArgs) => { path: `stories/${node.id}`, component: require.resolve(`../src/templates/StoryPage.tsx`), context: { - title: node.title, + title: node.name, storyId: node.id, loadedStore, }, diff --git a/integrations/gatsby-theme-stories/tsconfig.json b/integrations/gatsby-theme-stories/tsconfig.json index 13309c708..91f043601 100644 --- a/integrations/gatsby-theme-stories/tsconfig.json +++ b/integrations/gatsby-theme-stories/tsconfig.json @@ -10,6 +10,6 @@ "baseUrl": "./", "typeRoots": ["../../node_modules/@types", "node_modules/@types"] }, - "include": ["src/**/*"], + "include": ["src/**/*", "../../ui/app-components/src/Sidebar/Sidebox.tsx"], "exclude": ["node_modules/**"] } \ No newline at end of file diff --git a/integrations/storybook/README.md b/integrations/storybook/README.md index f9ee19a89..0aadc66ee 100644 --- a/integrations/storybook/README.md +++ b/integrations/storybook/README.md @@ -661,7 +661,7 @@ _StorySource [source code](https:/github.com/ccontrols/component-controls/tree/m | Name | Type | Description | | ------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `viewStype` | _ViewStyle_ | initial view mode | +| `viewStyle` | _ViewStyle_ | initial view mode | | `id` | _string_ | id of the story optional id to be used for the block if no id is provided, one will be calculated automatically from the title. | | `name` | _string_ | alternatively you can use the name of a story to load from an external file | | `title` | _string_ | optional section title for the block. | @@ -712,14 +712,9 @@ _PageContextContainer [source code](https:/github.com/ccontrols/component-contro ### properties -| Name | Type | Description | -| ------------ | ----------------------- | -------------------------------------------------------------------------------------- | -| `storyId` | _string_ | story to display in the page | -| `dark` | _boolean_ | dark/light theme for the page | -| `options` | _any_ | global options passed from container those are global parameters as well as decorators | -| `components` | _MDXProviderComponents_ | components to customize the markdown display. | -| `theme` | _Theme_ | optional custom theme | -| `store` | _StoryStore_ | store object | +| Name | Type | Description | +| ------- | ------- | ----------- | +| `theme` | _Theme_ | | ## DocsContainer @@ -727,14 +722,9 @@ _DocsContainer [source code](https:/github.com/ccontrols/component-controls/tree ### properties -| Name | Type | Description | -| ------------ | ----------------------- | -------------------------------------------------------------------------------------- | -| `storyId` | _string_ | story to display in the page | -| `dark` | _boolean_ | dark/light theme for the page | -| `options` | _any_ | global options passed from container those are global parameters as well as decorators | -| `components` | _MDXProviderComponents_ | components to customize the markdown display. | -| `theme` | _Theme_ | optional custom theme | -| `store` | _StoryStore_ | store object | -| `active` | _boolean_ | | +| Name | Type | Description | +| -------- | --------- | ----------- | +| `theme` | _Theme_ | | +| `active` | _boolean_ | | diff --git a/plugins/axe-plugin/README.md b/plugins/axe-plugin/README.md index ae9c09690..9a24355e4 100644 --- a/plugins/axe-plugin/README.md +++ b/plugins/axe-plugin/README.md @@ -12,6 +12,7 @@ - [AxeAllyBlock](#insaxeallyblockins) - [isSelected](#insisselectedins) - [isTagSelected](#insistagselectedins) + - [overview](#insoverviewins) # In action @@ -141,4 +142,8 @@ _isSelected [source code](https:/github.com/ccontrols/component-controls/tree/ma _isTagSelected [source code](https:/github.com/ccontrols/component-controls/tree/master/plugins/axe-plugin/src/components/RecoilContext.tsx)_ +## overview + +_overview [source code](https:/github.com/ccontrols/component-controls/tree/master/plugins/axe-plugin/src/stories/AllyBlock.stories.tsx)_ + diff --git a/ui/app-components/.babelrc b/ui/app-components/.babelrc new file mode 100644 index 000000000..c64480144 --- /dev/null +++ b/ui/app-components/.babelrc @@ -0,0 +1,13 @@ +{ + "presets": [ + "@babel/preset-typescript", + [ + "@babel/preset-env", + { + "targets": { + "node": "current" + } + } + ] + ] +} \ No newline at end of file diff --git a/ui/app-components/.eslintignore b/ui/app-components/.eslintignore new file mode 100644 index 000000000..53c37a166 --- /dev/null +++ b/ui/app-components/.eslintignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/ui/app-components/LICENSE.md b/ui/app-components/LICENSE.md new file mode 100644 index 000000000..a64cf9401 --- /dev/null +++ b/ui/app-components/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Atanas Stoyanov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/ui/app-components/README.md b/ui/app-components/README.md new file mode 100644 index 000000000..6ecf55d33 --- /dev/null +++ b/ui/app-components/README.md @@ -0,0 +1,97 @@ +# Table of contents + +- [Overview](#overview) +- [List of components](#list-of-components) + - [ColorMode](#inscolormodeins) + - [Keyboard](#inskeyboardins) + - [Navmenu](#insnavmenuins) + - [Sidebar](#inssidebarins) + - [SidebarContextProvider](#inssidebarcontextproviderins) + +# Overview + +Application components to create standaline user interface for component-controls. + +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. + +# List of components + + + + + +## ColorMode + +dark/light mode toggle for theme-ui themes + +_ColorMode [source code](https:/github.com/ccontrols/component-controls/tree/master/ui/app-components/src/ColorMode/ColorMode.tsx)_ + +### properties + +| Name | Type | Description | +| ------- | --------------------- | --------------------------------------------------- | +| `label` | _string_ | optional label to be displayed alongside the toggle | +| `ref` | _Ref<ReactSwitch>_ | obtain a ref target | + +## Keyboard + +Componet to monitor keystrokes. Can attach to child, document or window. + +_Keyboard [source code](https:/github.com/ccontrols/component-controls/tree/master/ui/app-components/src/Keyboard/Keyboard.tsx)_ + +### properties + +| Name | Type | Description | +| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | +| `keys*` | _number\[]_ | array of the keys to be trapped | +| `target` | _"children" \| "document" \| "window"_ | to where to attach the event handler | +| `onKeyDown*` | _KeyboardCallback_ | callbal on key down | +| `children` | _string \| number \| boolean \| {} \| ReactElement<any, string \| ((props: any) => ReactElement<any, string \| ... \| (new (props: any) => Component<any, any, any>)>) \| (new (props: any) => Component<...>)> \| ReactNodeArray \| ReactPortal \| ReactElement<...>_ | child element to the key event handler will be attached to if target = 'children' | + +## Navmenu + +Hierarchical collapsible menu + +_Navmenu [source code](https:/github.com/ccontrols/component-controls/tree/master/ui/app-components/src/Navmenu/Navmenu.tsx)_ + +### properties + +| Name | Type | Description | +| ------------- | ---------------------------------- | -------------------------------------------------------------- | +| `items*` | _MenuItems_ | Array of menu items | +| `activeItem` | _{ id?: string; label?: string; }_ | Initially active menu item | +| `buttonClass` | _any_ | Custom class to use for the button instead of Button | +| `expandAll` | _boolean_ | If specified, will expand all items with chidren | +| `onSelect` | _(item?: MenuItem) => void_ | Function that will be called when the user selects a menu item | +| `search` | _string_ | If specified, will filter the items by the search terms | + +## Sidebar + +Collapsible side bar component + +_Sidebar [source code](https:/github.com/ccontrols/component-controls/tree/master/ui/app-components/src/Sidebar/Sidebar.tsx)_ + +### properties + +| Name | Type | Description | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | +| `title` | _any_ | Title string or any react node | +| `width` | _number_ | The width of the side bar in pixels | +| `collapsible` | _boolean_ | Whether the sidebar can be collapsed | +| `children` | _any_ | children content elements to be displayed in Sidebar | +| `animate` | _Pick<CollapsibleProps, "slot" \| "style" \| "title" \| "defaultChecked" \| "defaultValue" \| "suppressContentEditableWarning" \| "suppressHydrationWarning" \| "accessKey" \| ... 254 more ... \| "easing">_ | collapsible animate height props | + +## SidebarContextProvider + +_SidebarContextProvider [source code](https:/github.com/ccontrols/component-controls/tree/master/ui/app-components/src/Sidebar/SidebarContext.tsx)_ + +### properties + +| Name | Type | Description | +| ------------- | --------- | ----------- | +| `collapsible` | _boolean_ | | + + diff --git a/ui/app-components/package.json b/ui/app-components/package.json new file mode 100644 index 000000000..1257429b0 --- /dev/null +++ b/ui/app-components/package.json @@ -0,0 +1,60 @@ +{ + "name": "@component-controls/app-components", + "version": "1.2.0", + "description": "Standalone application components.", + "main": "dist/index.js", + "module": "dist/index.esm.js", + "typings": "dist/index.d.ts", + "files": [ + "dist/", + "package.json", + "README.md" + ], + "scripts": { + "build": "yarn cross-env NODE_ENV=production rollup -c", + "dev": "yarn cross-env NODE_ENV=development rollup -cw", + "docs": "ts-node -O '{\"module\": \"commonjs\"}' ../../scripts/docs.ts", + "fix": "yarn lint --fix", + "lint": "yarn eslint . --ext mdx,ts,tsx", + "prepare": "yarn build", + "test": "yarn jest --passWithNoTests" + }, + "homepage": "https://github.com/ccontrols/component-controls", + "bugs": { + "url": "https://github.com/ccontrols/component-controls/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/ccontrols/component-controls.git", + "directory": "ui/app-components" + }, + "license": "MIT", + "dependencies": { + "@component-controls/components": "^1.2.0", + "@primer/octicons-react": "^9.6.0", + "react": "^16.8.3", + "react-dom": "^16.8.3", + "theme-ui": "^0.3.1", + "@theme-ui/match-media": "^0.3.1" + }, + "devDependencies": { + "@types/jest": "^25.1.2", + "@types/theme-ui": "^0.3.0", + "cross-env": "^5.2.1", + "eslint": "^6.5.1", + "jest": "^24.9.0" + }, + "peerDependencies": { + "@primer/octicons-react": "*", + "react": "*", + "react-dom": "*", + "theme-ui": "*" + }, + "publishConfig": { + "access": "public" + }, + "authors": [ + "Atanas Stoyanov" + ], + "gitHead": "c5145d66c6b8a355839e53c3bca97fd361ce9377" +} diff --git a/ui/app-components/rollup.config.js b/ui/app-components/rollup.config.js new file mode 100644 index 000000000..5bf5a8b41 --- /dev/null +++ b/ui/app-components/rollup.config.js @@ -0,0 +1,5 @@ +import { config } from '../../rollup-config'; + +export default config({ + input: './src/index.ts', +}); diff --git a/ui/app-components/src/ColorMode/ColorMode.stories.tsx b/ui/app-components/src/ColorMode/ColorMode.stories.tsx new file mode 100644 index 000000000..d386ba64c --- /dev/null +++ b/ui/app-components/src/ColorMode/ColorMode.stories.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { ThemeProvider } from '@component-controls/components'; +import { ColorMode } from '.'; + +export default { + title: 'App components/ColorMode', + component: ColorMode, +}; + +export const overview = () => ( + + + +); diff --git a/ui/app-components/src/ColorMode/ColorMode.tsx b/ui/app-components/src/ColorMode/ColorMode.tsx new file mode 100644 index 000000000..591057827 --- /dev/null +++ b/ui/app-components/src/ColorMode/ColorMode.tsx @@ -0,0 +1,73 @@ +/** @jsx jsx */ +import { jsx, useColorMode, Flex } from 'theme-ui'; +import { FC } from 'react'; +import Octicon, { + PrimitiveDot, + PrimitiveDotStroke, +} from '@primer/octicons-react'; +import { Toggle, ToggleProps } from '@component-controls/components'; + +/** + * dark/light mode toggle for theme-ui themes + */ +export const ColorMode: FC = props => { + const [colorMode, setColorMode] = useColorMode(); + const isDark = colorMode === `dark`; + const toggleColorMode = (checked: boolean) => { + setColorMode(checked ? `dark` : `light`); + }; + + return ( + + + + + + } + checkedIcon={ + + + + + + } + checked={isDark} + onChange={toggleColorMode} + onColor="#333" + offColor="#ddd" + {...props} + /> + ); +}; diff --git a/ui/app-components/src/ColorMode/index.ts b/ui/app-components/src/ColorMode/index.ts new file mode 100644 index 000000000..6d64c2112 --- /dev/null +++ b/ui/app-components/src/ColorMode/index.ts @@ -0,0 +1 @@ +export * from './ColorMode'; diff --git a/ui/app-components/src/Keyboard/Keyboard.tsx b/ui/app-components/src/Keyboard/Keyboard.tsx new file mode 100644 index 000000000..d2dc02af4 --- /dev/null +++ b/ui/app-components/src/Keyboard/Keyboard.tsx @@ -0,0 +1,78 @@ +import { + cloneElement, + FC, + useCallback, + useEffect, + Children, + ReactElement, +} from 'react'; + +type KeyboardCallback = (key: number) => void; + +export interface KeyboardProps { + /** + * array of the keys to be trapped + */ + keys: number[]; + /** + * to where to attach the event handler + */ + target?: 'children' | 'document' | 'window'; + /** + * callbal on key down + */ + onKeyDown: KeyboardCallback; + /** + * child element to the key event handler will be attached to if target = 'children' + */ + children?: ReactElement; +} + +export const LEFT_ARROW = 37; +export const UP_ARROW = 38; +export const RIGHT_ARROW = 39; +export const DOWN_ARROW = 40; + +/** + * Componet to monitor keystrokes. Can attach to child, document or window. + */ +export const Keyboard: FC = ({ + target = 'children', + keys, + onKeyDown, + children, +}) => { + const onKeyDownFn = useCallback( + (event: KeyboardEvent) => { + const key = event.keyCode ? event.keyCode : event.which; + if (keys.includes(key)) { + event.preventDefault(); + onKeyDown(key); + } + }, + [keys, onKeyDown], + ); + + useEffect(() => { + if (target === 'document') { + document.addEventListener('keydown', onKeyDownFn); + } else if (target === 'window') { + window.addEventListener('keydown', onKeyDownFn); + } + + return () => { + if (target === 'document') { + document.removeEventListener('keydown', onKeyDownFn); + } else if (target === 'window') { + window.removeEventListener('keydown', onKeyDownFn); + } + }; + }, [onKeyDownFn, target]); + if (target === 'children' && children) { + return cloneElement(Children.only(children), { + onKeyDown: onKeyDownFn, + }); + } + + return children || null; +}; diff --git a/ui/app-components/src/Keyboard/index.ts b/ui/app-components/src/Keyboard/index.ts new file mode 100644 index 000000000..b60858865 --- /dev/null +++ b/ui/app-components/src/Keyboard/index.ts @@ -0,0 +1 @@ +export * from './Keyboard'; diff --git a/ui/app-components/src/Navmenu/Navmenu.stories.tsx b/ui/app-components/src/Navmenu/Navmenu.stories.tsx new file mode 100644 index 000000000..9f8a09723 --- /dev/null +++ b/ui/app-components/src/Navmenu/Navmenu.stories.tsx @@ -0,0 +1,144 @@ +import React from 'react'; +import { Box, Badge, Input } from 'theme-ui'; +import Octicon, { + Inbox, + Mail, + Flame, + Star, + File, + Tag, + Trashcan, +} from '@primer/octicons-react'; +import { Navmenu } from '.'; + +export default { + title: 'App components/Navmenu', + component: Navmenu, +}; + +export const overview = () => ( + + console.log('onClick'), + label: 'F: drive', + }, + ], + }, + { + id: 'cloud', + label: 'Cloud', + items: [ + { id: 'drop_box', href: '/drop_box', label: 'DropBox' }, + { + id: 'google_drive', + onClick: () => console.log('onClick'), + label: 'Google drive', + }, + ], + }, + ]} + /> + +); + +const navItems = [ + { + id: 'inbox', + label: 'Inbox', + icon: , + expanded: true, + items: [ + { + id: 'all', + href: '/inbox/all', + label: 'All', + widget: 10, + }, + { + id: 'gmail', + href: '/inbox/gmail', + label: 'GMail', + widget: 8, + }, + { + id: 'work', + href: '/inbox/work', + label: 'Work', + widget: 1, + }, + { + id: 'amazon', + href: '/inbox/amazon', + label: 'Amazon', + widget: 1, + }, + ], + }, + { + id: 'sent', + href: '/inbox/sent', + label: 'Sent', + icon: , + }, + { + id: 'flagged', + href: '/inbox/flagged', + label: 'Flagged', + icon: , + widget: 3, + }, + { + id: 'starred', + href: '/inbox/starred', + label: 'Starred', + icon: , + }, + { + id: 'drafts', + href: '/inbox/drafts', + label: 'Drafts', + icon: , + }, + { + id: 'tagged', + href: '/inbox/tagged', + label: 'Tagged', + icon: , + }, + { + id: 'trash', + href: '/inbox/trash', + label: 'Trash', + icon: , + }, +]; + +export const items = () => ( + + + +); + +export const search = () => { + const [search, setSearch] = React.useState(undefined); + + return ( + + setSearch(e.target.value)} + /> + + + ); +}; diff --git a/ui/app-components/src/Navmenu/Navmenu.tsx b/ui/app-components/src/Navmenu/Navmenu.tsx new file mode 100644 index 000000000..4fda31575 --- /dev/null +++ b/ui/app-components/src/Navmenu/Navmenu.tsx @@ -0,0 +1,349 @@ +/** @jsx jsx */ +import React, { FC, useEffect, useState } from 'react'; +import { jsx, Box, Flex, Button, ButtonProps, LinkProps, Text } from 'theme-ui'; +import Octicon, { PlusSmall, Dash } from '@primer/octicons-react'; +import { + Keyboard, + LEFT_ARROW, + UP_ARROW, + RIGHT_ARROW, + DOWN_ARROW, +} from '../Keyboard'; + +export interface MenuItem { + /** Unique id */ + id?: string; + /** Label of the item */ + label?: React.ReactNode; + /** Initial expanded state */ + expanded?: boolean; + /** Icon in front of the label */ + icon?: React.ReactNode; + /** Widget or icon to place at the end of the item */ + widget?: React.ReactNode; + /** Array of child/sub-menu items */ + items?: MenuItems; + /** href parameter for anchor type child elements */ + href?: string; + /** event handler onClick */ + onClick?: (id: string) => void; +} + +export type MenuItems = MenuItem[]; + +export type ButtonClassType = + | React.FunctionComponent + | React.FunctionComponent; + +export interface NavMenuProps { + /** Array of menu items */ + items: MenuItems; + /** Initially active menu item */ + activeItem?: { + id?: string; + label?: string; + }; + /** Custom class to use for the button instead of Button */ + buttonClass?: ButtonClassType; + + /** If specified, will expand all items with chidren */ + expandAll?: boolean; + + /** Function that will be called when the user selects a menu item */ + onSelect?: (item?: MenuItem) => void; + + /** 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 hasActiveChidlren = (active: MenuItem, item: MenuItem): boolean => { + if (isActive(active, item)) { + return true; + } + return item.items + ? item.items.some(t => hasActiveChidlren(active, t)) + : false; +}; + +const getExpandedItems = (children: MenuItems, active?: MenuItem): MenuItems => + children.reduce((expandedItems: MenuItems, item: MenuItem) => { + const { items, expanded } = item; + if (expanded || (active && hasActiveChidlren(active, item))) { + expandedItems.push(item); + } + if (items) { + return expandedItems.concat(getExpandedItems(items, active)); + } + return expandedItems; + }, []); + +const getCollapsibleItems = (children: MenuItems): MenuItems => + children.reduce((collapsibleItems: MenuItems, item: MenuItem) => { + const { items } = item; + let childrenCollapsibleItems: MenuItems = []; + if (items) { + collapsibleItems.push(item); + + childrenCollapsibleItems = getCollapsibleItems(items); + } + return collapsibleItems.concat(childrenCollapsibleItems); + }, []); + +const getFlatChildrenIds = (children?: MenuItems): MenuItems => + children + ? children.reduce((flatChildren: MenuItems, item) => { + flatChildren.push(item); + if (item.items) { + // eslint-disable-next-line no-param-reassign + flatChildren = flatChildren.concat(getFlatChildrenIds(item.items)); + } + return flatChildren; + }, []) + : []; + +const getChildrenById = ( + children?: MenuItems, + id?: string, +): MenuItems | undefined => { + if (!children || id) { + return undefined; + } + + let items: MenuItems | undefined; + children.some(item => { + if (item.id === id || item.label === id) { + ({ items } = item); + return true; + } + if (item.items) { + items = getChildrenById(item.items, id); + + if (items) { + return true; + } + } + return false; + }); + return items; +}; + +const filterItems = (items: MenuItems, search?: string): MenuItems => { + if (search && search.length) { + const searchLC = search.toLowerCase(); + return items + .map(item => Object.assign({}, item)) + .filter(item => { + const { items: children, label } = item; + if ( + typeof label === 'string' && + label.toLowerCase().indexOf(searchLC) >= 0 + ) { + return true; + } + if (children) { + const childItems = filterItems(children, search); + // eslint-disable-next-line no-param-reassign + item.items = childItems; + if (childItems.length) { + return true; + } + } + return false; + }); + } + return items; +}; + +interface NavMenuState { + expandedItems?: MenuItems; + originalExpandAll?: boolean; + search?: string; + items?: MenuItems; + filteredItems?: MenuItems; + collapsibleItems?: MenuItems; + allExpanded?: boolean; + expandAll?: boolean; +} +/** + * 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, + items, + filteredItems, + search, + collapsibleItems, + allExpanded, + expandAll, + originalExpandAll: expandAll, + }; + }; + + const [state, setState] = useState(stateFromProps(props)); + + useEffect(() => { + setState( + stateFromProps({ + items: props.items, + expandAll: props.expandAll, + activeItem: props.activeItem, + search: props.search, + }), + ); + }, [props.items, props.expandAll, props.activeItem, props.search]); + + const onMenuChange = (item: MenuItem, expanded: boolean) => { + const { expandedItems, filteredItems } = state; + + let newExpandedItems = [...(expandedItems || [])]; + if (expanded) { + const id: string = + item.id || (typeof item.label === 'string' ? item.label : ''); + const toBeCollapsed = [ + id, + ...getFlatChildrenIds(getChildrenById(filteredItems, id)), + ]; + newExpandedItems = newExpandedItems.filter( + item => + toBeCollapsed.indexOf( + item.id || (typeof item.label === 'string' ? item.label : ''), + ) < 0, + ); + } else { + newExpandedItems.push(item); + } + + setState({ + ...state, + expandedItems: newExpandedItems, + }); + }; + + 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; + const itemId = id || label; + const isExpanded: boolean = + expandedItems && itemId ? expandedItems.includes(item) : false; + const ButtonClass: ButtonClassType = + (items ? Button : buttonClass) || Button; + const itemKey = `item_${itemId}_${level}`; + + let background; + if (activeItem && activeItem.id === id) { + background = 'active'; + } + + const content = ( + + { + if (items) { + onMenuChange(item, isExpanded); + } else if (typeof onSelect === 'function') { + onSelect(item); + } + }} + {...rest} + > + + {items && ( + + )} + + + {icon && {icon}} + {typeof label === 'string' ? ( + + {items ? {label} : label} + + ) : ( + label + )} + + + {widget && {widget}} + + + + + ); + return ( + + {items ? ( + + onMenuChange(item, [DOWN_ARROW, RIGHT_ARROW].includes(key)) + } + > + {content} + + ) : ( + content + )} + {items && + isExpanded && + items.map(child => renderItem(child, level + 1))} + + ); + }; + const { filteredItems } = state; + return ( + + {filteredItems && filteredItems.map(item => renderItem(item, 1))} + + ); +}; diff --git a/ui/app-components/src/Navmenu/index.ts b/ui/app-components/src/Navmenu/index.ts new file mode 100644 index 000000000..1f6eee808 --- /dev/null +++ b/ui/app-components/src/Navmenu/index.ts @@ -0,0 +1 @@ +export * from './Navmenu'; diff --git a/ui/app-components/src/Sidebar/Sidebar.stories.tsx b/ui/app-components/src/Sidebar/Sidebar.stories.tsx new file mode 100644 index 000000000..71e7768b6 --- /dev/null +++ b/ui/app-components/src/Sidebar/Sidebar.stories.tsx @@ -0,0 +1,123 @@ +import React from 'react'; +import Octicon, { Project } from '@primer/octicons-react'; +import { Flex } from 'theme-ui'; +import { + ThemeProvider, + CollapsibleProps, +} from '@component-controls/components'; +import { Sidebar, SidebarContext, SidebarContextProvider } from '.'; + +export default { + title: 'App components/Sidebar', + component: Sidebar, +}; + +export const overview = ({ + collapsible, + width, + easing, +}: { + collapsible: boolean; + width: number; + easing: CollapsibleProps['easing']; +}) => ( + + + + {({ SidebarToggle }) => ( + + + +
      +
    • item 1
    • +
    • item 2
    • +
    • item 3
    • +
    +
    +
    + )} +
    +
    +
    +); + +overview.story = { + controls: { + collapsible: { type: 'boolean', value: true }, + width: { type: 'number', value: undefined }, + easing: { + type: 'options', + options: ['ease', 'linear', 'ease-in', 'ease-out', 'ease-in-out'], + }, + }, +}; + +export const title = () => ( + + +
    + + {({ SidebarToggle }) => ( + + + + +
      +
    • item 1
    • +
    • item 2
    • +
    • item 3
    • +
    +
    +
    + )} +
    +
    +
    +
    +); + +export const icon = () => ( + + +
    + + {({ SidebarToggle }) => ( + + } /> + +
      +
    • item 1
    • +
    • item 2
    • +
    • item 3
    • +
    +
    +
    + )} +
    +
    +
    +
    +); + +export const buttonProps = () => ( + + +
    + + {({ SidebarToggle }) => ( + + + +
      +
    • item 1
    • +
    • item 2
    • +
    • item 3
    • +
    +
    +
    + )} +
    +
    +
    +
    +); diff --git a/ui/app-components/src/Sidebar/Sidebar.tsx b/ui/app-components/src/Sidebar/Sidebar.tsx new file mode 100644 index 000000000..b172526df --- /dev/null +++ b/ui/app-components/src/Sidebar/Sidebar.tsx @@ -0,0 +1,72 @@ +/** @jsx jsx */ +import React, { FC, Fragment, 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'; + +export interface SidebarProps { + /** + * Title string or any react node + */ + title?: React.ReactNode; + + /** The width of the side bar in pixels */ + width?: number; + + /** + * Whether the sidebar can be collapsed + */ + collapsible?: boolean; + + /** + children content elements to be displayed in Sidebar + */ + children: React.ReactNode; + + /** + * collapsible animate height props + */ + animate?: Omit; +} + +/** + * Collapsible side bar component + */ +export const Sidebar: FC = ({ + title, + width, + children, + animate, + ...rest +}) => { + const toggleContext = useContext(SidebarContext); + const { collapsed, collapsible = true } = toggleContext || {}; + const size: number = useBreakpointIndex(); + const isCollapsed = + (collapsible && size <= 1 && collapsed === undefined) || collapsed === true; + return ( + + + + + {title && ( + + {typeof title === 'string' ? ( + + {title} + + ) : ( + title + )} + + )} + + + {children} + + + + ); +}; diff --git a/ui/app-components/src/Sidebar/SidebarContext.tsx b/ui/app-components/src/Sidebar/SidebarContext.tsx new file mode 100644 index 000000000..2404764ea --- /dev/null +++ b/ui/app-components/src/Sidebar/SidebarContext.tsx @@ -0,0 +1,53 @@ +import React, { FC, createContext } from 'react'; +import { Button, ButtonProps } from 'theme-ui'; +import Octicon, { ThreeBars } from '@primer/octicons-react'; + +export type SidebarToggleProps = { + icon?: React.ReactNode; +} & ButtonProps; + +export interface SidebarContextProps { + SidebarToggle: FC; + collapsed?: boolean; + collapsible?: boolean; + setCollapsed: (value: boolean) => void; +} +export const SidebarContext = createContext({ + SidebarToggle: () => null, + setCollapsed: () => {}, +}); +export interface SidebarContextProviderProps { + collapsible?: boolean; +} +export const SidebarContextProvider: FC = ({ + children, + collapsible = true, +}) => { + const [collapsed, setCollapsed] = React.useState( + undefined, + ); + const SidebarToggle: FC = ({ icon, ...rest }) => { + console.log(collapsible); + return collapsible ? ( + + ) : null; + }; + return ( + + {children} + + ); +}; diff --git a/ui/app-components/src/Sidebar/index.ts b/ui/app-components/src/Sidebar/index.ts new file mode 100644 index 000000000..e9a228462 --- /dev/null +++ b/ui/app-components/src/Sidebar/index.ts @@ -0,0 +1,2 @@ +export * from './Sidebar'; +export * from './SidebarContext'; diff --git a/ui/app-components/src/index.ts b/ui/app-components/src/index.ts new file mode 100644 index 000000000..2f5f7ba4a --- /dev/null +++ b/ui/app-components/src/index.ts @@ -0,0 +1,4 @@ +export * from './ColorMode'; +export * from './Keyboard'; +export * from './Navmenu'; +export * from './Sidebar'; diff --git a/ui/app-components/src/typings.d.ts b/ui/app-components/src/typings.d.ts new file mode 100644 index 000000000..d4ceab75b --- /dev/null +++ b/ui/app-components/src/typings.d.ts @@ -0,0 +1 @@ +declare module '@theme-ui/match-media'; diff --git a/ui/app-components/tsconfig.json b/ui/app-components/tsconfig.json new file mode 100644 index 000000000..e69184809 --- /dev/null +++ b/ui/app-components/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "esnext", + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "types": ["node", "jest"], + "typeRoots": ["../../node_modules/@types", "node_modules/@types"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules/**"] +} \ No newline at end of file diff --git a/ui/blocks/README.md b/ui/blocks/README.md index a50586bfd..2e1a1e55d 100644 --- a/ui/blocks/README.md +++ b/ui/blocks/README.md @@ -37,7 +37,7 @@ Some of the guiding design goals for this library: # List of components - + @@ -172,12 +172,10 @@ _PageContainer [source code](https:/github.com/ccontrols/component-controls/tree | Name | Type | Description | | ------------ | ----------------------- | -------------------------------------------------------------------------------------- | +| `store*` | _StoryStore_ | store object | | `storyId` | _string_ | story to display in the page | -| `dark` | _boolean_ | dark/light theme for the page | | `options` | _any_ | global options passed from container those are global parameters as well as decorators | | `components` | _MDXProviderComponents_ | components to customize the markdown display. | -| `theme` | _Theme_ | optional custom theme | -| `store` | _StoryStore_ | store object | ## Playground @@ -326,7 +324,7 @@ _StorySource [source code](https:/github.com/ccontrols/component-controls/tree/m | Name | Type | Description | | ------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `viewStype` | _ViewStyle_ | initial view mode | +| `viewStyle` | _ViewStyle_ | initial view mode | | `id` | _string_ | id of the story optional id to be used for the block if no id is provided, one will be calculated automatically from the title. | | `name` | _string_ | alternatively you can use the name of a story to load from an external file | | `title` | _string_ | optional section title for the block. | diff --git a/ui/blocks/src/PageContainer/PageContainer.tsx b/ui/blocks/src/PageContainer/PageContainer.tsx index 4c8997433..77a0fc085 100644 --- a/ui/blocks/src/PageContainer/PageContainer.tsx +++ b/ui/blocks/src/PageContainer/PageContainer.tsx @@ -73,6 +73,7 @@ export const PageContainer: FC = ({ return ( = ({ return applyColorMode( { ...defTheme, + defaultBreakpoints: [40, 52, 64].map(n => n + 'em'), initialColorModeName: dark ? 'dark' : 'default', forms: { checkbox: { diff --git a/yarn.lock b/yarn.lock index 5785a1a4f..06b895756 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3871,6 +3871,11 @@ resolved "https://registry.yarnpkg.com/@theme-ui/css/-/css-0.3.1.tgz#b85c7e8fae948dc0de65aa30b853368993e25cb3" integrity sha512-QB2/fZBpo4inaLHL3OrB8NOBgNfwnj8GtHzXWHb9iQSRjmtNX8zPXBe32jLT7qQP0+y8JxPT4YChZIkm5ZyIdg== +"@theme-ui/match-media@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@theme-ui/match-media/-/match-media-0.3.1.tgz#05af6a71cf14368e1b3bd7180fc382c72d5ba53b" + integrity sha512-PHvSRB1vqUgDnPkGlXLa+qadmOMOy3LKSOzovwpTi+wzCUyyOGAsUY/fJQ7nufBrmU3vdYeUTrKplLn5VIEmlg== + "@theme-ui/mdx@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@theme-ui/mdx/-/mdx-0.3.0.tgz#8bb1342204acfaa69914d6b6567c5c49d9a8c1e6"