Skip to content
Open
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
19 changes: 16 additions & 3 deletions playgrounds/nuxt/app/pages/components/field-group.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,22 @@ const orientation = ref('horizontal' as keyof typeof theme.variants.orientation)
<UButton color="neutral" variant="outline">
Button
</UButton>
<UButton color="neutral" variant="subtle">
Button
</UButton>
<UModal>
<UButton color="neutral" variant="subtle">
Open
</UButton>

<template #footer="{ close }">
<UFieldGroup clear>
<UButton @click="close">
Submit
</UButton>
<UButton color="neutral" variant="outline" @click="close">
Close
</UButton>
</UFieldGroup>
</template>
</UModal>
<UButton color="neutral" variant="outline">
Button
</UButton>
Expand Down
26 changes: 21 additions & 5 deletions src/runtime/components/FieldGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export interface FieldGroupProps {
* @defaultValue 'horizontal'
*/
orientation?: FieldGroup['variants']['orientation']
/**
* When true, clears the field group context so nested components don't inherit styling.
* @defaultValue false
*/
clear?: boolean
class?: any
ui?: FieldGroup['slots']
}
Expand All @@ -46,14 +51,25 @@ const appConfig = useAppConfig() as FieldGroup['AppConfig']
// eslint-disable-next-line vue/no-dupe-keys
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.fieldGroup || {}) }))

provide(fieldGroupInjectionKey, computed(() => ({
orientation: props.orientation,
size: props.size
})))
provide(fieldGroupInjectionKey, computed(() => {
if (props.clear) {
return {
orientation: undefined,
size: undefined
}
}
return {
orientation: props.orientation,
size: props.size
}
}))
</script>

<template>
<Primitive :as="as" :data-orientation="orientation" :class="ui({ orientation, class: props.class })">
<template v-if="clear">
<slot />
</template>
<Primitive v-else :as="as" :data-orientation="orientation" :class="ui({ orientation, class: props.class })">
<slot />
</Primitive>
</template>
4 changes: 2 additions & 2 deletions src/runtime/components/Input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ const modelValue = useVModel<InputProps<T>, 'modelValue', 'update:modelValue'>(p

const appConfig = useAppConfig() as Input['AppConfig']

const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, emitFormFocus, ariaAttrs } = useFormField<InputProps<T>>(props, { deferInputValidation: true })
const { emitFormBlur, emitFormInput, emitFormChange, size: formFieldSize, color, id, name, highlight, disabled, emitFormFocus, ariaAttrs } = useFormField<InputProps<T>>(props, { deferInputValidation: true })
const { orientation, size: fieldGroupSize } = useFieldGroup<InputProps<T>>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)

const inputSize = computed(() => fieldGroupSize.value || formGroupSize.value)
const inputSize = computed(() => fieldGroupSize.value || formFieldSize.value)

const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.input || {}) })({
type: props.type as Input['variants']['type'],
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/components/InputDate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const slots = defineSlots<InputDateSlots>()
const appConfig = useAppConfig() as InputDate['AppConfig']

const rootProps = useForwardPropsEmits(reactiveOmit(props, 'id', 'name', 'range', 'modelValue', 'defaultValue', 'color', 'variant', 'size', 'highlight', 'disabled', 'autofocus', 'autofocusDelay', 'icon', 'avatar', 'leading', 'leadingIcon', 'trailing', 'trailingIcon', 'loading', 'loadingIcon', 'separatorIcon', 'class', 'ui'), emits)
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputDateProps<R>>(props)
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formFieldSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputDateProps<R>>(props)
const { orientation, size: fieldGroupSize } = useFieldGroup<InputDateProps<R>>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)

Expand All @@ -100,7 +100,7 @@ const [DefineSegmentsTemplate, ReuseSegmentsTemplate] = createReusableTemplate<{
type?: 'start' | 'end'
}>()

const inputSize = computed(() => fieldGroupSize.value || formGroupSize.value)
const inputSize = computed(() => fieldGroupSize.value || formFieldSize.value)

const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.inputDate || {}) })({
color: color.value,
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/components/InputMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,11 @@ const virtualizerProps = toRef(() => {
})
})

const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formFieldSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
const { orientation, size: fieldGroupSize } = useFieldGroup<InputProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))

const inputSize = computed(() => fieldGroupSize.value || formGroupSize.value)
const inputSize = computed(() => fieldGroupSize.value || formFieldSize.value)

const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: InputMenuItem, index: number }>({
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/components/InputNumber.vue
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ const appConfig = useAppConfig() as InputNumber['AppConfig']

const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultValue', 'min', 'max', 'step', 'stepSnapping', 'formatOptions', 'disableWheelChange', 'invertWheelChange', 'readonly'), emits)

const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, id, color, size: formGroupSize, name, highlight, disabled, ariaAttrs } = useFormField<InputNumberProps<T>>(props)
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, id, color, size: formFieldSize, name, highlight, disabled, ariaAttrs } = useFormField<InputNumberProps<T>>(props)
const { orientation, size: fieldGroupSize } = useFieldGroup<InputNumberProps<T>>(props)

const inputSize = computed(() => fieldGroupSize.value || formGroupSize.value)
const inputSize = computed(() => fieldGroupSize.value || formFieldSize.value)

