- Implement unified design system tokens, 10px radius, and IRANSans typography - Modernize sidebar with Gemini-style collapsed expander, square 1:1 items, and elevated profile flyout - Standardize all modal dialogs with 16px padding, fixed header/footer, 16px medium title, and sticky bottom actions - Refactor DealFormView and CustomFieldsSection with standard UiField, UiInput, and UiSelect components - Add draggable bottom-left QuickCreate floating action button
105 lines
1.9 KiB
Vue
105 lines
1.9 KiB
Vue
<script setup lang="ts">
|
|
withDefaults(
|
|
defineProps<{
|
|
label?: string;
|
|
forId?: string;
|
|
required?: boolean;
|
|
hint?: string;
|
|
error?: string | boolean;
|
|
horizontal?: boolean;
|
|
}>(),
|
|
{
|
|
required: false,
|
|
horizontal: false,
|
|
},
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="ui-field" :class="{ horizontal, hasError: !!error }">
|
|
<div v-if="label || $slots.label" class="label-row">
|
|
<label v-if="label" :for="forId" class="field-label">
|
|
{{ label }}
|
|
<span v-if="required" class="required-mark" aria-hidden="true">*</span>
|
|
</label>
|
|
<slot v-else name="label" />
|
|
<slot name="label-extra" />
|
|
</div>
|
|
|
|
<div class="control-wrap">
|
|
<slot />
|
|
<p v-if="typeof error === 'string' && error" class="field-error" role="alert">
|
|
{{ error }}
|
|
</p>
|
|
<p v-else-if="hint || $slots.hint" class="field-hint">
|
|
<slot name="hint">{{ hint }}</slot>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.ui-field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--sp-1);
|
|
min-width: 0;
|
|
width: 100%;
|
|
}
|
|
|
|
.ui-field.horizontal {
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: var(--sp-3);
|
|
}
|
|
|
|
.ui-field.horizontal .label-row {
|
|
flex: 0 0 140px;
|
|
}
|
|
|
|
.ui-field.horizontal .control-wrap {
|
|
flex: 1;
|
|
}
|
|
|
|
.label-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: var(--sp-2);
|
|
}
|
|
|
|
.field-label {
|
|
font-size: var(--fs-sm);
|
|
font-weight: var(--fw-semibold);
|
|
color: var(--ink-secondary);
|
|
line-height: var(--lh-tight);
|
|
}
|
|
|
|
.required-mark {
|
|
color: var(--danger);
|
|
margin-right: 2px;
|
|
}
|
|
|
|
.control-wrap {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
min-width: 0;
|
|
width: 100%;
|
|
}
|
|
|
|
.field-error {
|
|
margin: 2px 0 0;
|
|
font-size: var(--fs-xs);
|
|
color: var(--danger);
|
|
font-weight: var(--fw-medium);
|
|
}
|
|
|
|
.field-hint {
|
|
margin: 2px 0 0;
|
|
font-size: var(--fs-xs);
|
|
color: var(--muted);
|
|
line-height: var(--lh-normal);
|
|
}
|
|
</style>
|