Skip to content

Commit

Permalink
ds-material new base components for GenericList #178; store action ty…
Browse files Browse the repository at this point in the history
…pings #163

and normalize/optimize the GenericList widget html-structure and a bit of styling, very small sizing/gutter adjustments and better support for e,g. footer
  • Loading branch information
elbakerino committed Mar 23, 2022
1 parent 3284b0e commit babf3e7
Show file tree
Hide file tree
Showing 32 changed files with 671 additions and 267 deletions.
10 changes: 10 additions & 0 deletions packages/demo/src/schemas/demoLists.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ const schemaLists = createOrderedMap({
'end_check': true,
},
],
listActionLabels: {
en: {
add: 'New event',
remove: 'Remove event',
},
de: {
add: 'Neue Veranstaltung',
remove: 'Lösche Veranstaltung',
},
},
view: {
sizeXs: 12,
sizeMd: 12,
Expand Down
10 changes: 8 additions & 2 deletions packages/dictionary/src/de/labels.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ export const labels = {
'add-entry': 'Neuer Eintrag',
'entry': 'Eintrag',
'remove-entry': 'Entferne Eintrag',
'add-item': 'Neues Element',
'remove-item': 'Lösche Element',
'add-item': (context, locale) =>
context?.getIn(['actionLabels', locale, 'add']) ?
context?.getIn(['actionLabels', locale, 'add']) :
'Neues Element',
'remove-item': (context, locale) =>
context?.getIn(['actionLabels', locale, 'remove']) ?
context?.getIn(['actionLabels', locale, 'remove']) :
'Lösche Element',
'add-row': 'Neue Zeile',
'remove-row': 'Lösche Zeile',
'remove-rows': 'Lösche Zeilen',
Expand Down
10 changes: 8 additions & 2 deletions packages/dictionary/src/en/labels.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ export const labels = {
'add-entry': 'Add entry',
'entry': 'Entry',
'remove-entry': 'Remove entry',
'add-item': 'Add item',
'remove-item': 'Remove item',
'add-item': (context, locale) =>
context?.getIn(['actionLabels', locale, 'add']) ?
context?.getIn(['actionLabels', locale, 'add']) :
'Add item',
'remove-item': (context, locale) =>
context?.getIn(['actionLabels', locale, 'remove']) ?
context?.getIn(['actionLabels', locale, 'remove']) :
'Remove item',
'add-row': 'Add row',
'remove-row': 'Remove row',
'remove-rows': 'Remove rows',
Expand Down
6 changes: 6 additions & 0 deletions packages/docs/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>UI-Schema Documentation</title>
<style>
#main-content ul > li ul {
margin-top: 4px !important;
margin-bottom: 4px !important;
}
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
7 changes: 6 additions & 1 deletion packages/docs/src/component/Markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,18 @@ renderers.p = p => <Typography {...p} component={'p'} variant={'body2'} gutterBo
renderers.code = ({inline, ...p}) => inline ? <MdInlineCode variant={'body1'} {...p}/> : <Code variant={'body2'} {...p}/>;
const MarkdownH = ({level, ...p}) => <Typography {...p} component={'h' + (level + 1)} variant={'subtitle' + (level)} style={{textDecoration: 'underline', marginTop: 48 / level}} gutterBottom/>
renderers.h1 = renderers.h2 = renderers.h3 = renderers.h4 = renderers.h5 = renderers.h6 = MarkdownH
renderers.li = p => <Typography component={'li'} variant={'body2'} style={{fontWeight: 'bold'}}><span style={{fontWeight: 'normal', display: 'block'}}>{p.children}</span></Typography>;
renderers.li = p => <Typography component={'li'} variant={'body2'} style={{fontWeight: 'bold'}}>
<span style={{fontWeight: 'normal', display: 'block', marginBottom: 2}}>{p.children}</span>
</Typography>;
renderers.a = LinkInternalLocale;

const renderersContent = baseRenderers(false);
renderersContent.code = ({inline, ...p}) => inline ? <MdInlineCode variant={'body1'} {...p}/> : <Code variant={'body1'} {...p}/>;
renderersContent.h1 = renderersContent.h2 = renderersContent.h3 = renderersContent.h4 = renderersContent.h5 = renderersContent.h6 = LinkableHeadline;
renderersContent.a = LinkInternalLocale;
renderersContent.li = p => <Typography component={'li'} variant={'body1'} style={{fontWeight: 'bold'}}>
<span style={{fontWeight: 'normal', display: 'block', marginBottom: 2}}>{p.children}</span>
</Typography>;

const rehypePlugins = [rehypeRaw]
const remarkPlugins = [remarkGfm]
Expand Down
1 change: 1 addition & 0 deletions packages/docs/src/content/docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export const routesFurtherDesignSystem = [
},
routes: [
createDoc('ds-material/Table', 'Table', ''),
createDoc('ds-material/GenericList', 'GenericList', ''),
],
},
],
Expand Down
129 changes: 129 additions & 0 deletions packages/docs/src/content/docs/ds-material/GenericList.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# DS Material GenericList