const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.inputNumber || {}) })({
color: color.value,
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/components/InputTags.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ const appConfig = useAppConfig() as InputTags['AppConfig']

const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'addOnPaste', 'addOnTab', 'addOnBlur', 'duplicate', 'delimiter', 'max', 'convertValue', 'displayValue', 'required'), emits)

const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputTagsProps>(props)
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formFieldSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputTagsProps>(props)
const { orientation, size: fieldGroupSize } = useFieldGroup<InputTagsProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)

const inputSize = computed(() => fieldGroupSize.value || formGroupSize.value)
const inputSize = computed(() => fieldGroupSize.value || formFieldSize.value)

const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.inputTags || {}) })({
color: color.value,
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/components/InputTime.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ const appConfig = useAppConfig() as InputTime['AppConfig']

const rootProps = useForwardPropsEmits(reactiveOmit(props, 'id', 'name', 'color', 'variant', 'size', 'highlight', 'disabled', 'autofocus', 'autofocusDelay', 'icon', 'avatar', 'leading', 'leadingIcon', 'trailing', 'trailingIcon', 'loading', 'loadingIcon', 'class', 'ui'), emits)

const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, id, color, size: formGroupSize, name, highlight, disabled, ariaAttrs } = useFormField<InputTimeProps>(props)
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, id, color, size: formFieldSize, name, highlight, disabled, ariaAttrs } = useFormField<InputTimeProps>(props)
const { orientation, size: fieldGroupSize } = useFieldGroup<InputTimeProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)

const inputSize = computed(() => fieldGroupSize.value || formGroupSize.value)
const inputSize = computed(() => fieldGroupSize.value || formFieldSize.value)

const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.inputTime || {}) })({
color: color.value,
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/components/Select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,11 @@ const portalProps = usePortal(toRef(() => props.portal))
const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as SelectContentProps)
const arrowProps = toRef(() => props.arrow as SelectArrowProps)

const { emitFormChange, emitFormInput, emitFormBlur, emitFormFocus, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
const { emitFormChange, emitFormInput, emitFormBlur, emitFormFocus, size: formFieldSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
const { orientation, size: fieldGroupSize } = useFieldGroup<InputProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))

const selectSize = computed(() => fieldGroupSize.value || formGroupSize.value)
const selectSize = computed(() => fieldGroupSize.value || formFieldSize.value)

const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.select || {}) })({
color: color.value,
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/components/SelectMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,11 @@ const virtualizerProps = toRef(() => {
})
const searchInputProps = toRef(() => defu(props.searchInput, { placeholder: t('selectMenu.search'), variant: 'none' }) as InputProps)

const { emitFormBlur, emitFormFocus, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
const { emitFormBlur, emitFormFocus, emitFormInput, emitFormChange, size: formFieldSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
const { orientation, size: fieldGroupSize } = useFieldGroup<InputProps>(props)
const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))

const selectSize = computed(() => fieldGroupSize.value || formGroupSize.value)
const selectSize = computed(() => fieldGroupSize.value || formFieldSize.value)

const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: SelectMenuItem, index: number }>({
Expand Down
1 change: 1 addition & 0 deletions test/components/FieldGroup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe('FieldGroup', () => {
// Props
['with as', { props: { as: 'section' } }],
['with class', { props: { class: 'absolute' } }],
['with clear', { props: { clear: true } }],
// Slots
['with default slot', {
slots: {
Expand Down
2 changes: 2 additions & 0 deletions test/components/__snapshots__/FieldGroup-vue.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ exports[`FieldGroup > renders with as correctly 1`] = `"<section data-orientatio
exports[`FieldGroup > renders with class correctly 1`] = `"<div data-orientation="horizontal" class="inline-flex -space-x-px absolute"></div>"`;
exports[`FieldGroup > renders with clear correctly 1`] = `""`;
exports[`FieldGroup > renders with default slot correctly 1`] = `
"<div data-orientation="horizontal" class="relative inline-flex -space-x-px">
<div data-slot="root" class="relative inline-flex items-center group has-focus-visible:z-[1]"><input type="text" data-slot="base" class="w-full rounded-md border-0 appearance-none placeholder:text-dimmed focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 transition-colors group-not-only:group-first:rounded-e-none group-not-only:group-last:rounded-s-none group-not-last:group-not-first:rounded-none px-2.5 py-1.5 text-sm gap-1.5 text-highlighted bg-default ring ring-inset ring-accented focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary" autocomplete="off">
Expand Down
2 changes: 2 additions & 0 deletions test/components/__snapshots__/FieldGroup.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ exports[`FieldGroup > renders with as correctly 1`] = `"<section data-orientatio

exports[`FieldGroup > renders with class correctly 1`] = `"<div data-orientation="horizontal" class="inline-flex -space-x-px absolute"></div>"`;

exports[`FieldGroup > renders with clear correctly 1`] = `""`;

exports[`FieldGroup > renders with default slot correctly 1`] = `
"<div data-orientation="horizontal" class="relative inline-flex -space-x-px">
<div data-slot="root" class="relative inline-flex items-center group has-focus-visible:z-[1]"><input type="text" data-slot="base" class="w-full rounded-md border-0 appearance-none placeholder:text-dimmed focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 transition-colors group-not-only:group-first:rounded-e-none group-not-only:group-last:rounded-s-none group-not-last:group-not-first:rounded-none px-2.5 py-1.5 text-sm gap-1.5 text-highlighted bg-default ring ring-inset ring-accented focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary" autocomplete="off">
Expand Down
Loading