From a265c69200c0e9f720d032f8c9caa2a54c4a5659 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Wed, 7 Feb 2024 17:43:39 +1300 Subject: [PATCH] MNT Add to storybook --- .../components/LinkField/LinkField-story.js | 159 ++++++++++++++++++ .../LinkPicker/LinkPickerTitle-story.js | 110 ++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 client/src/components/LinkField/LinkField-story.js create mode 100644 client/src/components/LinkPicker/LinkPickerTitle-story.js diff --git a/client/src/components/LinkField/LinkField-story.js b/client/src/components/LinkField/LinkField-story.js new file mode 100644 index 00000000..388c2e25 --- /dev/null +++ b/client/src/components/LinkField/LinkField-story.js @@ -0,0 +1,159 @@ +/* global window */ +import React from 'react'; +import { Component as LinkField } from 'components/LinkField/LinkField'; + +// mock global ss config +if (!window.ss) { + window.ss = {}; +} +if (!window.ss.config) { + window.ss.config = { + sections: [ + { + name: 'SilverStripe\\LinkField\\Controllers\\LinkFieldController', + form: { + linkForm: { + dataUrl: '', + } + } + }, + ] + }; +} + +// mock toast actions +const mockedActions = { + toasts: { + error: () => {}, + success: () => {}, + } +}; + +// predetermine link types +const linkTypes = { + sitetree: { + key: 'sitetree', + title: 'Page on this site', + handlerName: 'FormBuilderModal', + priority: 0, + icon: 'font-icon-page', + allowed: true + }, + file: { + key: 'file', + title: 'Link to a file', + handlerName: 'FormBuilderModal', + priority: 10, + icon: 'font-icon-image', + allowed: true + }, + external: { + key: 'external', + title: 'Link to external URL', + handlerName: 'FormBuilderModal', + priority: 20, + icon: 'font-icon-external-link', + allowed: true + }, + email: { + key: 'email', + title: 'Link to email address', + handlerName: 'FormBuilderModal', + priority: 30, + icon: 'font-icon-p-mail', + allowed: true + }, + phone: { + key: 'phone', + title: 'Phone number', + handlerName: 'FormBuilderModal', + priority: 40, + icon: 'font-icon-mobile', + allowed: true + } +}; + +export default { + title: 'Linkfield/LinkField', + component: LinkField, + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: 'The LinkField component. Note that the form modal for creating, editing, and viewing link data is disabled for the storybook.' + }, + canvas: { + sourceState: 'shown', + }, + controls: { + exclude: [ + 'onChange', + 'value', + 'ownerID', + 'ownerClass', + 'ownerRelation', + 'actions', + ], + }, + }, + }, + argTypes: { + types: { + description: 'Types of links that are allowed in this field. The actual prop is a JSON object with some metadata about each type.', + control: 'inline-check', + options: Object.keys(linkTypes), + }, + isMulti: { + description: 'Whether the field supports multiple links or not.', + }, + canCreate: { + description: 'Whether the current user has create permission or not.', + }, + readonly: { + description: 'Whether the field is readonly or not.', + }, + disabled: { + description: 'Whether the field is disabled or not.', + }, + ownerSaved: { + description: 'Whether the record which owns the link field has been saved or not. The actual props for this are OwnerID, OwnerClass, and OwnerRelation.', + }, + }, +}; + +export const _LinkField = { + name: 'LinkField', + args: { + value: [0], + onChange: () => {}, + types: Object.keys(linkTypes), + actions: mockedActions, + isMulti: false, + canCreate: true, + readonly: false, + disabled: false, + ownerSaved: true, + ownerClass: '', + ownerRelation: '', + }, + render: (args) => { + const { types, ownerSaved } = args; + delete args.ownerSaved; + delete args.hasLinks; + + // `types` must be an array in args so controls can be used to toggle them. + // Because of that, we need to turn that back into the JSON object before + // passing that prop. + args.types = {}; + types.sort((a, b) => linkTypes[a].priority - linkTypes[b].priority); + // eslint-disable-next-line no-restricted-syntax + for (const type of types) { + args.types[type] = linkTypes[type]; + } + + // Determine whether the link is rendered as though the parent record is saved or not + args.ownerID = ownerSaved ? 1 : 0; + + return ; + }, +}; diff --git a/client/src/components/LinkPicker/LinkPickerTitle-story.js b/client/src/components/LinkPicker/LinkPickerTitle-story.js new file mode 100644 index 00000000..389ea35d --- /dev/null +++ b/client/src/components/LinkPicker/LinkPickerTitle-story.js @@ -0,0 +1,110 @@ +import React from 'react'; +import LinkPickerTitle from 'components/LinkPicker/LinkPickerTitle'; +import { LinkFieldContext } from '../LinkField/LinkField'; + +// mock toast actions +const mockedActions = { + toasts: { + error: () => {}, + success: () => {}, + } +}; + +export default { + title: 'LinkField/LinkPicker/LinkPickerTitle', + component: LinkPickerTitle, + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: 'The LinkPickerTitle component. Used to display a link inside the link field' + }, + canvas: { + sourceState: 'shown', + }, + controls: { + exclude: [ + 'id', + 'onDelete', + 'onClick', + 'onUnpublishedVersionedState', + 'isFirst', + 'isLast', + 'isSorting', + 'canCreate', + ], + }, + }, + }, + argTypes: { + versionState: { + description: 'The current versioned state of the link. "unsaved" and "unversioned" are effectively identical.', + control: 'select', + options: ['unversioned', 'unsaved', 'published', 'draft', 'modified'], + }, + title: { + description: 'The title (aka link text) for the link.', + }, + typeTitle: { + description: 'Text that informs the user what type of link this is.', + }, + description: { + description: 'The URL, or information about what the link is linking to.', + }, + typeIcon: { + description: 'CSS class of an icon for this type of link (usually prefixed with "font-icon-"). See the Admin/Icons story for the full set of options.', + }, + isMulti: { + description: 'Whether this link is inside a link field that supports multiple links or not.', + }, + canDelete: { + description: 'Whether the current user has the permissions necessary to delete (or archive) this link.', + }, + readonly: { + description: 'Whether the link field is readonly.', + }, + disabled: { + description: 'Whether the link field is disabled.', + }, + loading: { + description: 'Whether the link field is loading. This is passed as part of the context, not as a prop, but is here for demonstration purposes.', + }, + }, +}; + +export const _LinkPickerTitle = { + name: 'LinkPickerTitle', + args: { + id: 1, + title: 'Example link', + typeTitle: 'External URL', + description: 'https://www.example.com', + typeIcon: 'font-icon-external-link', + versionState: 'unversioned', + onDelete: () => {}, + onClick: () => {}, + onUnpublishedVersionedState: () => {}, + isFirst: true, + isLast: true, + isMulti: false, + canCreate: true, + canDelete: true, + isSorting: false, + readonly: false, + disabled: false, + loading: false, + }, + render: (args) => { + const providerArgs = { + ownerID: 1, + ownerClass: '', + ownerRelation: '', + actions: mockedActions, + loading: args.loading, + }; + delete args.loading; + return + + ; + } +};