Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/components/mock-modal/Modal.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.modal {
padding: 2rem;
border: 1px solid var(--goa-color-greyscale-700);
border-radius: 4px;
box-shadow: var(--goa-shadow-modal);
}
16 changes: 16 additions & 0 deletions src/components/mock-modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ReactNode } from "react";
import "./Modal.css";

interface Props {
children: ReactNode
};

export function GoAModal({ children }: Props) {
return (
<div className="modal">
{children}
</div>
)
}

export default GoAModal;
17 changes: 8 additions & 9 deletions src/components/sandbox/ComponentSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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)
}
Expand All @@ -36,15 +36,15 @@ 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);
return this.serializer.arrayToProp(name, items, delimit)
}

return ""
}
}

// Public
componentsToString(components: ReactElement[], spacing: number = 0): string {
Expand All @@ -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 <GoAInput />
if (!component.props) {
Expand Down Expand Up @@ -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)}</${elementType}>`;
}
}

return `<${elementType}${props} />`;
}
Expand Down
36 changes: 19 additions & 17 deletions src/components/sandbox/Sandbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Flag = "reactive"
interface ElementProps {
properties?: ComponentBinding[];
note?: string;
onChange: (bindings: ComponentBinding[], props: Record<string, unknown>) => void;
onChange?: (bindings: ComponentBinding[], props: Record<string, unknown>) => void;
flags?: Flag[];
}

Expand Down Expand Up @@ -44,22 +44,22 @@ export const Sandbox = (props: ElementProps & { children: ReactNode }) => {
const lang = useContext(LanguageContext);
const [formatLang, setFormatLang] = useState<string>("");

const formatMap: Record<string, string> = {
const formatMap: Record<string, string> = {
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[]) {
Expand All @@ -72,18 +72,17 @@ export const Sandbox = (props: ElementProps & { children: ReactNode }) => {
}, {})
}

// Filters components from within the Sandbox children
// i.e. Get all the <CodeSnippet> 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
Expand All @@ -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
Expand Down Expand Up @@ -139,8 +139,8 @@ export const Sandbox = (props: ElementProps & { children: ReactNode }) => {

return (
<>
{ props.properties &&
<SandboxProperties properties={props.properties} onChange={onChange} />
{props.properties &&
<SandboxProperties properties={props.properties} onChange={onChange} />
}

<div className="sandbox-note">
Expand All @@ -149,10 +149,12 @@ export const Sandbox = (props: ElementProps & { children: ReactNode }) => {

{/* rendered output */}
<div className="sandbox-render">
{getComponents("goa")}
<div>
{getComponents("goa")}
</div>
</div>

{ render() }
{render()}
</>
)
}
Expand Down
77 changes: 50 additions & 27 deletions src/routes/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -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() {

Expand Down Expand Up @@ -49,36 +50,58 @@ export default function DropdownPage() {

<h3>Ask a user for an address</h3>

<Sandbox
flags={["reactive"]}
onChange={onChange}
note="Used to carry out the primary action. Don’t use more than one primary button in a section or screen."
>

<GoABlock direction="column">
<GoAFormItem label="Street Address">
<GoAInput name="address" type="text" value="" onChange={noop} width="400px" />
</GoAFormItem>
<GoAFormItem label="Suite or unit #">
<GoAInput name="suite" type="text" value="" onChange={noop} width="400px" />
<Sandbox flags={["reactive"]}>
<GoAFormItem label="Street Address">
<GoAInput name="address" type="text" value="" onChange={noop} width="100%" />
</GoAFormItem>
<GoAFormItem label="Suite or unit #">
<GoAInput name="suite" type="text" value="" onChange={noop} width="100%" />
</GoAFormItem>
<GoAFormItem label="City/town">
<GoAInput name="city" type="text" value="" onChange={noop} width="100%" />
</GoAFormItem>
<GoABlock direction="row">
<GoAFormItem label="Provice/territory">
<GoADropdown onChange={noop} name="province" value="alberta">
<GoADropdownItem label="Alberta" value="alberta" />
<GoADropdownItem label="BC" value="bc" />
<GoADropdownItem label="Saskatchewan" value="saskatchewan" />
</GoADropdown>
</GoAFormItem>
<GoAFormItem label="City/town">
<GoAInput name="city" type="text" value="" onChange={noop} width="400px" />
<GoAFormItem label="Postal Code">
<GoAInput name="postalCode" type="text" value="" onChange={noop} width="100%" />
</GoAFormItem>
<GoABlock direction="row">
<GoAFormItem label="Provice/territory">
<GoADropdown onChange={noop} name="province" value="alberta">
<GoADropdownItem label="Alberta" value="alberta" />
<GoADropdownItem label="BC" value="bc" />
<GoADropdownItem label="Saskatchewan" value="saskatchewan" />
</GoADropdown>
</GoAFormItem>
<GoAFormItem label="Postal Code">
<GoAInput name="postalCode" type="text" value="" onChange={noop} width="190px" />
</GoAFormItem>
</GoABlock>
</GoABlock>

<GoAButtonGroup alignment="start" mt="l">
<GoAButton type="primary" onClick={noop}>Submit and continue</GoAButton>
<GoAButton type="secondary" onClick={noop}>Cancel</GoAButton>
</GoAButtonGroup>
</Sandbox>

<h3>Confirm a destructive action</h3>

<Sandbox>
<GoAModal>
<h3>Are you sure you want to delete this record?</h3>
<p>You cannot undo this action.</p>
<GoAButtonGroup alignment="end" mt="l">
<GoAButton type="secondary" onClick={noop}>Cancel</GoAButton>
<GoAButton type="primary" variant="destructive" onClick={noop}>Delete record</GoAButton>
</GoAButtonGroup>
</GoAModal>
</Sandbox>

<h3>Disabled button with a required field</h3>

<Sandbox flags={["reactive"]}>
<GoAFormItem label="Input">
<GoAInput name="input" type="text" value="" onChange={noop} width="400px" />
</GoAFormItem>
<GoAButtonGroup alignment="start" mt="l">
<GoAButton disabled={true} onClick={noop}>Confirm</GoAButton>
<GoAButton type="secondary" onClick={noop}>Cancel</GoAButton>
</GoAButtonGroup>
</Sandbox>
</>
)
Expand Down