mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
Persist optional account notes across creation, editing, imports, and credential refreshes. Show notes in compact account rows with overflow-only popovers. Support vertically resizable textareas, bounded select menus with custom scrollbars, and quota updates without table loading. Co-authored-by: Zyy <zyycn@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com>
84 lines
2.7 KiB
Vue
84 lines
2.7 KiB
Vue
<script setup lang="ts">
|
|
import { computed, inject, useAttrs } from 'vue'
|
|
import { formFieldKey } from './BaseForm/context'
|
|
|
|
type TextareaSize = 'sm' | 'md' | 'lg'
|
|
|
|
defineOptions({ inheritAttrs: false })
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
placeholder?: string
|
|
disabled?: boolean
|
|
size?: TextareaSize
|
|
rows?: number
|
|
resize?: 'none' | 'both' | 'horizontal' | 'vertical'
|
|
}>(),
|
|
{
|
|
placeholder: '',
|
|
disabled: false,
|
|
size: 'md',
|
|
rows: 5,
|
|
resize: 'vertical',
|
|
},
|
|
)
|
|
|
|
const model = defineModel<string>({ default: '' })
|
|
const attrs = useAttrs()
|
|
const field = inject(formFieldKey, null)
|
|
|
|
const controlId = computed(() => typeof attrs.id === 'string' ? attrs.id : field?.controlId.value)
|
|
const invalid = computed(() => Boolean(
|
|
field?.invalid.value || attrs['aria-invalid'] === true || attrs['aria-invalid'] === 'true',
|
|
))
|
|
const describedBy = computed(() => [
|
|
typeof attrs['aria-describedby'] === 'string' ? attrs['aria-describedby'] : undefined,
|
|
field?.describedBy.value,
|
|
].filter(Boolean).join(' ') || undefined)
|
|
const rootAttrs = computed(() => ({ class: attrs.class, style: attrs.style }))
|
|
const controlAttrs = computed(() => Object.fromEntries(
|
|
Object.entries(attrs).filter(([key]) => ![
|
|
'class',
|
|
'style',
|
|
'id',
|
|
'aria-describedby',
|
|
'aria-invalid',
|
|
'aria-required',
|
|
].includes(key)),
|
|
))
|
|
|
|
const sizeClasses: Record<TextareaSize, string> = {
|
|
sm: 'px-3 py-2.5 text-xs',
|
|
md: 'px-3.5 py-3 text-cp',
|
|
lg: 'px-4 py-3.5 text-cp-lg',
|
|
}
|
|
|
|
const textareaClasses = computed(() => [
|
|
'cp-scrollbar w-full rounded-cp border-0 bg-[var(--cp-input-bg)] text-cp-text shadow-cp-input outline-none transition-[background-color,box-shadow,color] duration-160 placeholder:text-cp-text-quaternary motion-reduce:transition-none',
|
|
'hover:bg-[var(--cp-input-hover-bg)] hover:shadow-cp-input-hover focus:bg-(--cp-input-active-bg) focus:shadow-cp-input-active',
|
|
'disabled:cursor-not-allowed disabled:bg-cp-bg-container-disabled disabled:text-cp-text-disabled disabled:shadow-none',
|
|
'leading-[1.55] font-emphasis',
|
|
sizeClasses[props.size],
|
|
invalid.value ? 'bg-(--cp-input-error-active-bg) shadow-cp-input-error-active' : undefined,
|
|
])
|
|
</script>
|
|
|
|
<template>
|
|
<div v-bind="rootAttrs" class="min-w-0">
|
|
<textarea
|
|
v-bind="controlAttrs"
|
|
:id="controlId"
|
|
v-model="model"
|
|
:class="textareaClasses"
|
|
:style="{ resize: disabled ? 'none' : resize }"
|
|
:rows="rows"
|
|
:placeholder="placeholder"
|
|
:disabled="disabled"
|
|
:required="field?.required.value || undefined"
|
|
:aria-describedby="describedBy"
|
|
:aria-invalid="invalid || undefined"
|
|
:aria-required="field?.required.value || undefined"
|
|
/>
|
|
</div>
|
|
</template>
|