Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customize styling of the porting embed #5

Merged
merged 4 commits into from Mar 1, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 18 additions & 3 deletions lib/PortingEmbed/EmbedField.tsx
@@ -1,11 +1,26 @@
import { FieldStore } from '@modular-forms/preact'

import { useEmbedOptions } from './Options'

type Props = {
children: React.ReactNode
of: FieldStore<any, string> // eslint-disable-line @typescript-eslint/no-explicit-any
}

export function EmbedField({ children }: Props) {
// TODO: customizable classNames
export function EmbedField({ children, of: field }: Props) {
const options = useEmbedOptions()
const customClassName =
options.className?.field?.({
name: field.name,
touched: field.touched.value,
dirty: field.dirty.value,
valid: !field.error.value,
}) || ''

return (
<div className="GigsEmbeds GigsPortingEmbed GigsEmbeds-field">
<div
className={`GigsEmbeds GigsPortingEmbed GigsEmbeds-field ${customClassName}`}
>
{children}
</div>
)
Expand Down
23 changes: 19 additions & 4 deletions lib/PortingEmbed/EmbedFieldError.tsx
@@ -1,15 +1,30 @@
import { FieldStore } from '@modular-forms/preact'

import { useEmbedOptions } from './Options'

type Props = {
error: string
of: FieldStore<any, string> // eslint-disable-line @typescript-eslint/no-explicit-any
}

export function EmbedFieldError({ error }: Props) {
export function EmbedFieldError({ of: field }: Props) {
const options = useEmbedOptions()
const customClassName =
options.className?.error?.({
name: field.name,
touched: field.touched.value,
dirty: field.dirty.value,
}) || ''

const error = field.error.value

if (!error) {
return null
}

// TODO: customizable classNames
return (
<div className="GigsEmbeds GigsPortingEmbed GigsEmbeds-fieldError">
<div
className={`GigsEmbeds GigsPortingEmbed GigsEmbeds-error ${customClassName}`}
>
{error}
</div>
)
Expand Down
26 changes: 21 additions & 5 deletions lib/PortingEmbed/EmbedFieldInput.tsx
@@ -1,11 +1,27 @@
type Props = React.HTMLAttributes<HTMLInputElement>
import { FieldStore } from '@modular-forms/preact'

import { useEmbedOptions } from './Options'

type Props = {
of: FieldStore<any, string> // eslint-disable-line @typescript-eslint/no-explicit-any
} & React.HTMLAttributes<HTMLInputElement>

export function EmbedFieldInput({ of: field, ...rest }: Props) {
const options = useEmbedOptions()
const customClassName =
options.className?.input?.({
name: field.name,
touched: field.touched.value,
dirty: field.dirty.value,
valid: !field.error.value,
}) || ''
const id = `__ge_${field.name}`

export function EmbedFieldInput(props: Props) {
// TODO: customizable classNames
return (
<input
className="GigsEmbeds GigsPortingEmbed GigsEmbeds-fieldInput"
{...props}
className={`GigsEmbeds GigsPortingEmbed GigsEmbeds-input ${customClassName}`}
id={id}
{...rest}
/>
)
}
26 changes: 21 additions & 5 deletions lib/PortingEmbed/EmbedFieldLabel.tsx
@@ -1,11 +1,27 @@
type Props = React.HTMLAttributes<HTMLLabelElement>
import { FieldStore } from '@modular-forms/preact'

import { useEmbedOptions } from './Options'

type Props = {
of: FieldStore<any, string> // eslint-disable-line @typescript-eslint/no-explicit-any
} & React.HTMLAttributes<HTMLLabelElement>

export function EmbedFieldLabel({ of: field, ...rest }: Props) {
const options = useEmbedOptions()
const customClassName =
options.className?.label?.({
name: field.name,
touched: field.touched.value,
dirty: field.dirty.value,
valid: !field.error.value,
}) || ''
const id = `__ge_${field.name}`

export function EmbedFieldLabel(props: Props) {
// TODO: customizable classNames
return (
<label
className="GigsEmbeds GigsPortingEmbed GigsEmbeds-fieldLabel"
{...props}
htmlFor={id}
className={`GigsEmbeds GigsPortingEmbed GigsEmbeds-label ${customClassName}`}
{...rest}
/>
)
}
36 changes: 36 additions & 0 deletions lib/PortingEmbed/Options.tsx
@@ -0,0 +1,36 @@
import { createContext } from 'preact'
import { useContext } from 'preact/hooks'

type FieldState = {
name: string
dirty: boolean
touched: boolean
valid: boolean
}

type FormState = {
name: string
dirty: boolean
valid: boolean
submitting: boolean
touched: boolean
}

export const defaultFormId = 'gigsPortingEmbedForm'

export type EmbedOptions = {
formId?: string
className?: {
form?: (state: FormState) => string
field?: (state: FieldState) => string
input?: (state: FieldState) => string
label?: (state: FieldState) => string
error?: (state: Omit<FieldState, 'valid'>) => string
}
}

export const OptionsContext = createContext<EmbedOptions>({})

export function useEmbedOptions() {
return useContext(OptionsContext)
}
31 changes: 15 additions & 16 deletions lib/PortingEmbed/PortingEmbed.tsx
@@ -1,12 +1,8 @@
import { Porting, UpdatePortingBody } from '../types'
import { EmbedOptions, OptionsContext } from './Options'
import { PortingForm } from './PortingForm'

export type CustomizableEmbedProps = {
// TODO: add styling options
styleConfig?: {
foo?: string
}
}
export type CustomizableEmbedProps = EmbedOptions

export type ValidationChangeEvent = {
isValid: boolean
Expand All @@ -18,22 +14,25 @@ type CoreEmbedProps = {
onPortingUpdate?: (updatedFields: UpdatePortingBody) => unknown
}

type PortingEmbedProps = CoreEmbedProps & CustomizableEmbedProps
type PortingEmbedProps = CoreEmbedProps & { options?: CustomizableEmbedProps }

export function PortingEmbed({
porting,
onPortingUpdate,
onValidationChange,
options,
}: PortingEmbedProps) {
return (
<div className="__ge_portingRoot">
<PortingForm
porting={porting}
onValidationChange={onValidationChange}
onSubmit={async (updatedFields) => {
await onPortingUpdate?.(updatedFields)
}}
/>
</div>
<OptionsContext.Provider value={options || {}}>
<div className="__ge_portingRoot">
<PortingForm
porting={porting}
onValidationChange={onValidationChange}
onSubmit={async (updatedFields) => {
await onPortingUpdate?.(updatedFields)
}}
/>
</div>
</OptionsContext.Provider>
)
}
2 changes: 1 addition & 1 deletion lib/PortingEmbed/PortingForm.tsx
Expand Up @@ -59,7 +59,7 @@ export function PortingForm({ porting, onValidationChange, onSubmit }: Props) {
)
}

if (step === 'donorApproval') {
if (step === 'donorProviderApproval') {
return (
<StepDonorProviderApprovalForm
porting={porting}
Expand Down