Base components for the `GenericList` widget, to easily configure and re-wire the widget parts, see also the [widget docs here](/docs/widgets/GenericList).

```typescript jsx
import React from 'react'
import { List } from 'immutable'
import { memo } from '@ui-schema/ui-schema/Utils/memo'
import { WidgetProps } from '@ui-schema/ui-schema/Widget'
import { useUIStore, WithOnChange } from '@ui-schema/ui-schema/UIStore'
import {
GenericListContent, GenericListFooter,
GenericListItem,
GenericListItemMore, GenericListItemPos,
} from '@ui-schema/ds-material/BaseComponents/GenericList'
import { MuiWidgetBinding } from '@ui-schema/ds-material/widgetsBinding'

// it is important to use `memo` from `@ui-schema/ui-schema` for the content component,
// as the generic list will re-render on each change of anything in the store,
// with passing down `listSize` and not other data, the `GenericListContent` will only re-render when the `listSize` changes
export const GenericListContentMemo = memo(GenericListContent)

export const GenericList = (props: WidgetProps<MuiWidgetBinding> & WithOnChange): React.ReactElement => {
const {store} = useUIStore()
// info: `store?.extractValues` is new since `0.3.0-alpha.11` and can be used instead of the `extractValue` HOC
const {value} = store?.extractValues<List<any>>(props.storeKeys) || {}
// extracting and calculating the list size here, not passing down the actual list for performance reasons
// https://github.com/ui-schema/ui-schema/issues/133
return <GenericListContentMemo
{...props}
listSize={value?.size || 0}
ComponentItemPos={GenericListItemPos}
ComponentItemMore={GenericListItemMore}
ComponentItem={GenericListItem}
ComponentFooter={GenericListFooter}
// e.g. use `Button` instead of `IconButton` for `add-item`
//btnAddShowLabel
/>
}
```

Easily define own schema keywords, for options which are only supported by `props`:

```typescript jsx
export const GenericList = (props: WidgetProps<MuiWidgetBinding> & WithOnChange): React.ReactElement => {
const {store} = useUIStore()
const {value} = store?.extractValues<List<any>>(props.storeKeys) || {}
// extracting and calculating the list size here, not passing down the actual list for performance reasons
// https://github.com/ui-schema/ui-schema/issues/133
return <GenericListContentMemo
{...props}
listSize={value?.size || 0}
ComponentItemPos={GenericListItemPos}
ComponentItemMore={GenericListItemMore}
ComponentItem={GenericListItem}
ComponentFooter={GenericListFooter}
btnAddShowLabel={Boolean(schema.getIn(['view', 'btnAsLabel']))}
/>
}
```

