From b6fea2cc69d6b1fcac60b0ad712b30130c0cd27c Mon Sep 17 00:00:00 2001 From: Mitchell Hamilton Date: Tue, 13 Oct 2020 14:15:49 +1000 Subject: [PATCH] Misc small Admin UI new interfaces changes (#3939) * Misc small Admin UI new interfaces changes * Fix linting and arrow on popover * Fix a thing --- .../packages/core/src/components/Core.tsx | 7 +- design-system/packages/fields/src/index.ts | 2 + design-system/packages/options/package.json | 1 + design-system/packages/options/src/index.tsx | 68 ++++++++++------ .../packages/popover/src/Popover.tsx | 79 ++++++++++++++++++- design-system/website/next.config.js | 27 ++++++- .../src/components/CreateItemDrawer.tsx | 9 +++ .../src/pages/ListPage/FilterList.tsx | 15 +--- .../admin-ui/src/pages/ListPage/index.tsx | 15 +++- .../fields/src/types/text/views/index.tsx | 1 + packages-next/types/src/admin-meta.ts | 2 + 11 files changed, 176 insertions(+), 50 deletions(-) diff --git a/design-system/packages/core/src/components/Core.tsx b/design-system/packages/core/src/components/Core.tsx index b1ce4efba70..52171f728ae 100644 --- a/design-system/packages/core/src/components/Core.tsx +++ b/design-system/packages/core/src/components/Core.tsx @@ -1,7 +1,7 @@ /* @jsx jsx */ import { jsx, Global } from '../emotion'; -import { Fragment, ReactNode, memo } from 'react'; +import { Fragment, ReactNode } from 'react'; import { normalize } from '../normalize'; import { useTheme } from '../theme'; @@ -15,9 +15,6 @@ type CoreProps = { optimizeLegibility?: boolean; }; -// only re-render leaf nodes that listen to theme changes -const App = memo(props => ); - export const Core = ({ children, includeNormalize = true, @@ -26,7 +23,7 @@ export const Core = ({ return ( - {children} + {children} ); }; diff --git a/design-system/packages/fields/src/index.ts b/design-system/packages/fields/src/index.ts index b8a8baa5bf0..4275b3e29db 100644 --- a/design-system/packages/fields/src/index.ts +++ b/design-system/packages/fields/src/index.ts @@ -15,3 +15,5 @@ export { Switch } from './Switch'; export { TextArea } from './TextArea'; export { TextInput } from './TextInput'; export { SelectInput } from './SelectInput'; +export { useIndicatorTokens, useIndicatorStyles } from './hooks/indicators'; +export { useInputTokens, useInputStyles } from './hooks/inputs'; diff --git a/design-system/packages/options/package.json b/design-system/packages/options/package.json index c5fb28f7f4a..12ca60aea38 100644 --- a/design-system/packages/options/package.json +++ b/design-system/packages/options/package.json @@ -14,6 +14,7 @@ "dependencies": { "@babel/runtime": "^7.11.2", "@keystone-ui/core": "*", + "@keystone-ui/fields": "*", "@keystone-ui/icons": "*", "@types/react-select": "^3.0.22", "react-select": "^3.1.0" diff --git a/design-system/packages/options/src/index.tsx b/design-system/packages/options/src/index.tsx index ad74f7c40d8..3ab1f614077 100644 --- a/design-system/packages/options/src/index.tsx +++ b/design-system/packages/options/src/index.tsx @@ -1,5 +1,6 @@ /** @jsx jsx */ import { jsx, useTheme } from '@keystone-ui/core'; +import { useIndicatorTokens } from '@keystone-ui/fields'; import { CheckIcon } from '@keystone-ui/icons/icons/CheckIcon'; import { ComponentProps, useMemo } from 'react'; import ReactSelect, { components as reactSelectComponents, Props } from 'react-select'; @@ -13,40 +14,55 @@ export const CheckMark = ({ isFocused?: boolean; isSelected?: boolean; }) => { - let bg; - let fg; - let border; - let size = 24; - - const theme = useTheme(); - - if (isDisabled) { - bg = theme.fields.disabled.controlForeground; - fg = isSelected ? 'white' : theme.fields.disabled.controlForeground; - border = theme.fields.disabled.controlForeground; - } else if (isSelected) { - bg = isFocused ? 'white' : theme.fields.selected.controlBorderColor; - fg = isFocused ? theme.fields.selected.controlBorderColor : 'white'; - border = theme.fields.selected.controlBorderColor; - } else { - border = isFocused ? theme.fields.focus.controlBorderColor : theme.fields.controlBorderColor; - bg = 'white'; - fg = 'white'; - } + const tokens = useIndicatorTokens({ + size: 'medium', + type: 'radio', + }); return (
diff --git a/design-system/packages/popover/src/Popover.tsx b/design-system/packages/popover/src/Popover.tsx index 22fbcd41683..f0b450f90ca 100644 --- a/design-system/packages/popover/src/Popover.tsx +++ b/design-system/packages/popover/src/Popover.tsx @@ -9,6 +9,7 @@ import { useEffect, useState, useCallback, + CSSProperties, } from 'react'; import { Options, Placement } from '@popperjs/core'; import { usePopper } from 'react-popper'; @@ -113,7 +114,7 @@ type Props = { }; export const Popover = ({ placement = 'bottom', triggerRenderer, ...props }: Props) => { - const { isOpen, setOpen, trigger, dialog } = usePopover({ + const { isOpen, setOpen, trigger, dialog, arrow } = usePopover({ placement, modifiers: [ { @@ -135,7 +136,13 @@ export const Popover = ({ placement = 'bottom', triggerRenderer, ...props }: Pro onClick: () => setOpen(true), }, })} - + ); }; @@ -148,10 +155,16 @@ type DialogProps = { children: ReactNode; /** When true, the popover will be visible. */ isVisible: boolean; + arrow: { + ref: (element: HTMLDivElement) => void; + props: { + style: CSSProperties; + }; + }; }; export const PopoverDialog = forwardRef( - ({ isVisible, ...props }, consumerRef) => { + ({ isVisible, children, arrow, ...props }, consumerRef) => { const { elevation, radii, shadow, colors } = useTheme(); return ( @@ -165,9 +178,14 @@ export const PopoverDialog = forwardRef( opacity: isVisible ? 1 : 0, pointerEvents: isVisible ? undefined : 'none', zIndex: elevation.e500, // on top of drawers + ...useArrowStyles(), }} {...props} - /> + > +
+ + {children} +
); } @@ -258,3 +276,56 @@ const useKeyPress = ({ return keyPressed; }; + +const useArrowStyles = () => { + const theme = useTheme(); + const size = 16; + return { + '& [data-popper-arrow]': { + position: 'absolute', + overflow: 'hidden', + pointerEvents: 'none', + height: size * 2, + width: size * 2, + '&::after': { + content: '""', + position: 'absolute', + background: theme.colors.background, + width: size, + height: size, + transform: 'translateX(-50%) translateY(-50%) rotate(45deg)', + boxShadow: theme.shadow.s200, + }, + }, + "&[data-popper-placement^='left'] > [data-popper-arrow]": { + left: '100%', + '&::after': { + top: '50%', + left: '0', + }, + }, + "&[data-popper-placement^='right'] > [data-popper-arrow]": { + right: '100%', + '&::after': { + top: '50%', + left: '100%', + }, + }, + "&[data-popper-placement^='top'] > [data-popper-arrow]": { + top: '100%', + '&::after': { + top: 0, + bottom: '-50%', + left: '50%', + }, + }, + "&[data-popper-placement^='bottom'] > [data-popper-arrow]": { + bottom: '100%', + right: 'unset', + '&::after': { + bottom: '-50%', + left: '50%', + }, + }, + } as const; +}; diff --git a/design-system/website/next.config.js b/design-system/website/next.config.js index da9d919a518..a282ada7088 100644 --- a/design-system/website/next.config.js +++ b/design-system/website/next.config.js @@ -1,3 +1,26 @@ -const withPreconstruct = require('@preconstruct/next'); +module.exports = { + webpack(config, { isServer, defaultLoaders }) { + let hasFoundRule = false; + defaultLoaders.babel.options.rootMode = 'upward-optional'; + config.module.rules.forEach(rule => { + if ( + rule.use === defaultLoaders.babel || + (Array.isArray(rule.use) && rule.use.includes(defaultLoaders.babel)) + ) { + hasFoundRule = true; + delete rule.include; + } + }); + if (!hasFoundRule) { + throw new Error('The Next Babel loader could not be found'); + } + if (isServer) { + config.externals = ['react', 'react-dom', 'next/router']; + config.resolve.mainFields = ['browser', 'module', 'main']; + } else { + config.resolve.mainFields = ['module', 'main']; + } -module.exports = withPreconstruct(); + return config; + }, +}; diff --git a/packages-next/admin-ui/src/components/CreateItemDrawer.tsx b/packages-next/admin-ui/src/components/CreateItemDrawer.tsx index 2b1835838ac..b2ca00fd993 100644 --- a/packages-next/admin-ui/src/components/CreateItemDrawer.tsx +++ b/packages-next/admin-ui/src/components/CreateItemDrawer.tsx @@ -7,6 +7,7 @@ import isDeepEqual from 'fast-deep-equal'; import { useList } from '../context'; import { Notice } from '@keystone-ui/notice'; import { Drawer } from '@keystone-ui/modals'; +import { useToasts } from '@keystone-ui/toast'; export function CreateItemDrawer({ listKey, @@ -21,10 +22,13 @@ export function CreateItemDrawer({ }) { const list = useList(listKey); + const toasts = useToasts(); + const [createItem, { loading, error }] = useMutation( gql`mutation($data: ${list.gqlNames.createInputName}!) { item: ${list.gqlNames.createMutationName}(data: $data) { id + _label_ } }` ); @@ -81,6 +85,11 @@ export function CreateItemDrawer({ }) .then(({ data }) => { onCreate(data.item.id); + toasts.addToast({ + title: data.item._label_, + message: 'Created Successfully', + tone: 'positive', + }); }) .catch(() => {}); }, diff --git a/packages-next/admin-ui/src/pages/ListPage/FilterList.tsx b/packages-next/admin-ui/src/pages/ListPage/FilterList.tsx index c253bfea21c..b2e8cf402f8 100644 --- a/packages-next/admin-ui/src/pages/ListPage/FilterList.tsx +++ b/packages-next/admin-ui/src/pages/ListPage/FilterList.tsx @@ -5,7 +5,6 @@ import { Filter } from './useFilters'; import { useRouter } from '../../router'; import { Button } from '@keystone-ui/button'; import { usePopover, PopoverDialog } from '@keystone-ui/popover'; -import { tabbable } from 'tabbable'; import { FormEvent, Fragment, useState } from 'react'; import { Pill } from '@keystone-ui/pill'; @@ -22,7 +21,7 @@ export function FilterList({ filters, list }: { filters: Filter[]; list: ListMet function FilterPill({ filter, field }: { filter: Filter; field: FieldMeta }) { const router = useRouter(); - const { isOpen, setOpen, trigger, dialog } = usePopover({ + const { isOpen, setOpen, trigger, dialog, arrow } = usePopover({ placement: 'bottom', modifiers: [ { @@ -56,7 +55,7 @@ function FilterPill({ filter, field }: { filter: Filter; field: FieldMeta }) { }) .toLowerCase()} - + { setOpen(false); @@ -97,15 +96,7 @@ function EditDialog({ onClose(); }} > -
{ - if (node) { - tabbable(node)[0]?.focus(); - } - }} - > - -
+
diff --git a/packages-next/admin-ui/src/pages/ListPage/index.tsx b/packages-next/admin-ui/src/pages/ListPage/index.tsx index 7979acfcc8a..555ca017afd 100644 --- a/packages-next/admin-ui/src/pages/ListPage/index.tsx +++ b/packages-next/admin-ui/src/pages/ListPage/index.tsx @@ -22,6 +22,7 @@ import { FilterList } from './FilterList'; import { ListMeta } from '@keystone-spike/types'; import { AlertDialog, DrawerController } from '@keystone-ui/modals'; import { useToasts } from '@keystone-ui/toast'; +import { LoadingDots } from '@keystone-ui/loading'; type ListPageProps = { listKey: string; @@ -121,7 +122,7 @@ export const ListPage = ({ listKey }: ListPageProps) => { let selectedFields = useSelectedFields(listKey, listViewFieldModesByField); - let { data, error, refetch } = useQuery( + let { data: newData, error: newError, refetch, loading } = useQuery( useMemo(() => { let selectedGqlFields = selectedFields.fields .map(fieldPath => { @@ -157,6 +158,17 @@ export const ListPage = ({ listKey }: ListPageProps) => { } ); + let [dataState, setDataState] = useState({ data: newData, error: newError }); + + if (newData && dataState.data !== newData) { + setDataState({ + data: newData, + error: newError, + }); + } + + const { data, error } = dataState; + const dataGetter = makeDataGetter< DeepNullable<{ meta: { count: number }; @@ -237,6 +249,7 @@ export const ListPage = ({ listKey }: ListPageProps) => { listKey={listKey} fieldModesByFieldPath={listViewFieldModesByField} />{' '} + {loading && } ); })() diff --git a/packages-next/fields/src/types/text/views/index.tsx b/packages-next/fields/src/types/text/views/index.tsx index f57783df93d..98b3b7d0358 100644 --- a/packages-next/fields/src/types/text/views/index.tsx +++ b/packages-next/fields/src/types/text/views/index.tsx @@ -64,6 +64,7 @@ export const controller = ( props.onChange(event.target.value); }} value={props.value} + autoFocus /> ); }, diff --git a/packages-next/types/src/admin-meta.ts b/packages-next/types/src/admin-meta.ts index f30547b2a5e..44197c2a1bc 100644 --- a/packages-next/types/src/admin-meta.ts +++ b/packages-next/types/src/admin-meta.ts @@ -35,6 +35,7 @@ export type FieldController = type: string; value: FilterValue; onChange(value: FilterValue): void; + autoFocus?: boolean; }): ReactElement | null; }; }; @@ -105,6 +106,7 @@ export type FieldProps FieldControll field: ReturnType; value: ReturnType['deserialize']>; onChange?(value: ReturnType['deserialize']>): void; + autoFocus?: boolean; }; export type FieldViews = {