From 6c80192a8d53f2f95640e3f7e8a7c5323e46fc1b Mon Sep 17 00:00:00 2001 From: Chris Olsen Date: Fri, 30 Jun 2023 09:55:14 -0600 Subject: [PATCH] feat: add button component page --- src/components/mock-modal/Modal.css | 6 ++ src/components/mock-modal/Modal.tsx | 16 ++++ src/components/sandbox/ComponentSerializer.ts | 17 ++-- src/components/sandbox/Sandbox.tsx | 36 +++++---- src/routes/components/Button.tsx | 77 ++++++++++++------- 5 files changed, 99 insertions(+), 53 deletions(-) create mode 100644 src/components/mock-modal/Modal.css create mode 100644 src/components/mock-modal/Modal.tsx diff --git a/src/components/mock-modal/Modal.css b/src/components/mock-modal/Modal.css new file mode 100644 index 000000000..aaa8d1435 --- /dev/null +++ b/src/components/mock-modal/Modal.css @@ -0,0 +1,6 @@ +.modal { + padding: 2rem; + border: 1px solid var(--goa-color-greyscale-700); + border-radius: 4px; + box-shadow: var(--goa-shadow-modal); +} \ No newline at end of file diff --git a/src/components/mock-modal/Modal.tsx b/src/components/mock-modal/Modal.tsx new file mode 100644 index 000000000..873ae10b7 --- /dev/null +++ b/src/components/mock-modal/Modal.tsx @@ -0,0 +1,16 @@ +import { ReactNode } from "react"; +import "./Modal.css"; + +interface Props { + children: ReactNode +}; + +export function GoAModal({ children }: Props) { + return ( +
+ {children} +
+ ) +} + +export default GoAModal; \ No newline at end of file diff --git a/src/components/sandbox/ComponentSerializer.ts b/src/components/sandbox/ComponentSerializer.ts index 79394a99a..43f829a0e 100644 --- a/src/components/sandbox/ComponentSerializer.ts +++ b/src/components/sandbox/ComponentSerializer.ts @@ -4,11 +4,11 @@ import { Serializer } from './BaseSerializer'; // https://github.com/grommet/jsx-to-string/blob/master/src/index.js interface GoAElement { - type: string | { name: string} + type: string | { name: string } } export class ComponentSerializer { - constructor(private serializer: Serializer) {} + constructor(private serializer: Serializer) { } #serializeComponent(item: ReactElement, isRoot: boolean, spacing: number): string { if (isValidElement(item)) { @@ -19,8 +19,8 @@ export class ComponentSerializer { } return ""; } - - #serializeProp(elementName: string, name: string, item: ReactElement | Object | string | number | boolean, delimit=true): string { + + #serializeProp(elementName: string, name: string, item: ReactElement | Object | string | number | boolean, delimit = true): string { if (typeof item === "string") { return this.serializer.stringToProp(name, item) } @@ -36,7 +36,7 @@ export class ComponentSerializer { if (typeof item === 'function') { return this.serializer.funcToProp(name, item) } - + if (Array.isArray(item)) { const delimiter = delimit ? ', ' : `\n `; const items: string = item.map(i => this.#serializeProp(elementName, name, i)).join(delimiter); @@ -44,7 +44,7 @@ export class ComponentSerializer { } return "" - } + } // Public componentsToString(components: ReactElement[], spacing: number = 0): string { @@ -54,7 +54,7 @@ export class ComponentSerializer { #componentToString(component: ReactElement & GoAElement, isRoot: boolean, spacing: number = 0): string { let elementType = this.serializer.componentNameToString((component.type as unknown as any).name || component.type as string); this.serializer.setIsRoot(isRoot) - this.serializer.setState({element: elementType, props: { name: component.props.name}}) + this.serializer.setState({ element: elementType, props: { name: component.props.name } }) // no props return empty component ex if (!component.props) { @@ -90,13 +90,12 @@ export class ComponentSerializer { .map((child: ReactElement) => this.#serializeComponent(child, false, spacing)) .join(`\n${indentation}`); } else { - // FIXME: should this be moved into the array handling above? children = this.#serializeComponent(component.props.children, false, spacing); } // final output return `<${elementType}${props}>\n${indentation}${children}\n${indentation.slice(0, -2)}`; - } + } return `<${elementType}${props} />`; } diff --git a/src/components/sandbox/Sandbox.tsx b/src/components/sandbox/Sandbox.tsx index be5e007ef..96ce452d8 100644 --- a/src/components/sandbox/Sandbox.tsx +++ b/src/components/sandbox/Sandbox.tsx @@ -16,7 +16,7 @@ type Flag = "reactive" interface ElementProps { properties?: ComponentBinding[]; note?: string; - onChange: (bindings: ComponentBinding[], props: Record) => void; + onChange?: (bindings: ComponentBinding[], props: Record) => void; flags?: Flag[]; } @@ -44,22 +44,22 @@ export const Sandbox = (props: ElementProps & { children: ReactNode }) => { const lang = useContext(LanguageContext); const [formatLang, setFormatLang] = useState(""); - const formatMap: Record = { + const formatMap: Record = { react: "tsx", angular: "html" } - + useEffect(() => { if (!props.properties) return; if (!props.children) return; - + setFormatLang(formatMap[lang]) }, [lang, props.children, props.properties]) // Functions function onChange(bindings: ComponentBinding[]) { - props.onChange(bindings, toKeyValue(bindings)) + props.onChange?.(bindings, toKeyValue(bindings)) } function toKeyValue(bindings: ComponentBinding[]) { @@ -72,18 +72,17 @@ export const Sandbox = (props: ElementProps & { children: ReactNode }) => { }, {}) } + // Filters components from within the Sandbox children + // i.e. Get all the components function getComponents(type: "goa" | "codesnippet"): ReactElement[] { - const children = - Array.isArray(props.children) - ? props.children - : [props.children] - - if (children.length === 0) return []; + const children = Array.isArray(props.children) ? props.children : [props.children] return (children as ReactElement[]) - .filter(el => typeof el.type !== "string" && el.type.name.toLowerCase().startsWith(type)) + .filter(el => typeof el.type !== "string" && el.type.name.toLowerCase().startsWith(type)) } + // Gets code snippets matching the tags passed in. This allows for Angular reactive components + // to be displayed, while hiding the non-reactive ones function getCodeSnippets(...tags: string[]) { const matches = (list: string[]): boolean => { return tags.filter(tag => list.includes(tag)).length === list.length @@ -96,12 +95,13 @@ export const Sandbox = (props: ElementProps & { children: ReactNode }) => { }) } + // CodeSnippet output. To show code the root element *must* start with goa (case-insensitive). + // This allows function output(fn: Serializer): string { return fn(getComponents("goa"), props.properties || []); } function render() { - if (lang === "angular" && props.flags?.includes("reactive")) { return <> Angular @@ -139,8 +139,8 @@ export const Sandbox = (props: ElementProps & { children: ReactNode }) => { return ( <> - { props.properties && - + {props.properties && + }
@@ -149,10 +149,12 @@ export const Sandbox = (props: ElementProps & { children: ReactNode }) => { {/* rendered output */}
- {getComponents("goa")} +
+ {getComponents("goa")} +
- { render() } + {render()} ) } diff --git a/src/routes/components/Button.tsx b/src/routes/components/Button.tsx index 9f33848ea..f752cceb2 100644 --- a/src/routes/components/Button.tsx +++ b/src/routes/components/Button.tsx @@ -1,7 +1,8 @@ import { useState } from "react"; -import { GoABlock, GoAButton, GoADropdown, GoADropdownItem, GoAFormItem, GoAInput } from "@abgov/react-components"; +import { GoABlock, GoAButton, GoAButtonGroup, GoADropdown, GoADropdownItem, GoAFormItem, GoAInput } from "@abgov/react-components"; import { Sandbox, ComponentBinding } from "@components/sandbox"; import { CodeSnippet } from "@components/code-snippet/CodeSnippet"; +import { GoAModal } from "@components/mock-modal/Modal" export default function DropdownPage() { @@ -49,36 +50,58 @@ export default function DropdownPage() {

Ask a user for an address

- - - - - - - - + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - + + Submit and continue + Cancel + + + +

Confirm a destructive action

+ + + +

Are you sure you want to delete this record?

+

You cannot undo this action.

+ + Cancel + Delete record + +
+
+ +

Disabled button with a required field

+ + + + + + + Confirm + Cancel + )