refactor: merge FormField and FormFieldWrapper components
- Add children prop to FormField to support wrapper mode - Update components using FormFieldWrapper to use FormField - Remove redundant FormFieldWrapper component - Maintain all existing functionality with unified API This reduces code duplication and simplifies the form component hierarchy. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ea7ec61377
commit
05ddafcdea
5 changed files with 37 additions and 95 deletions
|
|
@ -97,7 +97,7 @@ Refactor components to reduce duplication and complexity.
|
||||||
- [-] **Create base components**
|
- [-] **Create base components**
|
||||||
- [x] Extract `BaseModal` component for shared modal logic
|
- [x] Extract `BaseModal` component for shared modal logic
|
||||||
- [x] Create `BaseDropdown` for dropdown patterns
|
- [x] Create `BaseDropdown` for dropdown patterns
|
||||||
- [ ] Merge `FormField` and `FormFieldWrapper`
|
- [x] Merge `FormField` and `FormFieldWrapper`
|
||||||
- [ ] Create `BaseSegmentedController` for shared logic
|
- [ ] Create `BaseSegmentedController` for shared logic
|
||||||
|
|
||||||
- [ ] **Refactor photo grids**
|
- [ ] **Refactor photo grids**
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string
|
label: string
|
||||||
name: string
|
name?: string
|
||||||
type?: string
|
type?: string
|
||||||
value?: any
|
value?: any
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
|
|
@ -10,11 +10,12 @@
|
||||||
helpText?: string
|
helpText?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onchange?: (e: Event) => void
|
onchange?: (e: Event) => void
|
||||||
|
children?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
label,
|
label,
|
||||||
name,
|
name = '',
|
||||||
type = 'text',
|
type = 'text',
|
||||||
value = $bindable(),
|
value = $bindable(),
|
||||||
placeholder = '',
|
placeholder = '',
|
||||||
|
|
@ -22,9 +23,13 @@
|
||||||
error = '',
|
error = '',
|
||||||
helpText = '',
|
helpText = '',
|
||||||
disabled = false,
|
disabled = false,
|
||||||
onchange
|
onchange,
|
||||||
|
children
|
||||||
}: Props = $props()
|
}: Props = $props()
|
||||||
|
|
||||||
|
// If children are provided, this is a wrapper mode
|
||||||
|
const isWrapper = $derived(!!children)
|
||||||
|
|
||||||
function handleChange(e: Event) {
|
function handleChange(e: Event) {
|
||||||
const target = e.target as HTMLInputElement | HTMLTextAreaElement
|
const target = e.target as HTMLInputElement | HTMLTextAreaElement
|
||||||
value = target.value
|
value = target.value
|
||||||
|
|
@ -33,14 +38,16 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="form-field" class:has-error={!!error}>
|
<div class="form-field" class:has-error={!!error}>
|
||||||
<label for={name}>
|
<label for={!isWrapper ? name : undefined}>
|
||||||
{label}
|
{label}
|
||||||
{#if required}
|
{#if required}
|
||||||
<span class="required">*</span>
|
<span class="required">*</span>
|
||||||
{/if}
|
{/if}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{#if type === 'textarea'}
|
{#if isWrapper}
|
||||||
|
{@render children()}
|
||||||
|
{:else if type === 'textarea'}
|
||||||
<textarea
|
<textarea
|
||||||
id={name}
|
id={name}
|
||||||
{name}
|
{name}
|
||||||
|
|
@ -75,10 +82,17 @@
|
||||||
.form-field {
|
.form-field {
|
||||||
margin-bottom: $unit-4x;
|
margin-bottom: $unit-4x;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&.has-error {
|
&.has-error {
|
||||||
input,
|
input,
|
||||||
textarea {
|
textarea,
|
||||||
border-color: #c33;
|
:global(input),
|
||||||
|
:global(textarea),
|
||||||
|
:global(select) {
|
||||||
|
border-color: $red-error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -88,9 +102,10 @@
|
||||||
margin-bottom: $unit;
|
margin-bottom: $unit;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: $gray-20;
|
color: $gray-20;
|
||||||
|
font-size: 0.925rem;
|
||||||
|
|
||||||
.required {
|
.required {
|
||||||
color: #c33;
|
color: $red-error;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -100,11 +115,11 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: $unit-2x $unit-3x;
|
padding: $unit-2x $unit-3x;
|
||||||
border: 1px solid $gray-80;
|
border: 1px solid $gray-80;
|
||||||
border-radius: 6px;
|
border-radius: $corner-radius-sm;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
transition: border-color 0.2s ease;
|
transition: border-color $transition-normal ease;
|
||||||
background-color: white;
|
background-color: $white;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
@ -124,13 +139,13 @@
|
||||||
|
|
||||||
.error-text {
|
.error-text {
|
||||||
margin-top: $unit;
|
margin-top: $unit;
|
||||||
color: #c33;
|
color: $red-error;
|
||||||
font-size: 0.875rem;
|
font-size: $font-size-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-text {
|
.help-text {
|
||||||
margin-top: $unit;
|
margin-top: $unit;
|
||||||
color: $gray-40;
|
color: $gray-40;
|
||||||
font-size: 0.875rem;
|
font-size: $font-size-small;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
interface Props {
|
|
||||||
label: string
|
|
||||||
required?: boolean
|
|
||||||
helpText?: string
|
|
||||||
error?: string
|
|
||||||
children?: any
|
|
||||||
}
|
|
||||||
|
|
||||||
let { label, required = false, helpText, error, children }: Props = $props()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="form-field" class:has-error={!!error}>
|
|
||||||
<label>
|
|
||||||
{label}
|
|
||||||
{#if required}
|
|
||||||
<span class="required">*</span>
|
|
||||||
{/if}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{@render children?.()}
|
|
||||||
|
|
||||||
{#if helpText}
|
|
||||||
<p class="help-text">{helpText}</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if error}
|
|
||||||
<p class="error-text">{error}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.form-field {
|
|
||||||
margin-bottom: $unit-3x;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-error {
|
|
||||||
:global(input),
|
|
||||||
:global(textarea),
|
|
||||||
:global(select) {
|
|
||||||
border-color: #c33;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: $unit;
|
|
||||||
font-weight: 500;
|
|
||||||
color: $gray-20;
|
|
||||||
font-size: 0.925rem;
|
|
||||||
|
|
||||||
.required {
|
|
||||||
color: #c33;
|
|
||||||
margin-left: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-text {
|
|
||||||
margin-top: $unit;
|
|
||||||
color: #c33;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help-text {
|
|
||||||
margin-top: $unit;
|
|
||||||
color: $gray-40;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FormFieldWrapper from './FormFieldWrapper.svelte'
|
import FormField from './FormField.svelte'
|
||||||
|
|
||||||
interface Option {
|
interface Option {
|
||||||
value: string
|
value: string
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormFieldWrapper {label} {required} {helpText} {error}>
|
<FormField {label} {required} {helpText} {error}>
|
||||||
{#snippet children()}
|
{#snippet children()}
|
||||||
<div class="segmented-control-wrapper" class:full-width={fullWidth}>
|
<div class="segmented-control-wrapper" class:full-width={fullWidth}>
|
||||||
<div class="segmented-control">
|
<div class="segmented-control">
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</FormFieldWrapper>
|
</FormField>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.segmented-control-wrapper {
|
.segmented-control-wrapper {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Select from './Select.svelte'
|
import Select from './Select.svelte'
|
||||||
import FormFieldWrapper from './FormFieldWrapper.svelte'
|
import FormField from './FormField.svelte'
|
||||||
import type { HTMLSelectAttributes } from 'svelte/elements'
|
import type { HTMLSelectAttributes } from 'svelte/elements'
|
||||||
|
|
||||||
interface Option {
|
interface Option {
|
||||||
|
|
@ -36,11 +36,11 @@
|
||||||
}: Props = $props()
|
}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormFieldWrapper {label} {required} {helpText} {error}>
|
<FormField {label} {required} {helpText} {error}>
|
||||||
{#snippet children()}
|
{#snippet children()}
|
||||||
<Select bind:value {options} {size} {variant} {fullWidth} {pill} {...restProps} />
|
<Select bind:value {options} {size} {variant} {fullWidth} {pill} {...restProps} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</FormFieldWrapper>
|
</FormField>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
// Ensure proper spacing for select fields
|
// Ensure proper spacing for select fields
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue