refactor(ui): add color and `reset` props to Input component

This commit is contained in:
upsiflu 2025-02-02 20:14:29 +01:00
parent ee22e02617
commit 6e69a74b75
3 changed files with 76 additions and 50 deletions

View File

@ -2,13 +2,12 @@
import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n';
import onKeyboardShortcut from '~/composables/onKeyboardShortcut';
import { color } from "~/composables/color.ts";
import { type ColorProps, type VariantProps, type DefaultProps, type RaisedProps, type PastelProps, color } from "~/composables/color.ts";
import Button from "~/components/ui/Button.vue"
import Layout from "~/components/ui/Layout.vue"
const props = withDefaults(
defineProps<{
const { icon, placeholder, ...props } = defineProps<{
icon?: string;
placeholder?: string;
password?: true;
@ -16,14 +15,10 @@ const props = withDefaults(
numeric?: true;
label?: string;
autofocus?: boolean;
raised?: boolean;
}>(),
{
raised: false, // Default value
}
);
const { icon, placeholder, ...restProps } = props;
reset?: () => void;
} & (ColorProps | DefaultProps | PastelProps )
& VariantProps
& RaisedProps>()
// TODO(A11y): Add `inputmode="numeric" pattern="[0-9]*"` to input if model type is number:
// https://technology.blog.gov.uk/2020/02/24/why-the-gov-uk-design-system-team-changed-the-input-type-for-numbers/
@ -37,9 +32,9 @@ onKeyboardShortcut('escape', () => showPassword.value = false)
// TODO: Implement `copy password` button?
const attributes = computed(() => ({
...(restProps.password && !showPassword.value? {type: 'password'} : {}),
...(restProps.search? {type: 'search'} : {}),
...(restProps.numeric? {type: 'numeric'} : {}),
...(props.password && !showPassword.value? {type: 'password'} : {}),
...(props.search? {type: 'search'} : {}),
...(props.numeric? {type: 'numeric'} : {}),
}))
const { t } = useI18n()
@ -47,10 +42,10 @@ const { t } = useI18n()
const input = ref()
onMounted(() => {
if (restProps.autofocus) input.value.focus();
if (props.autofocus) input.value.focus();
})
const model = defineModel<string|number>()
const model = defineModel<string|number>({ required: true })
</script>
<template>
@ -62,8 +57,8 @@ const model = defineModel<string|number>()
<slot name="label" />
</span>
<span v-if="restProps.label" class="label">
{{ restProps.label }}
<span v-if="props.label" class="label">
{{ props.label }}
</span>
<input
@ -82,7 +77,7 @@ const model = defineModel<string|number>()
</div>
<!-- Search -->
<div v-if="restProps.search" class="prefix">
<div v-if="props.search" class="prefix">
<i class="bi bi-search" />
</div>
@ -96,7 +91,7 @@ const model = defineModel<string|number>()
<!-- Password -->
<button
v-if="restProps.password"
v-if="props.password"
style="background:transparent; border:none; appearance:none;"
role="switch"
type="button"
@ -110,12 +105,22 @@ const model = defineModel<string|number>()
<!-- Search -->
<Button
solid primary
type="submit"
v-if="restProps.search"
v-if="props.search"
class="input-right search"
>
{{ t('components.Sidebar.link.search') }}
</Button>
<!-- Reset -->
<Button
ghost primary square-small
v-if="props.reset"
icon="bi-arrow-counterclockwise"
class="input-right reset"
:onClick="reset"
:title="t('components.library.EditForm.button.reset')"
/>
</Layout>
</template>

View File

@ -102,17 +102,23 @@
border-bottom-left-radius: 0;
}
}
&:has(>.search)>input {
padding-right: 140px;
}
> .show-password {
justify-content:center;
}
&:has(>.show-password)>input {
padding-right: 40px;
}
&:has(>.search)>input {
padding-right: 140px;
>.reset {
min-width: auto;
margin: 4px;
// Make button fit snuggly into rounded border
border-radius: 4px;
}
}

View File