Use the [GenericListContent.tsx](https://github.com/ui-schema/ui-schema/tree/master/packages/ds-material/src/BaseComponents/GenericList/GenericListContent.tsx) as a starting point if you need to further adjust the widget parts.

## Translation / Labels

- `labels.add-item` used by `GenericListFooter` for the add-button, supports named-labels key `add`
- `labels.remove-item` used by `GenericListItemMore` for the delete-button, supports named-labels key `remove`
- `labels.move-to-position` used by `GenericListItemPos` to render the up/down buttons, adds the context `nextIndex` to render to-which-position it will be moved
- `labels.entry` used by `GenericListItemPos` as `sr-only` for the nth-label

**Notices:**

- `sr-only` = `screen-reader-only` text `span` for a11y
- supports named-label: uses the `context` to pass down `actionLabels` for a not-generic-labelling, the given key is the intended action-key for the label

For `actionLabels` handling, see [dictionary/en/labels](https://github.com/ui-schema/ui-schema/tree/master/packages/dictionary/en/labels.js) as an example.

Example schema structure that activates `actionLabels`, with support for multiple languages and different buttons:

```json
{
"type": "object",
"widget": "GenericList",
"listActionLabels": {
"en": {
"add": "New event",
"remove": "Remove event"
},
"de": {
"add": "Neue Veranstaltung",
"remove": "Lösche Veranstaltung"
}
}
}
```

## Components

### GenericListContent

The list renderer component, uses the components passed down per `props` to build the needed list out of the `listSize` - it does not receive any data from the list.

`GenericListContentProps`:

- `btnAddShowLabel`: `boolean`, for the add-item button, when `true` displays a normal button and not only an icon-button
- `btnAddStyle`: `React.CSSProperties`, for the add-item button, custom react styles
- `ComponentItemPos`: `React.ComponentType<GenericListItemSharedProps>`, will be rendered in `ComponentItem`, contains the position and up/down buttons
- `ComponentItemMore`: `React.ComponentType<GenericListItemSharedProps>`, will be rendered in `ComponentItem`, contains the delete button
- `ComponentItem`: `React.ComponentType<GenericListItemProps>`, is rendered per item in the list, responsible to further render nested schema, also uses component props
- `ComponentFooter`: `React.ComponentType<GenericListFooterProps>`, will be rendered in `GenericListContent`, contains the add-button
- `listSize`: `number`, the size of the list
- `schemaKeys`: `StoreKeys`, experimental [#104](https://github.com/ui-schema/ui-schema/issues/104)
- `listSpacing`: `GridSpacing`, used as the spacing for the item-list

### GenericListItem

For props check typings.

### GenericListItemPos

For props check typings.

### GenericListItemMore

For props check typings.

### GenericListFooter

For props check typings.
2 changes: 1 addition & 1 deletion packages/docs/src/content/docs/ds-material/Table.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# DS Material
# DS Material Table

Base components for the `Table` widget, to build custom table widgets, check the [widget docs here](/docs/widgets/Table).

Expand Down
9 changes: 8 additions & 1 deletion packages/docs/src/content/docs/widgets/GenericList.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Widgets for complex structures in arrays.

### Material-UI

> see the [GenericList base components](https://ui-schema.bemit.codes/docs/ds-material/GenericList) for further customization and details about label translation
```js
import {GenericList} from "@ui-schema/ds-material/Widgets/GenericList";

Expand All @@ -32,8 +34,13 @@ const widgets = {
**Supports extra keywords:**

- `view`
- `btnSize` either `small` (default) or `medium`
- `btnSize` either `small` (default) or `medium`, used for the add-button
- `deleteBtnSize` either `small` (default) or `medium`, used for the delete-button
- `btnVariant` either `text | outlined | contained`, used for the add-button
- `btnVariant` either `text | outlined | contained`, used for the add-button
- `btnColor` either `inherit | primary | secondary | default`, used for the add-button
- `hideTitle` when `true` doesn't show any title
- `listActionLabels` used for [named-label translation](https://ui-schema.bemit.codes/docs/ds-material/GenericList#translation--labels)
- `notDeletable` when `true` doesn't allow deletion of items
- `notAddable` when `true` doesn't allow adding items
- `notSortable` when `true` doesn't allow sorting of items
Expand Down
9 changes: 4 additions & 5 deletions packages/docs/src/content/docs/widgets/Table.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ Widget for data tables.

[![Component Examples](https://img.shields.io/badge/Examples-green?labelColor=1d3d39&color=1a6754&logoColor=ffffff&style=flat-square&logo=plex)](#demo-ui-generator) [![supports Material-UI Binding](https://img.shields.io/badge/Material-green?labelColor=1a237e&color=0d47a1&logoColor=ffffff&style=flat-square&logo=material-ui)](#material-ui)

- type: `array(array | object)`,
- type: `array(array | object)`
- widget keywords:
- `Table` **(usable, beta)**
- `TableAdvanced` **(in-dev)**
- view
- does not support grid keywords in direct nested schemas, disables grid for own stack, but supports for nested `object`

- does not support grid keywords in direct nested schemas, disables grid for own stack, but supported inside nested `object`
- [Array Type Properties](/docs/schema#type-array)
- [Object Type Properties](/docs/schema#type-object)

Expand All @@ -26,6 +25,8 @@ Widget for data tables.
Special `Table` component for complex, always validated, lists. Using custom widgets without labels. Hidden rows from pagination are still validated, using `isVirtual` prop.

> see the [table base components](https://ui-schema.bemit.codes/docs/ds-material/Table) for further customization
**Supports extra keywords:**

- `view`
Expand Down Expand Up @@ -85,8 +86,6 @@ customWidgets.custom = {

Currently included cell components are based on the `TextField` components and support the same features - except label/title related options. The title related schema keywords are used by `Table` for the `TableHeader` cell contents.

> todo: more code sharing through utils
- `TextRendererCell` supports multi-line text
- **extra keywords:**
- `view`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react'
import FormControl from '@material-ui/core/FormControl'
import Grid from '@material-ui/core/Grid'
import FormLabel from '@material-ui/core/FormLabel'
import { TransTitle, StoreKeys, WidgetProps, WithOnChange } from '@ui-schema/ui-schema'
import { ListButtonOverwrites } from '@ui-schema/ds-material/Component/ListButton'
import { MuiWidgetBinding } from '@ui-schema/ds-material/widgetsBinding'
import { GenericListFooterProps, GenericListItemProps, GenericListItemSharedProps } from '@ui-schema/ds-material/BaseComponents'
import Box from '@material-ui/core/Box'
import { GridSpacing } from '@material-ui/core/Grid/Grid'

export interface GenericListContentProps extends ListButtonOverwrites {
btnAddShowLabel?: boolean
btnAddStyle?: React.CSSProperties
ComponentItemPos?: React.ComponentType<GenericListItemSharedProps>
ComponentItemMore?: React.ComponentType<GenericListItemSharedProps>
ComponentItem: React.ComponentType<GenericListItemProps>
ComponentFooter?: React.ComponentType<GenericListFooterProps>
listSize: number
schemaKeys?: StoreKeys
listSpacing?: GridSpacing
}

export const GenericListContent = <P extends WidgetProps<MuiWidgetBinding>>(
{
storeKeys, schemaKeys, ownKey, schema, listSize, onChange,
showValidity, valid, errors, required, level, widgets,
ComponentItemMore, ComponentItemPos,
ComponentItem, ComponentFooter,
btnAddShowLabel, btnAddStyle,
btnSize: btnSizeProp,
btnVariant: btnVariantProp,
btnColor: btnColorProp,
listSpacing = 3,
}: P & WithOnChange & GenericListContentProps,
): React.ReactElement => {
const btnSize = (schema.getIn(['view', 'btnSize']) || btnSizeProp || 'small') as ListButtonOverwrites['btnSize']
const deleteBtnSize = (schema.getIn(['view', 'deleteBtnSize']) || btnSizeProp || 'small') as ListButtonOverwrites['btnSize']
const btnVariant = (schema.getIn(['view', 'btnVariant']) || btnVariantProp || undefined) as ListButtonOverwrites['btnVariant']
const btnColor = (schema.getIn(['view', 'btnColor']) || btnColorProp || undefined) as ListButtonOverwrites['btnColor']
const notSortable = schema.get('notSortable') as boolean | undefined
const notAddable = schema.get('notAddable') as boolean | undefined
const notDeletable = schema.get('notDeletable') as boolean | undefined
const InfoRenderer = widgets?.InfoRenderer

const info = InfoRenderer && schema?.get('info') ?
<InfoRenderer
schema={schema} variant={'preview'} openAs={'embed'}
storeKeys={storeKeys} valid={valid} errors={errors}
/> : null

return <FormControl required={required} error={!valid && showValidity} component="fieldset" style={{width: '100%'}}>
{!schema.getIn(['view', 'hideTitle']) ?
<Box mb={1}>
<Box mb={1}>
<FormLabel component="legend">
<TransTitle schema={schema} storeKeys={storeKeys} ownKey={ownKey}/>
</FormLabel>
</Box>

{info}
</Box> : null}

{schema.getIn(['view', 'hideTitle']) ?
<Box mb={1}>{info}</Box> : null}

<Grid container spacing={listSpacing}>
{Array(listSize).fill(null).map((_val, i) =>
<ComponentItem
key={i} index={i} listSize={listSize}
storeKeys={storeKeys}
schemaKeys={schemaKeys}
schema={schema} onChange={onChange}
level={level}
listRequired={required}
btnSize={deleteBtnSize}
notSortable={notSortable}
notDeletable={notDeletable}
showValidity={showValidity}
ComponentPos={ComponentItemPos}
ComponentMore={ComponentItemMore}
/>,
)}
</Grid>

{ComponentFooter ?
<ComponentFooter
schema={schema}
required={required}
storeKeys={storeKeys}
onChange={onChange}
errors={errors}
showValidity={showValidity}
btnSize={btnSize}
btnAddShowLabel={btnAddShowLabel}
btnAddStyle={btnAddStyle}
btnColor={btnColor}
btnVariant={btnVariant}
notAddable={notAddable}
notSortable={notSortable}
notDeletable={notDeletable}
/> : null}
</FormControl>
}

0 comments on commit babf3e7

Please sign in to comment.