Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@ import { useClass } from '@/Lib/UseClass/Internal';
import { computed, toValue } from 'vue';

export function useFloatingLabel(
elementName: MaybeRefOrGetter<FloatingLabelComponentName>,
componentName: MaybeRefOrGetter<FloatingLabelComponentName>,
labelType: MaybeRefOrGetter<LabelType | undefined>,
): ComputedRef<string> {
return computed(() => {
const isFloating = toValue(labelType) === 'floating';

const classes: Record<FloatingLabelComponentName, ComputedRef<string>> = {
FoInputFile: useClass(isFloating, 'input-floating'),
FoInputText: useClass(isFloating, 'input-floating'),
FoSelect: useClass(isFloating, 'select-floating'),
FoTextarea: useClass(isFloating, 'textarea-floating'),
};

return classes[toValue(elementName)].value;
return classes[toValue(componentName)].value;
});
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ComponentName } from '@/Lib';

export type FloatingLabelComponentName = Extract<ComponentName, 'FoInputText' | 'FoSelect' | 'FoTextarea'>;
// todo: there is a mismatch between configurable and floating, this should be checked
export type FloatingLabelComponentName = Extract<ComponentName, 'FoInputFile' | 'FoInputText' | 'FoSelect' | 'FoTextarea'>;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type NonConfigurableComponentName = 'FoAvatarGroup'
| 'FoDiff'
| 'FoDotStyleBadge'
| 'FoJoin'
| 'FoImageRadio'
| 'FoLabel'
| 'FoListGroup'
| 'FoListGroupItem'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { HeadingProps, KeyboardProps, LinkProps }
import type { DividerProps } from '@/UI/Content/Divider';
import type { IconProps } from '@/UI/Customization';
import type { CheckboxProps, InputTextProps, SelectProps, SwitchProps, TextareaProps } from '@/UI/Forms';
import type { InputFileProps } from '@/UI/Forms/InputFile';
import type { RadioProps } from '@/UI/Forms/Radio';
import type { RangeProps } from '@/UI/Forms/Range';
import type { TabProps, TabsProps } from '@/UI/Navigations';
Expand Down Expand Up @@ -113,6 +114,11 @@ export interface ConfigurableComponentProps {
FoDivider: ConfigurableProps<DividerProps>;
FoHeading: ConfigurableProps<HeadingProps>;
FoIcon: ConfigurableProps<IconProps>;
FoInputFile: ConfigurableProps<
InputFileProps
& HorizontalPositionComponentConfig<HorizontalHelperTextPositionConfig>
& LabelTypeComponentConfig
>;
FoInputText: ConfigurableProps<
InputTextProps
& HorizontalPositionComponentConfig<HorizontalPositionGlobalConfig>
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/Lib/UseSize/Internal/Lib/UseSize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ export function useSize(
large: 'checkbox-lg',
extraLarge: 'checkbox-xl',
},
FoInputFile: {
extraSmall: 'input-xs',
small: 'input-sm',
medium: '',
large: 'input-lg',
extraLarge: 'input-xl',
},
FoInputText: {
extraSmall: 'input-xs',
small: 'input-sm',
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/Lib/UseSize/Types/Size.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type SizableComponentName = Extract<
| 'FoBadge'
| 'FoButton'
| 'FoCheckbox'
| 'FoInputFile'
| 'FoInputText'
| 'FoKeyboard'
| 'FoLoading'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ComponentName } from '@/Lib';
import type { HorizontalPosition } from '@/Types';

export type PositionableHelperTextComponentName = Extract<ComponentName, 'FoInputText' | 'FoSelect' | 'FoTextarea'>;
export type PositionableHelperTextComponentName = Extract<ComponentName, 'FoInputFile' | 'FoInputText' | 'FoSelect' | 'FoTextarea'>;

export type HelperText = string;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import type { ComponentName, FloatingLabelComponentName, MaybeStringId } from '@/Lib';
import type { LabelType } from '@/UI/Components';

export type ConfigurableLabelComponentName = Extract<ComponentName, 'FoInputText' | 'FoSelect' | 'FoTextarea'>;
export type ConfigurableLabelComponentName = Extract<
ComponentName,
'FoInputFile'
| 'FoInputText'
| 'FoSelect'
| 'FoTextarea'
>;

export interface LabelProps<T extends LabelType = LabelType> extends MaybeStringId {
componentName?: FloatingLabelComponentName;
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/UI/Components/Label/Internal/UI/FoLabel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ const labelClass = computed((): string => {
}

const classes: Record<FloatingLabelComponentName, Record<LabelType, string>> = {
FoInputFile: {
text: 'label-text',
floating: 'input-floating-label',
inline: '',
},
FoInputText: {
text: 'label-text',
floating: 'input-floating-label',
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/UI/Forms/InputFile/Types/InputFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Disableable, MaybeStringId, Sizable, Validity } from '@/Lib';
import type { LabelType, WithConfigurableHelperText, WithConfigurableInputLabel } from '@/UI/Components';

export type InputFileLabelType = Extract<LabelType, 'text' | 'floating'>;

export type InputFileProps = MaybeStringId
& Disableable
& Sizable
& WithConfigurableInputLabel<InputFileLabelType>
& Validity
& WithConfigurableHelperText;
1 change: 1 addition & 0 deletions packages/core/src/UI/Forms/InputFile/Types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '@/UI/Forms/InputFile/Types/InputFile';
33 changes: 33 additions & 0 deletions packages/core/src/UI/Forms/InputFile/UI/FoInputFile.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<template>
<FoInputFiles v-bind="props"
:multiple="false"
@upload:files="uploadFile"
/>
</template>

<script setup lang="ts">
import type { InputFileProps } from '@/UI/Forms';
import { FoInputFiles } from '@/UI/Forms';

const props = withDefaults(defineProps<InputFileProps>(), {
isValid: undefined,
});

const emit = defineEmits<{
(e: 'upload:file', file: File): void;
}>();

function uploadFile(files: FileList): void {
const file = files.item(0);

if (file === null) {
throw new Error('Received an upload file event but no file has been uploaded.');
}

if (files.length > 1) {
throw new Error('Received an upload file event with multiple files.');
}

emit('upload:file', file);
}
</script>
102 changes: 102 additions & 0 deletions packages/core/src/UI/Forms/InputFile/UI/FoInputFiles.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<template>
<div :class="floatingLabelClass"
:style="$attrs.style as StyleValue"
>
<FoLabel v-if="defaultLabel?.type === 'text'"
:for="id"
:component-name="componentName"
type="text"
:is-hidden="defaultLabel.isHidden"
>
{{ defaultLabel.text }}
</FoLabel>

<input :id="id"
type="file"
class="input"
:class="[sizeClass, validityClass]"
:disabled="isDisabled"
multiple
v-bind="reactiveOmit($attrs, 'style')"
@change="change"
>

<FoLabel v-if="defaultLabel?.type === 'floating'"
:for="id"
:component-name="componentName"
type="floating"
:is-hidden="defaultLabel.isHidden"
>
{{ defaultLabel.text }}
</FoLabel>

<FoHelperText v-if="inputFileHelperText !== undefined"
:position="inputFileHelperText.position"
>
{{ inputFileHelperText.text }}
</FoHelperText>
</div>
</template>

<script setup lang="ts">
import type { ComponentName } from '@/Lib';
import type { InputFileProps } from '@/UI/Forms/InputFile';
import type { StyleValue } from 'vue';
import { useFlyonUIVueAppConfig } from '@/Lib';
import { useFloatingLabel } from '@/Lib/UseFloatingLabel/Internal';
import { useElementId } from '@/Lib/UseIdentifiable/Internal';
import { useSize } from '@/Lib/UseSize/Internal';
import { useValidity } from '@/Lib/UseValidity/Internal';
import { FoHelperText, usePositionableHelperText } from '@/UI/Components/HelperText/Internal';
import { FoLabel, useLabel } from '@/UI/Components/Label/Internal';
import { reactiveOmit } from '@vueuse/core';

defineOptions({
inheritAttrs: false,
});

const props = withDefaults(defineProps<InputFileProps>(), {
isValid: undefined,
});

const emit = defineEmits<{
(e: 'upload:files', files: FileList): void;
}>();

const componentName: ComponentName = 'FoInputFile';
const { config } = useFlyonUIVueAppConfig();

const id = useElementId(() => props.id);

const defaultLabel = useLabel(
config,
componentName,
() => props.label,
);

const inputFileHelperText = usePositionableHelperText(
config,
componentName,
() => props.helperText,
);

const [
floatingLabelClass,
sizeClass,
validityClass,
] = [
useFloatingLabel(componentName, () => defaultLabel.value?.type),
useSize(config, componentName, () => props.size),
useValidity(() => props.isValid),
];

function change(event: Event): void {
const target = event.target;

if (target instanceof HTMLInputElement === false || target.files === null) {
throw new TypeError('The event does not come from an input element.');
}

emit('upload:files', target.files);
}
</script>
2 changes: 2 additions & 0 deletions packages/core/src/UI/Forms/InputFile/UI/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as FoInputFile } from '@/UI/Forms/InputFile/UI/FoInputFile.vue';
export { default as FoInputFiles } from '@/UI/Forms/InputFile/UI/FoInputFiles.vue';
2 changes: 2 additions & 0 deletions packages/core/src/UI/Forms/InputFile/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from '@/UI/Forms/InputFile/Types';
export * from '@/UI/Forms/InputFile/UI';
4 changes: 2 additions & 2 deletions packages/core/src/UI/Forms/Select/UI/FoSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div v-if="options.length"
:class="[
icon && useIcon && 'select',
icon === undefined && defaultLabel?.type === 'floating' && floatingLabelClass,
icon === undefined && floatingLabelClass,
]"
>
<FoIcon v-if="icon && useIcon"
Expand All @@ -25,7 +25,7 @@
v-model="selectedOption"
class="select"
:class="[
icon === undefined && defaultLabel?.type === 'floating' && floatingLabelClass,
icon === undefined && floatingLabelClass,
shapeClass,
sizeClass,
validityClass,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/UI/Forms/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from '@/UI/Forms/Checkbox';
export * from '@/UI/Forms/InputFile';
export * from '@/UI/Forms/InputText';
export * from '@/UI/Forms/Join';
export * from '@/UI/Forms/Radio';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,13 @@ export function useSidebarItems(): ComputedRef<SidebarItem[]> {
icon: 'mdi:checkbox-marked',
children: [],
},
{
text: 'Input File',
to: `${flyonUIVueNextPath}/forms/input-file`,
icon: 'mdi:file-upload',
children: [],
badge: _unreleasedBadge,
},
{
text: 'Input Text',
to: '/forms/input-text',
Expand Down
2 changes: 2 additions & 0 deletions packages/docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import SkeletonDocs from '@/Next/Components/Sk
import StatusDocs from '@/Next/Components/Status/StatusDocs.vue';
import BlockQuoteDocs from '@/Next/Content/BlockQuote/BlockQuoteDocs.vue';
import DividerDocs from '@/Next/Content/Divider/DividerDocs.vue';
import InputFileDocs from '@/Next/Forms/InputFile/InputFileDocs.vue';
import RadioDocs from '@/Next/Forms/Radio/RadioDocs.vue';
import RangeDocs from '@/Next/Forms/Range/RangeDocs.vue';
import DataTableDocs from '@/Next/Tables/DataTable/DataTableDocs.vue';
Expand Down Expand Up @@ -100,6 +101,7 @@ export default {
{ name: 'DataTableDocs', instance: DataTableDocs },
{ name: 'DiffDocs', instance: DiffDocs },
{ name: 'DividerDocs', instance: DividerDocs },
{ name: 'InputFileDocs', instance: InputFileDocs },
{ name: 'InputTextDocs', instance: InputTextDocs },
{ name: 'IconsDocs', instance: IconsDocs },
{ name: 'JoinDocs', instance: JoinDocs },
Expand Down
41 changes: 41 additions & 0 deletions packages/docs/Next/Forms/InputFile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Input file

### Default

<InputFileDocs section="default" />

### Labels

<InputFileDocs section="label" />

### Sizes

<InputFileDocs section="size" />

### Validation states

<InputFileDocs section="validation-state" />

## Illustrations

### Disabled

<InputFileDocs section="disabled" />

### Helper text

<InputFileDocs section="helper-text" />

### Multiple files

<InputFileDocs section="multiple-files" />

## Api

### Props

<InputFileDocs section="props" />

### Slots

<InputFileDocs section="slots" />
10 changes: 10 additions & 0 deletions packages/docs/Next/Forms/InputFile/DefaultInputFile.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<template>
<FoInputFile @upload:file="file = $event" />
</template>

<script setup lang="ts">
import { FoInputFile } from 'flyonui-vue';
import { ref } from 'vue';

const file = ref<File | null>(null);
</script>
12 changes: 12 additions & 0 deletions packages/docs/Next/Forms/InputFile/DisabledInputFile.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<template>
<FoInputFile is-disabled
@upload:file="file = $event"
/>
</template>

<script setup lang="ts">
import { FoInputFile } from 'flyonui-vue';
import { ref } from 'vue';

const file = ref<File | null>(null);
</script>
Loading