@ -7,16 +7,21 @@ import Layout from "~/components/ui/Layout.vue"
import Spacer from "~/components/ui/Spacer.vue"
import Alert from "~/components/ui/Alert.vue"
const value = ref("Value")
const value = ref("Preset Value")
const search = ref("")
const user = ref("")
const password = ref("")
const reset = () => { console.log("Hello"); value.value = 'Original value' }
</script>
```ts
import Input from "~/components/ui/Input.vue"
import Input from "~/components/ui/Input.vue";
```
# Input
Inputs are areas in which users can enter information. In Funkwhale, these mostly take the form of search fields.
Inputs are areas in which users can enter a single-line text or a number. Several [presets](#presets) are available.
| Prop | Data type | Required? | Description |
| ------------- | --------- | --------- | --------------------------------------------------------------------------- |
@ -24,13 +29,17 @@ Inputs are areas in which users can enter information. In Funkwhale, these mostl
| `icon` | String | No | The [Bootstrap icon](https://icons.getbootstrap.com/) to show on the input. |
| `v-model` | String | Yes | The text entered in the input. |
You can link a user's input to form data by referencing the data in a `v-model` directive.
Link a user's input to form data by referencing the data in a `v-model` of type `string`.
```ts
const value = ref("Preset Value");
```
```vue-html{2}
<Input v-model="value" placeholder="Your favorite animal" />
```
<Input placeholder="Your favorite animal" />
<Input v-model="value" placeholder="Your favorite animal" />
## Input icons
@ -45,14 +54,14 @@ Add a [Bootstrap icon](https://icons.getbootstrap.com/) to an input to make its
## Label slot
```vue-html{2-4}
<Input>
<Input v-model="user">
<template #label>
User name
</template>
</Input>
```
<Input>
<Input v-model="user">
<template #label>
User name
</template>
@ -61,10 +70,10 @@ Add a [Bootstrap icon](https://icons.getbootstrap.com/) to an input to make its
If you just have a string, we have a convenience prop, so instead you can write:
```vue-html
<Input label="User name" />
<Input v-model="user" label="User name" />
```
<Input label="User name" />
<Input v-model="user" label="User name" />
## Input-right slot
@ -78,29 +87,33 @@ You can add a template on the right-hand side of the input to guide the user's i
</Input>
```
<Input placeholder="Search">
<Input v-model="search" placeholder="Search">
<template #input-right>
suffix
</template>
</Input>
## Color
See [Button](./button.md#button-colors) for a detailed overview of available props.
## Presets
### Search
```vue-html
<Input search />
<Input search v-model="search" />
```
<Input search />
<Input search v-model="search" />
### Password
```vue-html
<Spacer :size="64" />
<Layout form stack>
<Input label="User name" />
<Input password label="Password" />
<Input v-model="user" label="User name" />
<Input password v-model="password" label="Password" />
<Layout flex>
<Button primary> Submit </Button>
<Button> Cancel </Button>
@ -110,8 +123,8 @@ You can add a template on the right-hand side of the input to guide the user's i
<Spacer :size="64" />
<Layout form stack>
<Input label="User name" />
<Input password label="Password" />
<Input v-model="user" label="User name" />
<Input password v-model="password" label="Password" />
<Layout flex>
<Button primary> Submit </Button>
<Button> Cancel </Button>
@ -124,28 +137,30 @@ We use the spacer to simulate the baseline alignment on page layouts (64px betwe
:::
## Value
### Add a reset option
```vue-html
<Input v-model="value"/>
<Input v-model="value"/>
<Input
v-model="value"
:reset="() => { value = 'Original value' }">
</Input>
```
<Layout flex>
<Input auto v-model="value"/>
<Input auto v-model="value"/>
</Layout>
<Input
v-model="value"
:reset="() => { value = 'Original value' }">
</Input>
## Fallthrough attributes
If you add attributes that are no props, they will be added to the component:
```vue-html
<Input required
<Input v-model="password" required
field-id="password-field"
/>
```
<Input required
<Input v-model="password" required
field-id="password-field"
/>