From 34f4a2cbeb60b8562631f9c3677fbe5b27e2ff6b Mon Sep 17 00:00:00 2001 From: Tilman Frick Date: Sun, 16 Sep 2018 08:23:35 +0200 Subject: [PATCH] feat: improve user experience (#607) * feat: update styling of bug report button * fix: remove non functioning menu items * feat: rename builtin component library * feat: restructure project settings * feat: add empty state for element pane * fix: make preview pane non-selectable * fix: remove unneeded element list scroll area * feat: add export button * fix: restore element list spacing * fix: simplify code * fix: restore user selection * fix: remove unneeded text property --- src/components/add-button/demo.tsx | 11 ++++ .../{add-page-button => add-button}/index.tsx | 34 ++++++---- src/components/add-button/pattern.json | 8 +++ src/components/add-page-button/demo.tsx | 11 ---- src/components/add-page-button/pattern.json | 8 --- src/components/bug-report/demo.tsx | 11 ---- src/components/bug-report/index.tsx | 27 -------- src/components/bug-report/pattern.json | 8 --- src/components/button-group/index.tsx | 4 ++ src/components/chrome-button/demo.tsx | 8 +++ src/components/chrome-button/index.tsx | 40 ++++++++++++ src/components/chrome-button/pattern.json | 8 +++ src/components/copy/index.tsx | 1 + src/components/empty-state/demo.tsx | 8 +++ src/components/empty-state/index.tsx | 30 +++++++++ src/components/empty-state/pattern.json | 8 +++ src/components/headline/index.tsx | 1 + src/components/icons/index.tsx | 44 +++++++++++++ src/components/index.ts | 5 +- src/components/link/index.tsx | 9 ++- src/components/panes/preview-pane/index.tsx | 1 + src/components/property-box/index.tsx | 2 +- src/container/chrome/chrome-container.tsx | 47 ++++++++++---- src/container/element-list/element-list.tsx | 10 +++ src/container/library-settings-container.tsx | 6 -- .../page-list/page-list-container.tsx | 7 +- src/container/project-settings-container.tsx | 37 ++++++++++- .../create-main-menu/create-file-menu.ts | 64 ++++--------------- src/model/project.ts | 4 +- 29 files changed, 303 insertions(+), 159 deletions(-) create mode 100644 src/components/add-button/demo.tsx rename src/components/{add-page-button => add-button}/index.tsx (52%) create mode 100644 src/components/add-button/pattern.json delete mode 100644 src/components/add-page-button/demo.tsx delete mode 100644 src/components/add-page-button/pattern.json delete mode 100644 src/components/bug-report/demo.tsx delete mode 100644 src/components/bug-report/index.tsx delete mode 100644 src/components/bug-report/pattern.json create mode 100644 src/components/chrome-button/demo.tsx create mode 100644 src/components/chrome-button/index.tsx create mode 100644 src/components/chrome-button/pattern.json create mode 100644 src/components/empty-state/demo.tsx create mode 100644 src/components/empty-state/index.tsx create mode 100644 src/components/empty-state/pattern.json diff --git a/src/components/add-button/demo.tsx b/src/components/add-button/demo.tsx new file mode 100644 index 000000000..31c56b4b1 --- /dev/null +++ b/src/components/add-button/demo.tsx @@ -0,0 +1,11 @@ +import DemoContainer from '../demo-container'; +import * as React from 'react'; +import { AddButton } from '.'; + +const AddButtonDemo: React.StatelessComponent = (): JSX.Element => ( + + Add Page + +); + +export default AddButtonDemo; diff --git a/src/components/add-page-button/index.tsx b/src/components/add-button/index.tsx similarity index 52% rename from src/components/add-page-button/index.tsx rename to src/components/add-button/index.tsx index f49954abd..031da4cd4 100644 --- a/src/components/add-page-button/index.tsx +++ b/src/components/add-button/index.tsx @@ -3,34 +3,42 @@ import { Copy } from '../copy'; import { IconSize } from '../icons'; import * as React from 'react'; import { Plus } from 'react-feather'; -import { getSpace, SpaceSize } from '../space'; +import { Space, getSpace, SpaceSize } from '../space'; import styled from 'styled-components'; -export interface AddPageButtonProps { +export interface AddButtonProps { onClick?: React.MouseEventHandler; + margin?: boolean; } -const StyledAddPageButton = styled.button` +const StyledSpace = styled(Space)` + width: 100%; +`; + +const StyledAddButton = styled.button` position: relative; box-sizing: border-box; height: 60px; width: 100%; - border: 1px solid ${Color.Grey80}; + border: 1px solid ${Color.Grey90}; border-radius: 6px; background-color: transparent; - margin: ${getSpace(SpaceSize.XS)}px; display: flex; align-items: center; justify-content: center; transition: border 0.2s; user-select: none; + &:hover { + border: 1px solid ${Color.Grey60}; + } + &:focus { outline: none; } - &:hover { - border: 1px solid ${Color.Grey60}; + &:active { + background-color: ${Color.Grey90}; } `; @@ -38,9 +46,11 @@ const StyledIcon = styled(Plus)` margin-right: ${getSpace(SpaceSize.XS)}px; `; -export const AddPageButton: React.SFC = props => ( - - - Add Page - +export const AddButton: React.SFC = props => ( + + + + {props.children} + + ); diff --git a/src/components/add-button/pattern.json b/src/components/add-button/pattern.json new file mode 100644 index 000000000..98523d5a6 --- /dev/null +++ b/src/components/add-button/pattern.json @@ -0,0 +1,8 @@ +{ + "name": "add-button", + "description": "Button to add a page or library", + "displayName": "Add Button", + "version": "1.0.0", + "tags": ["atom", "button"], + "flag": "alpha" +} diff --git a/src/components/add-page-button/demo.tsx b/src/components/add-page-button/demo.tsx deleted file mode 100644 index 0fbdfeeca..000000000 --- a/src/components/add-page-button/demo.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import DemoContainer from '../demo-container'; -import * as React from 'react'; -import { AddPageButton } from '.'; - -const AddPageButtonDemo: React.StatelessComponent = (): JSX.Element => ( - - - -); - -export default AddPageButtonDemo; diff --git a/src/components/add-page-button/pattern.json b/src/components/add-page-button/pattern.json deleted file mode 100644 index 704647c0a..000000000 --- a/src/components/add-page-button/pattern.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "add-page-button", - "description": "Button to add pages", - "displayName": "Add Page Button", - "version": "1.0.0", - "tags": ["atom", "button"], - "flag": "alpha" -} diff --git a/src/components/bug-report/demo.tsx b/src/components/bug-report/demo.tsx deleted file mode 100644 index 2204060df..000000000 --- a/src/components/bug-report/demo.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import DemoContainer from '../demo-container'; -import { BugReport } from './index'; -import * as React from 'react'; - -const BugReportDemo: React.StatelessComponent = (): JSX.Element => ( - - - -); - -export default BugReportDemo; diff --git a/src/components/bug-report/index.tsx b/src/components/bug-report/index.tsx deleted file mode 100644 index 77fd80dec..000000000 --- a/src/components/bug-report/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Button, ButtonOrder, ButtonSize } from '../button'; -import * as React from 'react'; -import styled from 'styled-components'; - -export interface BugReportProps { - onClick?: React.MouseEventHandler; - onDoubleClick?: React.MouseEventHandler; - title: string; -} - -const StyledBugReport = styled.div` - justify-self: right; - -webkit-app-region: no-drag; -`; - -export const BugReport: React.StatelessComponent = props => ( - - - -); diff --git a/src/components/bug-report/pattern.json b/src/components/bug-report/pattern.json deleted file mode 100644 index 82ad98124..000000000 --- a/src/components/bug-report/pattern.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "bug-report", - "displayName": "Bug Report", - "description": "Button to report a bug as Github Issue", - "version": "1.0.0", - "tags": ["bug", "bug-report"], - "flag": "alpha" -} diff --git a/src/components/button-group/index.tsx b/src/components/button-group/index.tsx index 5c40a0978..ed62f6154 100644 --- a/src/components/button-group/index.tsx +++ b/src/components/button-group/index.tsx @@ -10,6 +10,10 @@ const StyledButtonGroup = styled.div` margin-top: ${getSpace(SpaceSize.XS)}px; border-top: 1px solid ${Color.Grey90}; + &:active { + background: ${Color.Grey97}; + } + @media screen and (-webkit-min-device-pixel-ratio: 2) { border-top-width: 0.5px; } diff --git a/src/components/chrome-button/demo.tsx b/src/components/chrome-button/demo.tsx new file mode 100644 index 000000000..cec2ad90d --- /dev/null +++ b/src/components/chrome-button/demo.tsx @@ -0,0 +1,8 @@ +import { ChromeButton } from './index'; +import * as React from 'react'; + +const ChromeButtonDemo: React.StatelessComponent = (): JSX.Element => ( + +); + +export default ChromeButtonDemo; diff --git a/src/components/chrome-button/index.tsx b/src/components/chrome-button/index.tsx new file mode 100644 index 000000000..98cd91053 --- /dev/null +++ b/src/components/chrome-button/index.tsx @@ -0,0 +1,40 @@ +import * as React from 'react'; +import styled from 'styled-components'; +import { Color } from '../colors'; +import { getSpace, SpaceSize } from '../space'; + +export interface ChromeButtonProps { + onClick?: React.MouseEventHandler; + onDoubleClick?: React.MouseEventHandler; + title: string; + icon?: React.ReactNode; +} + +const StyledChromeButton = styled.div` + display: flex; + padding: 0 ${getSpace(SpaceSize.S)}px; + align-items: center; + -webkit-app-region: no-drag; + box-sizing: border-box; + border-radius: 3px; + margin-left: ${getSpace(SpaceSize.S)}px; + background: linear-gradient(to bottom, ${Color.White} 0%, ${Color.Grey97}); + height: 21px; + box-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.1), 0 0.5px 2px 0 rgba(0, 0, 0, 0.3); + color: ${Color.Grey50}; + + &:active { + background: ${Color.Grey90}; + } +`; + +const StyledIcon = styled.div` + margin-right: 6px; +`; + +export const ChromeButton: React.StatelessComponent = props => ( + + {props.icon && {props.icon}} +
{props.title}
+
+); diff --git a/src/components/chrome-button/pattern.json b/src/components/chrome-button/pattern.json new file mode 100644 index 000000000..64839b0e2 --- /dev/null +++ b/src/components/chrome-button/pattern.json @@ -0,0 +1,8 @@ +{ + "name": "chrome-button", + "displayName": "Chrome Button", + "description": "Button in the chrome", + "version": "1.0.0", + "tags": ["chrome-button"], + "flag": "alpha" +} diff --git a/src/components/copy/index.tsx b/src/components/copy/index.tsx index 1c2d3a8f7..7d4b4b336 100644 --- a/src/components/copy/index.tsx +++ b/src/components/copy/index.tsx @@ -21,6 +21,7 @@ export enum CopySize { const StyledCopy = styled.p` margin: 0; line-height: 1.5; + cursor: default; ${(props: StyledCopyProps) => typeof props.size !== 'undefined' ? `font-size: ${props.size}px;` : 'font-size: 12px'}; ${(props: StyledCopyProps) => diff --git a/src/components/empty-state/demo.tsx b/src/components/empty-state/demo.tsx new file mode 100644 index 000000000..349a81125 --- /dev/null +++ b/src/components/empty-state/demo.tsx @@ -0,0 +1,8 @@ +import { EmptyState } from './index'; +import * as React from 'react'; + +const EmptyStateDemo: React.StatelessComponent = (): JSX.Element => ( + +); + +export default EmptyStateDemo; diff --git a/src/components/empty-state/index.tsx b/src/components/empty-state/index.tsx new file mode 100644 index 000000000..2b38e4ab3 --- /dev/null +++ b/src/components/empty-state/index.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import styled from 'styled-components'; +import { Color } from '../colors'; +import { Space, getSpace, SpaceSize } from '../space'; +import { Headline } from '../headline'; +import { Copy } from '../copy'; + +export interface EmptyStateProps { + headline: string; + copy: string; + image?: React.ReactNode; + highlighted?: boolean; +} + +const StyledEmptyState = styled.div` + text-align: center; + padding: ${getSpace(SpaceSize.XXXL)}px; +`; + +export const EmptyState: React.StatelessComponent = props => ( + + + {props.headline} + + + {props.copy} + + {props.image} + +); diff --git a/src/components/empty-state/pattern.json b/src/components/empty-state/pattern.json new file mode 100644 index 000000000..d1af9d6ab --- /dev/null +++ b/src/components/empty-state/pattern.json @@ -0,0 +1,8 @@ +{ + "name": "empty-state", + "displayName": "Empty State", + "description": "Explanation of todos in case of an empty state", + "version": "1.0.0", + "tags": ["empty-state"], + "flag": "alpha" +} diff --git a/src/components/headline/index.tsx b/src/components/headline/index.tsx index 4257c6690..7e29c95cd 100644 --- a/src/components/headline/index.tsx +++ b/src/components/headline/index.tsx @@ -21,6 +21,7 @@ const StyledHeadline = styled.div` margin-top: 0; font-family: ${fonts().NORMAL_FONT}; font-weight: 500; + cursor: default; ${(props: StyledHeadlineProps) => (props.textColor ? `color: ${props.textColor};` : '')}; ${(props: HeadlineProps) => { diff --git a/src/components/icons/index.tsx b/src/components/icons/index.tsx index d7dee0991..b08194c6a 100644 --- a/src/components/icons/index.tsx +++ b/src/components/icons/index.tsx @@ -87,6 +87,50 @@ const icons: { readonly [key: string]: JSX.Element[][] | JSX.Element[] } = { ] }; +export const Images: { readonly [key: string]: JSX.Element[][] | JSX.Element[] } = { + EmptyElements: [ + [ + + + + + + + + + + + + + + + + + + + + ] + ] +}; + const StyledIconRegistry = styled.svg` display: none; `; diff --git a/src/components/index.ts b/src/components/index.ts index ae01ce0b4..c850de8c3 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,8 +1,8 @@ -export * from './add-page-button'; -export * from './bug-report'; +export * from './add-button'; export * from './button'; export * from './button-group'; export * from './chrome'; +export * from './chrome-button'; export * from './colors'; export * from './copy'; export * from './create-select'; @@ -10,6 +10,7 @@ export * from './drag-area'; export * from './editable-title'; export * from './element'; export * from './element-slot'; +export * from './empty-state'; export * from './fonts'; export * from './global-styles'; export * from './headline'; diff --git a/src/components/link/index.tsx b/src/components/link/index.tsx index 49b54a567..72f489725 100644 --- a/src/components/link/index.tsx +++ b/src/components/link/index.tsx @@ -10,14 +10,16 @@ export interface LinkProps { disabled?: boolean; onClick?: React.MouseEventHandler; uppercase?: boolean; + underline?: boolean; } const StyledLink: StyledComponentClass = styled.a` font-family: ${fonts().NORMAL_FONT}; ${(props: LinkProps) => (props.color ? `color: ${props.color}` : 'color: inherit')}; - ${(props: LinkProps) => (props.onClick ? 'cursor: pointer;' : 'cursor: auto;')} ${( - props: LinkProps - ) => (props.uppercase ? 'text-transform: uppercase;' : '')}; + ${(props: LinkProps) => + props.underline ? 'text-decoration: underline' : 'text-decoration: none'}; + ${(props: LinkProps) => (props.onClick ? 'cursor: pointer' : 'cursor: auto')}; + ${(props: LinkProps) => (props.uppercase ? 'text-transform: uppercase' : '')}; `; export const Link: React.StatelessComponent = props => ( @@ -27,6 +29,7 @@ export const Link: React.StatelessComponent = props => ( disabled={props.disabled} onClick={props.onClick} uppercase={props.uppercase} + underline={props.underline} > {props.children} diff --git a/src/components/panes/preview-pane/index.tsx b/src/components/panes/preview-pane/index.tsx index 758be8ad2..fe1a45255 100644 --- a/src/components/panes/preview-pane/index.tsx +++ b/src/components/panes/preview-pane/index.tsx @@ -7,6 +7,7 @@ const StyledPreviewWrapper = styled.div` justify-content: center; flex-grow: 1; flex-shrink: 0; + user-select: none; `; const StyledPreviewPane = styled.div` diff --git a/src/components/property-box/index.tsx b/src/components/property-box/index.tsx index 6f07e2c84..a44aa06e4 100644 --- a/src/components/property-box/index.tsx +++ b/src/components/property-box/index.tsx @@ -20,10 +20,10 @@ const StyledPropertyBox = styled.div` user-select: none; cursor: default; margin-bottom: ${getSpace(SpaceSize.S)}px; + overflow: hidden; `; const StyledPropertyBoxBar = styled.div` - height: ${getSpace(SpaceSize.M) * 2}px; margin-top: ${getSpace(SpaceSize.M)}px; margin-right: -${getSpace(SpaceSize.M)}px; margin-bottom: -${getSpace(SpaceSize.M)}px; diff --git a/src/container/chrome/chrome-container.tsx b/src/container/chrome/chrome-container.tsx index e3409ef83..98a7c9969 100644 --- a/src/container/chrome/chrome-container.tsx +++ b/src/container/chrome/chrome-container.tsx @@ -1,12 +1,13 @@ import * as AlvaUtil from '../../alva-util'; import { ChromeSwitch } from './chrome-switch'; -import { BugReport, Chrome, CopySize, ViewSwitch } from '../../components'; +import { ChromeButton, Chrome, CopySize, IconSize, ViewSwitch } from '../../components'; import { MessageType } from '../../message'; import * as MobxReact from 'mobx-react'; import { Page } from '../../model'; import * as React from 'react'; import { ViewStore } from '../../store'; import * as uuid from 'uuid'; +import { LogOut } from 'react-feather'; export interface InjectedChromeContainerProps { page: Page; @@ -57,19 +58,37 @@ export const ChromeContainer = MobxReact.inject('store')( > {page.getName()} - { - props.store.getSender().send({ - type: MessageType.OpenExternalURL, - id: uuid.v4(), - payload: 'https://github.com/meetalva/alva/labels/type%3A%20bug' - }); - }} - onDoubleClick={event => { - event.stopPropagation(); - }} - /> +
+ { + props.store.getSender().send({ + type: MessageType.OpenExternalURL, + id: uuid.v4(), + payload: 'https://github.com/meetalva/alva/labels/type%3A%20bug' + }); + }} + onDoubleClick={event => { + event.stopPropagation(); + }} + /> + + } + onClick={() => { + props.store.getSender().send({ + id: uuid.v4(), + type: MessageType.ExportHtmlProject, + payload: { path: undefined } + }); + }} + onDoubleClick={event => { + event.stopPropagation(); + }} + /> +
{props.children} ); diff --git a/src/container/element-list/element-list.tsx b/src/container/element-list/element-list.tsx index bf90d0de1..064cb1f1d 100644 --- a/src/container/element-list/element-list.tsx +++ b/src/container/element-list/element-list.tsx @@ -10,6 +10,8 @@ import * as Store from '../../store'; import * as Types from '../../types'; import * as utils from '../../alva-util'; +import { Images } from '../../components/icons'; + @MobxReact.inject('store') @MobxReact.observer export class ElementList extends React.Component { @@ -384,6 +386,7 @@ export class ElementList extends React.Component { return null; } const childContent = rootElement.getContentBySlotType(Types.SlotType.Children); + const hasChildren = childContent && childContent.getElements().length > 0; return ( {childContent ? : null} + {!hasChildren && ( + + )} - - - Details - - <> {mayUpdate && ( diff --git a/src/container/page-list/page-list-container.tsx b/src/container/page-list/page-list-container.tsx index 592be75b0..afba4f145 100644 --- a/src/container/page-list/page-list-container.tsx +++ b/src/container/page-list/page-list-container.tsx @@ -115,7 +115,8 @@ export class PageListContainer extends React.Component { page={page} /> ))} - store.getSender().send({ id: uuid.v4(), @@ -123,7 +124,9 @@ export class PageListContainer extends React.Component { type: MessageType.CreateNewPage }) } - /> + > + Add Page + ); diff --git a/src/container/project-settings-container.tsx b/src/container/project-settings-container.tsx index 88a392f1a..61ee54e5a 100644 --- a/src/container/project-settings-container.tsx +++ b/src/container/project-settings-container.tsx @@ -3,6 +3,10 @@ import * as MobxReact from 'mobx-react'; import * as React from 'react'; import { WithStore } from '../store'; import { LibrarySettingsContainer } from './library-settings-container'; +import { SpaceSize } from '../components/space'; +import { Color } from '../components/colors'; +import { MessageType } from '../message'; +import * as uuid from 'uuid'; @MobxReact.inject('store') @MobxReact.observer @@ -12,12 +16,43 @@ export class ProjectSettingsContainer extends React.Component { return (
- Connected Libraries + + Connected Libraries + + You can connect one or multiple React component libraries. + + + + + store.getSender().send({ + type: MessageType.OpenExternalURL, + id: uuid.v4(), + payload: 'https://github.com/meetalva/alva#pattern-requirements' + }) + } + > + See Library Requirements + + {store .getPatternLibraries() .map(library => ( ))} + + store.getSender().send({ + id: uuid.v4(), + payload: { library: undefined }, + type: MessageType.ConnectPatternLibraryRequest + }) + } + > + Connect Library +
); } diff --git a/src/electron/create-main-menu/create-file-menu.ts b/src/electron/create-main-menu/create-file-menu.ts index c68c99f38..18e5695f3 100644 --- a/src/electron/create-main-menu/create-file-menu.ts +++ b/src/electron/create-main-menu/create-file-menu.ts @@ -74,58 +74,20 @@ export function createFileMenu( type: 'separator' }, { - label: '&Export', - submenu: [ - { - label: 'Export Page as Sketch', - enabled: Boolean(activePage), - click: async () => { - if (!activePage) { - return; - } - - injection.sender.send({ - id: uuid.v4(), - type: MessageType.ExportSketchPage, - payload: { path: undefined } - }); - } - }, - { - label: 'Export Page as PNG', - enabled: Boolean(activePage), - click: async () => { - if (!activePage) { - return; - } - - injection.sender.send({ - id: uuid.v4(), - type: MessageType.ExportPngPage, - payload: { path: undefined } - }); - } - }, - { - type: 'separator' - }, - { - label: 'Export Project as HTML', - enabled: Boolean(activePage), - accelerator: 'CmdOrCtrl+Shift+E', - click: async () => { - if (!ctx.project) { - return; - } - - injection.sender.send({ - id: uuid.v4(), - type: MessageType.ExportHtmlProject, - payload: { path: undefined } - }); - } + label: 'Export Prototype as HTML', + enabled: Boolean(activePage), + accelerator: 'CmdOrCtrl+E', + click: async () => { + if (!ctx.project) { + return; } - ] + + injection.sender.send({ + id: uuid.v4(), + type: MessageType.ExportHtmlProject, + payload: { path: undefined } + }); + } }, { type: 'separator', diff --git a/src/model/project.ts b/src/model/project.ts index 776129cdd..6d8bbafbf 100644 --- a/src/model/project.ts +++ b/src/model/project.ts @@ -180,9 +180,9 @@ export class Project { { bundle: '', bundleId: '', - description: 'Basic building blocks available to every new Alva project', + description: 'Built-in components for basic layouts and logic', id: uuid.v4(), - name: 'Built-In Components', + name: 'Essentials', origin: Types.PatternLibraryOrigin.BuiltIn, patternProperties: [], patterns: [],