refactor: migrate all input fields to StyledInput (#5306)

* feat: StyledInput component

* migrate: auth pages to styledInput

* migrate: search/filter inputs

* migrate: dashboard inputs

* migrate: app frontend

* migrate: search related inputs

* migrate: all of app-frontend

* fix: missing inputs on app-frontend

* migrate: frontend

* feat: multiline

* migrate: textareas

* fix: storybook use text-primary

* fix: lint

* fix: merge conflict

* feat: cleanup
This commit is contained in:
Calum H.
2026-02-09 14:57:31 +00:00
committed by GitHub
parent 90438a1ad5
commit 37eac92329
108 changed files with 1670 additions and 1479 deletions

View File

@@ -196,14 +196,7 @@
width: 15rem;
}
> :where(
input + *,
.input-group + *,
.textarea-wrapper + *,
.chips + *,
.resizable-textarea-wrapper + *,
.input-div + *
) {
> :where(input + *, .input-group + *, .chips + *, .input-div + *) {
&:not(:empty) {
margin-block-start: var(--spacing-card-md);
}
@@ -643,31 +636,6 @@ tr.button-transparent {
}
}
.textarea-wrapper {
display: flex;
flex-direction: column;
align-items: stretch;
textarea {
border-radius: var(--size-rounded-sm);
flex: 1;
overflow-y: auto;
resize: none;
max-width: 100%;
}
}
.resizable-textarea-wrapper {
display: block;
textarea {
border-radius: var(--size-rounded-sm);
min-height: 10rem;
width: calc(100% - var(--spacing-card-lg) - var(--spacing-card-sm));
resize: vertical;
}
}
.error {
display: flex;
flex-direction: column;

View File

@@ -20,12 +20,12 @@
<label for="days" class="flex flex-col gap-1">
<span class="text-lg font-semibold text-contrast"> Days to credit </span>
</label>
<input
<StyledInput
id="days"
v-model.number="days"
class="w-32"
v-model="days"
wrapper-class="w-32"
type="number"
min="1"
:min="1"
autocomplete="off"
/>
</div>
@@ -36,11 +36,10 @@
<span class="text-lg font-semibold text-contrast"> Node hostnames </span>
</label>
<div class="flex items-center gap-2">
<input
<StyledInput
id="node-input"
v-model="nodeInput"
class="w-32"
type="text"
wrapper-class="w-32"
autocomplete="off"
/>
<ButtonStyled color="blue" color-fill="text">
@@ -93,14 +92,13 @@
class="text-muted flex flex-col gap-2 rounded-lg border border-surface-5 bg-button-bg p-4"
>
<span>Hi {user.name},</span>
<div class="textarea-wrapper">
<textarea
id="message-batch"
v-model="message"
rows="3"
class="w-full overflow-hidden !bg-surface-3"
/>
</div>
<StyledInput
id="message-batch"
v-model="message"
multiline
:rows="3"
input-class="!bg-surface-3"
/>
<span>
To make up for it, we've added {{ days }} day{{ pluralize(days) }} to your Modrinth
Servers subscription.
@@ -137,6 +135,7 @@ import {
Combobox,
injectNotificationManager,
NewModal,
StyledInput,
TagItem,
Toggle,
} from '@modrinth/ui'

View File

@@ -25,15 +25,14 @@
</span>
<span>Server IDs (one per line or comma-separated.)</span>
</label>
<div class="textarea-wrapper">
<textarea
id="server-ids"
v-model="serverIdsInput"
rows="4"
class="w-full bg-surface-3"
placeholder="123e4569-e89b-12d3-a456-426614174005&#10;123e9569-e89b-12d3-a456-413678919876"
/>
</div>
<StyledInput
id="server-ids"
v-model="serverIdsInput"
multiline
:rows="4"
input-class="bg-surface-3"
placeholder="123e4569-e89b-12d3-a456-426614174005&#10;123e9569-e89b-12d3-a456-413678919876"
/>
<span v-if="parsedServerIds.length" class="text-sm text-secondary">
{{ parsedServerIds.length }} server{{ parsedServerIds.length === 1 ? '' : 's' }} selected
</span>
@@ -49,11 +48,10 @@
<span>Add nodes to transfer (comma or space-separated).</span>
</label>
<div class="flex items-center gap-2">
<input
<StyledInput
id="node-input"
v-model="nodeInput"
class="w-64"
type="text"
wrapper-class="w-64"
autocomplete="off"
placeholder="us-vin200, us-vin201"
@keydown.enter.prevent="addNodes"
@@ -88,11 +86,10 @@
<span class="text-lg font-semibold text-contrast">Tag transferred nodes</span>
<span>Optional tag to add to the transferred nodes.</span>
</label>
<input
<StyledInput
id="tag-nodes"
v-model="tagNodes"
class="max-w-[12rem]"
type="text"
wrapper-class="max-w-[12rem]"
autocomplete="off"
/>
</div>
@@ -117,11 +114,10 @@
<span>Optional preferred node tags for node selection.</span>
</label>
<div class="flex items-center gap-2">
<input
<StyledInput
id="tag-input"
v-model="tagInput"
class="w-40"
type="text"
wrapper-class="w-40"
autocomplete="off"
placeholder="ovh-gen4"
@keydown.enter.prevent="addTag"
@@ -151,11 +147,11 @@
:format-label="(item) => scheduleOptionLabels[item]"
:capitalize="false"
/>
<input
<StyledInput
v-if="scheduleOption === 'later'"
v-model="scheduledDate"
type="datetime-local"
class="mt-2 max-w-[16rem]"
wrapper-class="mt-2 max-w-[16rem]"
autocomplete="off"
/>
</div>
@@ -168,15 +164,14 @@
</span>
<span>Provide a reason for this transfer batch.</span>
</label>
<div class="textarea-wrapper">
<textarea
id="reason"
v-model="reason"
rows="2"
class="w-full bg-surface-3"
placeholder="Node maintenance scheduled"
/>
</div>
<StyledInput
id="reason"
v-model="reason"
multiline
:rows="2"
input-class="bg-surface-3"
placeholder="Node maintenance scheduled"
/>
</div>
<div class="flex gap-2">
@@ -205,6 +200,7 @@ import {
Combobox,
injectNotificationManager,
NewModal,
StyledInput,
TagItem,
Toggle,
} from '@modrinth/ui'

View File

@@ -13,10 +13,13 @@
size="small"
/>
</div>
<div class="iconified-input w-full">
<SearchIcon aria-hidden="true" />
<input v-model="searchQuery" type="text" placeholder="Search versions" />
</div>
<StyledInput
v-model="searchQuery"
:icon="SearchIcon"
type="text"
placeholder="Search versions"
wrapper-class="w-full"
/>
<div
class="flex h-72 select-none flex-col gap-3 overflow-y-auto rounded-xl border border-solid border-surface-5 p-3 py-4"
>
@@ -64,7 +67,7 @@
<script lang="ts" setup>
import type { Labrinth } from '@modrinth/api-client'
import { SearchIcon } from '@modrinth/assets'
import { ButtonStyled, Chips } from '@modrinth/ui'
import { ButtonStyled, Chips, StyledInput } from '@modrinth/ui'
import { useMagicKeys } from '@vueuse/core'
import { computed, nextTick, onMounted, ref } from 'vue'

View File

@@ -16,26 +16,24 @@
<span class="font-semibold text-contrast">
Version number <span class="text-red">*</span>
</span>
<input
<StyledInput
id="version-number"
v-model="draftVersion.version_number"
:disabled="isUploading"
placeholder="Enter version number, e.g. 1.2.3-alpha.1"
type="text"
autocomplete="off"
maxlength="32"
:maxlength="32"
/>
<span> The version number differentiates this specific version from others. </span>
</div>
<div class="flex flex-col gap-2">
<span class="font-semibold text-contrast"> Version subtitle </span>
<input
<StyledInput
id="version-number"
v-model="draftVersion.name"
placeholder="Enter subtitle..."
type="text"
autocomplete="off"
maxlength="256"
:maxlength="256"
:disabled="isUploading"
/>
</div>
@@ -55,7 +53,7 @@
</template>
<script lang="ts" setup>
import { Chips, MarkdownEditor } from '@modrinth/ui'
import { Chips, MarkdownEditor, StyledInput } from '@modrinth/ui'
import { useImageUpload } from '~/composables/image-upload.ts'
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'

View File

@@ -9,11 +9,10 @@
<span class="text-brand-red">*</span>
</span>
</label>
<input
<StyledInput
id="name"
v-model="name"
type="text"
maxlength="64"
:maxlength="64"
:placeholder="formatMessage(messages.namePlaceholder)"
autocomplete="off"
:disabled="hasHitLimit"
@@ -26,15 +25,14 @@
}}</span>
<span>{{ formatMessage(messages.summaryDescription) }}</span>
</label>
<div class="textarea-wrapper">
<textarea
id="additional-information"
v-model="description"
maxlength="256"
:placeholder="formatMessage(messages.summaryPlaceholder)"
:disabled="hasHitLimit"
/>
</div>
<StyledInput
id="additional-information"
v-model="description"
multiline
:maxlength="256"
:placeholder="formatMessage(messages.summaryPlaceholder)"
:disabled="hasHitLimit"
/>
</div>
<p class="m-0">
{{ formatMessage(messages.collectionInfo, { count: projectIds.length }) }}
@@ -64,6 +62,7 @@ import {
defineMessages,
injectNotificationManager,
NewModal,
StyledInput,
useVIntl,
} from '@modrinth/ui'
@@ -172,10 +171,6 @@ defineExpose({
width: 100%;
}
textarea {
min-height: 5rem;
}
.input-group {
margin-top: var(--gap-md);
}

View File

@@ -9,15 +9,14 @@
<span class="text-brand-red">*</span>
</span>
</label>
<input
<StyledInput
id="name"
v-model="name"
type="text"
maxlength="64"
:maxlength="64"
:placeholder="formatMessage(messages.namePlaceholder)"
autocomplete="off"
:disabled="hasHitLimit"
@input="updateSlug"
@update:model-value="updateSlug"
/>
</div>
<div class="flex flex-col gap-2">
@@ -29,14 +28,13 @@
</label>
<div class="text-input-wrapper">
<div class="text-input-wrapper__before">https://modrinth.com/organization/</div>
<input
<StyledInput
id="slug"
v-model="slug"
type="text"
maxlength="64"
:maxlength="64"
autocomplete="off"
:disabled="hasHitLimit"
@input="setManualSlug"
@update:model-value="setManualSlug"
/>
</div>
</div>
@@ -48,15 +46,14 @@
</span>
<span>{{ formatMessage(messages.summaryDescription) }}</span>
</label>
<div class="textarea-wrapper">
<textarea
id="additional-information"
v-model="description"
maxlength="256"
:placeholder="formatMessage(messages.summaryPlaceholder)"
:disabled="hasHitLimit"
/>
</div>
<StyledInput
id="additional-information"
v-model="description"
multiline
:maxlength="256"
:placeholder="formatMessage(messages.summaryPlaceholder)"
:disabled="hasHitLimit"
/>
</div>
<p class="m-0">
{{ formatMessage(messages.ownershipInfo) }}
@@ -87,6 +84,7 @@ import {
defineMessages,
injectNotificationManager,
NewModal,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import { ref } from 'vue'
@@ -214,10 +212,6 @@ defineExpose({
width: 100%;
}
textarea {
min-height: 5rem;
}
.input-group {
margin-top: var(--gap-md);
}

View File

@@ -9,15 +9,14 @@
<span class="text-brand-red">*</span>
</span>
</label>
<input
<StyledInput
id="name"
v-model="name"
type="text"
maxlength="64"
:maxlength="64"
:placeholder="formatMessage(messages.namePlaceholder)"
autocomplete="off"
:disabled="hasHitLimit"
@input="updatedName()"
@update:model-value="updatedName()"
/>
</div>
<div class="flex flex-col gap-2">
@@ -29,14 +28,13 @@
</label>
<div class="text-input-wrapper">
<div class="text-input-wrapper__before">https://modrinth.com/project/</div>
<input
<StyledInput
id="slug"
v-model="slug"
type="text"
maxlength="64"
:maxlength="64"
autocomplete="off"
:disabled="hasHitLimit"
@input="manualSlug = true"
@update:model-value="manualSlug = true"
/>
</div>
</div>
@@ -65,15 +63,14 @@
</span>
<span>{{ formatMessage(messages.summaryDescription) }}</span>
</label>
<div class="textarea-wrapper">
<textarea
id="additional-information"
v-model="description"
maxlength="256"
:placeholder="formatMessage(messages.summaryPlaceholder)"
:disabled="hasHitLimit"
/>
</div>
<StyledInput
id="additional-information"
v-model="description"
multiline
:maxlength="256"
:placeholder="formatMessage(messages.summaryPlaceholder)"
:disabled="hasHitLimit"
/>
</div>
<div class="flex justify-end gap-2">
<ButtonStyled class="w-24">
@@ -102,6 +99,7 @@ import {
defineMessages,
injectNotificationManager,
NewModal,
StyledInput,
useVIntl,
} from '@modrinth/ui'

View File

@@ -2,17 +2,17 @@
<div class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<div class="relative flex-1">
<input
<StyledInput
ref="amountInput"
:value="modelValue"
:model-value="modelValue"
type="number"
step="0.01"
:step="0.01"
:min="minAmount"
:max="safeMaxAmount"
:disabled="isDisabled"
:placeholder="formatMessage(formFieldPlaceholders.amountPlaceholder)"
class="w-full rounded-[14px] bg-surface-4 py-2.5 pl-4 pr-4 text-contrast placeholder:text-secondary"
@input="handleInput"
wrapper-class="w-full"
@update:model-value="handleStyledInput"
/>
</div>
<Combobox
@@ -54,10 +54,11 @@ import {
Combobox,
commonMessages,
formFieldPlaceholders,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import { formatMoney } from '@modrinth/utils'
import { computed, nextTick, ref, watch } from 'vue'
import { computed, ref, watch } from 'vue'
const props = withDefaults(
defineProps<{
@@ -81,7 +82,7 @@ const emit = defineEmits<{
}>()
const { formatMessage } = useVIntl()
const amountInput = ref<HTMLInputElement | null>(null)
const amountInput = ref<InstanceType<typeof StyledInput> | null>(null)
const safeMaxAmount = computed(() => {
return Math.max(0, props.maxAmount)
@@ -101,26 +102,19 @@ const isAboveMaximum = computed(() => {
return props.modelValue !== undefined && props.modelValue > safeMaxAmount.value
})
async function setMaxAmount() {
function setMaxAmount() {
const maxValue = safeMaxAmount.value
emit('update:modelValue', maxValue)
await nextTick()
if (amountInput.value) {
amountInput.value.value = maxValue.toFixed(2)
}
}
function handleInput(event: Event) {
const input = event.target as HTMLInputElement
const value = input.value
function handleStyledInput(val: string | number) {
const value = String(val)
if (value && value.includes('.')) {
const parts = value.split('.')
if (parts[1] && parts[1].length > 2) {
const rounded = Math.floor(parseFloat(value) * 100) / 100
emit('update:modelValue', rounded)
input.value = rounded.toString()
return
}
}
@@ -131,14 +125,10 @@ function handleInput(event: Event) {
watch(
() => props.modelValue,
async (newAmount) => {
(newAmount) => {
if (newAmount !== undefined && newAmount !== null) {
if (newAmount > safeMaxAmount.value) {
emit('update:modelValue', safeMaxAmount.value)
await nextTick()
if (amountInput.value) {
amountInput.value.value = safeMaxAmount.value.toFixed(2)
}
} else if (newAmount < 0) {
emit('update:modelValue', 0)
}

View File

@@ -41,11 +41,10 @@
>
</label>
<div class="flex flex-row gap-2">
<input
<StyledInput
v-model="venmoHandle"
type="text"
:placeholder="formatMessage(messages.venmoHandlePlaceholder)"
class="w-full rounded-[14px] bg-surface-4 px-4 py-3 text-contrast placeholder:text-secondary sm:py-2.5"
wrapper-class="w-full"
/>
<ButtonStyled color="brand">
<button
@@ -113,6 +112,7 @@ import {
formFieldLabels,
IntlFormatted,
normalizeChildren,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import { useDebounceFn } from '@vueuse/core'

View File

@@ -56,21 +56,19 @@
</span>
<div class="flex flex-col gap-3 sm:flex-row sm:gap-4">
<div class="flex flex-1 flex-col gap-2.5">
<input
<StyledInput
v-model="formData.bankAccountOwnerFirstName"
type="text"
:placeholder="formatMessage(formFieldPlaceholders.firstNamePlaceholder)"
autocomplete="given-name"
class="w-full rounded-[14px] bg-surface-4 px-4 py-3 text-contrast placeholder:text-secondary sm:py-2.5"
wrapper-class="w-full"
/>
</div>
<div class="flex flex-1 flex-col gap-2.5">
<input
<StyledInput
v-model="formData.bankAccountOwnerLastName"
type="text"
:placeholder="formatMessage(formFieldPlaceholders.lastNamePlaceholder)"
autocomplete="family-name"
class="w-full rounded-[14px] bg-surface-4 px-4 py-3 text-contrast placeholder:text-secondary sm:py-2.5"
wrapper-class="w-full"
/>
</div>
</div>
@@ -93,13 +91,12 @@
class="h-10"
/>
<input
<StyledInput
v-else
v-model="formData.bankName"
type="text"
:placeholder="formatMessage(formFieldPlaceholders.bankNamePlaceholder)"
autocomplete="off"
class="w-full rounded-[14px] bg-surface-4 px-4 py-3 text-contrast placeholder:text-secondary sm:py-2.5"
wrapper-class="w-full"
/>
</div>
@@ -111,14 +108,14 @@
</span>
</label>
<input
<StyledInput
v-if="['text', 'email', 'tel'].includes(field.type)"
v-model="formData[field.name]"
:type="field.type"
:type="field.type === 'tel' ? undefined : field.type === 'text' ? undefined : field.type"
:inputmode="field.type === 'tel' ? 'tel' : undefined"
:placeholder="field.placeholder ? formatMessage(field.placeholder) : undefined"
:pattern="field.pattern"
:autocomplete="field.autocomplete || 'off'"
class="w-full rounded-[14px] bg-surface-4 px-4 py-3 text-contrast placeholder:text-secondary sm:py-2.5"
wrapper-class="w-full"
/>
<Combobox
@@ -134,11 +131,11 @@
class="h-10"
/>
<input
<StyledInput
v-else-if="field.type === 'date'"
v-model="formData[field.name]"
type="date"
class="w-full rounded-[14px] bg-surface-4 px-4 py-2.5 text-contrast placeholder:text-secondary"
wrapper-class="w-full"
/>
<span v-if="field.helpText" class="text-sm text-secondary">
@@ -162,12 +159,11 @@
<span v-if="dynamicDocumentNumberField.required" class="text-red">*</span>
</span>
</label>
<input
<StyledInput
v-model="formData.documentNumber"
:type="dynamicDocumentNumberField.type"
:placeholder="dynamicDocumentNumberField.placeholder"
autocomplete="off"
class="w-full rounded-[14px] bg-surface-4 px-4 py-3 text-contrast placeholder:text-secondary sm:py-2.5"
wrapper-class="w-full"
/>
</div>
</div>
@@ -244,6 +240,7 @@ import {
getCurrencyIcon,
IntlFormatted,
normalizeChildren,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import { useDebounceFn } from '@vueuse/core'

View File

@@ -32,12 +32,11 @@
<span class="text-red">*</span>
</span>
</label>
<input
<StyledInput
v-model="formData.businessName"
type="text"
:placeholder="formatMessage(formFieldPlaceholders.businessNamePlaceholder)"
autocomplete="organization"
class="w-full rounded-[14px] bg-surface-4 px-4 py-2.5 text-contrast placeholder:text-secondary"
wrapper-class="w-full"
/>
</div>
@@ -48,12 +47,12 @@
<span class="text-red">*</span>
</span>
</label>
<input
<StyledInput
v-model="formData.email"
type="email"
:placeholder="formatMessage(formFieldPlaceholders.emailPlaceholder)"
autocomplete="email"
class="w-full rounded-[14px] bg-surface-4 px-4 py-2.5 text-contrast placeholder:text-secondary"
wrapper-class="w-full"
/>
</div>
@@ -66,12 +65,11 @@
<span class="text-red">*</span>
</span>
</label>
<input
<StyledInput
v-model="formData.firstName"
type="text"
:placeholder="formatMessage(formFieldPlaceholders.firstNamePlaceholder)"
autocomplete="given-name"
class="w-full rounded-[14px] bg-surface-4 px-4 py-3 text-contrast placeholder:text-secondary sm:py-2.5"
wrapper-class="w-full"
/>
</div>
<div class="flex flex-1 flex-col gap-2.5">
@@ -81,12 +79,11 @@
<span class="text-red">*</span>
</span>
</label>
<input
<StyledInput
v-model="formData.lastName"
type="text"
:placeholder="formatMessage(formFieldPlaceholders.lastNamePlaceholder)"
autocomplete="family-name"
class="w-full rounded-[14px] bg-surface-4 px-4 py-3 text-contrast placeholder:text-secondary sm:py-2.5"
wrapper-class="w-full"
/>
</div>
</div>
@@ -98,12 +95,12 @@
<span class="text-red">*</span>
</span>
</label>
<input
<StyledInput
v-model="formData.dateOfBirth"
type="date"
:max="maxDate"
autocomplete="bday"
class="w-full rounded-[14px] bg-surface-4 px-4 py-2.5 text-contrast placeholder:text-secondary"
wrapper-class="w-full"
/>
</div>
</div>
@@ -115,12 +112,11 @@
<span class="text-red">*</span>
</span>
</label>
<input
<StyledInput
v-model="formData.physicalAddress.address1"
type="text"
:placeholder="formatMessage(formFieldPlaceholders.addressPlaceholder)"
autocomplete="address-line1"
class="w-full rounded-[14px] bg-surface-4 px-4 py-2.5 text-contrast placeholder:text-secondary"
wrapper-class="w-full"
/>
</div>
@@ -130,12 +126,11 @@
{{ formatMessage(formFieldLabels.addressLine2) }}
</span>
</label>
<input
<StyledInput
v-model="formData.physicalAddress.address2"
type="text"
:placeholder="formatMessage(formFieldPlaceholders.address2Placeholder)"
autocomplete="address-line2"
class="w-full rounded-[14px] bg-surface-4 px-4 py-2.5 text-contrast placeholder:text-secondary"
wrapper-class="w-full"
/>
</div>
@@ -147,12 +142,11 @@
<span class="text-red">*</span>
</span>
</label>
<input
<StyledInput
v-model="formData.physicalAddress.city"
type="text"
:placeholder="formatMessage(formFieldPlaceholders.cityPlaceholder)"
autocomplete="address-level2"
class="w-full rounded-[14px] bg-surface-4 px-4 py-3 text-contrast placeholder:text-secondary sm:py-2.5"
wrapper-class="w-full"
/>
</div>
<div class="flex flex-1 flex-col gap-2.5">
@@ -170,13 +164,12 @@
searchable
search-placeholder="Search subdivisions..."
/>
<input
<StyledInput
v-else
v-model="formData.physicalAddress.state"
type="text"
:placeholder="formatMessage(formFieldPlaceholders.statePlaceholder)"
autocomplete="address-level1"
class="w-full rounded-[14px] bg-surface-4 px-4 py-3 text-contrast placeholder:text-secondary sm:py-2.5"
wrapper-class="w-full"
/>
</div>
</div>
@@ -189,12 +182,11 @@
<span class="text-red">*</span>
</span>
</label>
<input
<StyledInput
v-model="formData.physicalAddress.zip"
type="text"
:placeholder="formatMessage(formFieldPlaceholders.postalCodePlaceholder)"
autocomplete="postal-code"
class="w-full rounded-[14px] bg-surface-4 px-4 py-3 text-contrast placeholder:text-secondary sm:py-2.5"
wrapper-class="w-full"
/>
</div>
<div class="flex flex-1 flex-col gap-2.5">
@@ -224,6 +216,7 @@ import {
defineMessages,
formFieldLabels,
formFieldPlaceholders,
StyledInput,
useVIntl,
} from '@modrinth/ui'
// TODO: Switch to using Muralpay's improved endpoint when it's available.

View File

@@ -39,12 +39,12 @@
>{{ formatMessage(formFieldLabels.email) }} <span class="text-red">*</span></span
>
</label>
<input
<StyledInput
v-model="deliveryEmail"
type="email"
:placeholder="formatMessage(formFieldPlaceholders.emailPlaceholder)"
autocomplete="email"
class="w-full rounded-[14px] bg-surface-4 px-4 py-3 text-contrast placeholder:text-secondary sm:py-2.5"
wrapper-class="w-full"
/>
</div>
@@ -149,19 +149,17 @@
<div v-if="showGiftCardSelector && useFixedDenominations" class="flex flex-col gap-2.5">
<template v-if="useDenominationSuggestions">
<div class="iconified-input w-full">
<SearchIcon aria-hidden="true" />
<input
v-model.number="denominationSearchInput"
type="number"
step="0.01"
:min="0"
:disabled="effectiveMinAmount > roundedMaxAmount"
:placeholder="formatMessage(messages.enterDenominationPlaceholder)"
class="!bg-surface-4"
@input="hasTouchedSuggestions = true"
/>
</div>
<StyledInput
v-model="denominationSearchInput"
type="number"
:icon="SearchIcon"
:step="0.01"
:min="0"
:disabled="effectiveMinAmount > roundedMaxAmount"
:placeholder="formatMessage(messages.enterDenominationPlaceholder)"
wrapper-class="w-full"
@update:model-value="hasTouchedSuggestions = true"
/>
<Transition
enter-active-class="transition-opacity duration-200 ease-out"
enter-from-class="opacity-0"
@@ -350,6 +348,7 @@ import {
IntlFormatted,
normalizeChildren,
paymentMethodMessages,
StyledInput,
useDebugLogger,
useVIntl,
} from '@modrinth/ui'

View File

@@ -149,13 +149,13 @@
:disabled="false"
:heading-buttons="false"
/>
<textarea
<StyledInput
v-else
v-model="message"
type="text"
class="bg-bg-input h-[400px] w-full rounded-lg border border-solid border-surface-5 px-3 py-2 font-mono text-base"
multiline
placeholder="No message generated."
autocomplete="off"
input-class="h-[400px] font-mono"
@input="persistState"
/>
</div>
@@ -298,13 +298,12 @@
<span v-if="input.required" class="required">*</span>
</span>
</label>
<input
<StyledInput
:id="`input-${getActionId(action)}-${inputIndex}`"
v-model="textInputValues[`${getActionId(action)}-${inputIndex}`]"
type="text"
:placeholder="input.placeholder"
autocomplete="off"
@input="persistState"
@update:model-value="persistState"
/>
</template>
</div>
@@ -471,6 +470,7 @@ import {
MarkdownEditor,
OverflowMenu,
type OverflowMenuOption,
StyledInput,
useDebugLogger,
} from '@modrinth/ui'
import {

View File

@@ -34,35 +34,32 @@
<label for="proof">
<span class="label__title">Proof</span>
</label>
<input
<StyledInput
id="proof"
v-model="(modPackData[currentIndex] as ModerationUnknownModpackItem).proof"
type="text"
autocomplete="off"
placeholder="Enter proof of status..."
@input="persistAll()"
@update:model-value="persistAll()"
/>
<label for="link">
<span class="label__title">Link</span>
</label>
<input
<StyledInput
id="link"
v-model="(modPackData[currentIndex] as ModerationUnknownModpackItem).url"
type="text"
autocomplete="off"
placeholder="Enter link of project..."
@input="persistAll()"
@update:model-value="persistAll()"
/>
<label for="title">
<span class="label__title">Title</span>
</label>
<input
<StyledInput
id="title"
v-model="(modPackData[currentIndex] as ModerationUnknownModpackItem).title"
type="text"
autocomplete="off"
placeholder="Enter title of project..."
@input="persistAll()"
@update:model-value="persistAll()"
/>
</div>
</div>
@@ -146,7 +143,7 @@
<script setup lang="ts">
import { LeftArrowIcon, RightArrowIcon } from '@modrinth/assets'
import { ButtonStyled } from '@modrinth/ui'
import { ButtonStyled, StyledInput } from '@modrinth/ui'
import type {
ModerationFlameModpackItem,
ModerationJudgements,

View File

@@ -63,19 +63,17 @@
</nav>
<div class="flex flex-shrink-0 items-center gap-2">
<div class="iconified-input w-full sm:w-[280px]">
<SearchIcon aria-hidden="true" class="!text-secondary" />
<input
id="search-folder"
:value="searchQuery"
type="search"
name="search"
autocomplete="off"
class="h-10 w-full rounded-[14px] border-0 bg-surface-4 text-sm"
placeholder="Search files"
@input="$emit('update:searchQuery', ($event.target as HTMLInputElement).value)"
/>
</div>
<StyledInput
id="search-folder"
:model-value="searchQuery"
:icon="SearchIcon"
type="search"
name="search"
autocomplete="off"
placeholder="Search files"
wrapper-class="w-full sm:w-[280px]"
@update:model-value="$emit('update:searchQuery', $event)"
/>
<ButtonStyled type="outlined">
<OverflowMenu
@@ -128,7 +126,7 @@ import {
SearchIcon,
UploadIcon,
} from '@modrinth/assets'
import { ButtonStyled, OverflowMenu } from '@modrinth/ui'
import { ButtonStyled, OverflowMenu, StyledInput } from '@modrinth/ui'
defineProps<{
breadcrumbSegments: string[]

View File

@@ -3,14 +3,12 @@
<form class="flex flex-col gap-4 md:w-[600px]" @submit.prevent="handleSubmit">
<div class="flex flex-col gap-2">
<div class="font-semibold text-contrast">Name</div>
<input
<StyledInput
ref="createInput"
v-model="itemName"
autofocus
type="text"
class="bg-bg-input w-full rounded-lg p-4"
wrapper-class="bg-bg-input w-full rounded-lg p-4"
:placeholder="`e.g. ${type === 'file' ? 'config.yml' : 'plugins'}`"
required
/>
<div v-if="submitted && error" class="text-red">{{ error }}</div>
</div>
@@ -34,7 +32,7 @@
<script setup lang="ts">
import { PlusIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, NewModal } from '@modrinth/ui'
import { ButtonStyled, NewModal, StyledInput } from '@modrinth/ui'
import { computed, nextTick, ref } from 'vue'
const props = defineProps<{

View File

@@ -2,14 +2,12 @@
<NewModal ref="modal" :header="`Moving ${item?.name}`">
<form class="flex flex-col gap-4 md:w-[600px]" @submit.prevent="handleSubmit">
<div class="flex flex-col gap-2">
<input
<StyledInput
ref="destinationInput"
v-model="destination"
autofocus
type="text"
class="bg-bg-input w-full rounded-lg p-4"
wrapper-class="bg-bg-input w-full rounded-lg p-4"
placeholder="e.g. /mods/modname"
required
/>
</div>
<div class="flex items-center gap-2 text-nowrap">
@@ -38,7 +36,7 @@
<script setup lang="ts">
import { ArrowBigUpDashIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, NewModal } from '@modrinth/ui'
import { ButtonStyled, NewModal, StyledInput } from '@modrinth/ui'
import { computed, nextTick, ref } from 'vue'
const destinationInput = ref<HTMLInputElement | null>(null)

View File

@@ -3,13 +3,11 @@
<form class="flex flex-col gap-4 md:w-[600px]" @submit.prevent="handleSubmit">
<div class="flex flex-col gap-2">
<div class="font-semibold text-contrast">Name</div>
<input
<StyledInput
ref="renameInput"
v-model="itemName"
autofocus
type="text"
class="bg-bg-input w-full rounded-lg p-4"
required
wrapper-class="bg-bg-input w-full rounded-lg p-4"
/>
<div v-if="submitted && error" class="text-red">{{ error }}</div>
</div>
@@ -33,7 +31,7 @@
<script setup lang="ts">
import { EditIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, NewModal } from '@modrinth/ui'
import { ButtonStyled, NewModal, StyledInput } from '@modrinth/ui'
import { computed, nextTick, ref } from 'vue'
const props = defineProps<{

View File

@@ -33,16 +33,14 @@
</li>
</ol>
<p v-else class="mb-1 mt-0">Copy and paste the direct download URL of a .zip file.</p>
<input
<StyledInput
ref="urlInput"
v-model="url"
autofocus
:disabled="submitted"
type="text"
data-1p-ignore
data-lpignore="true"
data-protonpass-ignore="true"
required
:placeholder="
cf
? 'https://www.curseforge.com/minecraft/modpacks/.../files/6412259'
@@ -81,6 +79,7 @@ import {
injectModrinthServerContext,
injectNotificationManager,
NewModal,
StyledInput,
} from '@modrinth/ui'
import { ModrinthServersFetchError } from '@modrinth/utils'
import { computed, nextTick, ref } from 'vue'

View File

@@ -2,15 +2,16 @@
<div class="contents">
<div class="flex items-center gap-4">
<div class="relative w-full">
<input
<StyledInput
v-model="searchInput"
type="text"
type="search"
:icon="SearchIcon"
placeholder="Search logs"
class="h-12 !w-full !pl-10 !pr-48"
wrapper-class="h-12 !w-full"
input-class="!pr-48"
:disabled="loading"
@keydown.escape="clearSearch"
/>
<SearchIcon class="absolute left-4 top-1/2 -translate-y-1/2" />
<ButtonStyled v-if="searchInput && !loading" @click="clearSearch">
<button class="absolute right-2 top-1/2 -translate-y-1/2">
<XIcon class="h-5 w-5" />
@@ -298,7 +299,7 @@
<script setup lang="ts">
import { CopyIcon, EyeIcon, RightArrowIcon, SearchIcon, XIcon } from '@modrinth/assets'
import { NewModal } from '@modrinth/ui'
import { NewModal, StyledInput } from '@modrinth/ui'
import ButtonStyled from '@modrinth/ui/src/components/base/ButtonStyled.vue'
import { useDebounceFn } from '@vueuse/core'
import DOMPurify from 'dompurify'

View File

@@ -6,6 +6,7 @@ import {
injectNotificationManager,
NewModal,
ServerNotice,
StyledInput,
TagItem,
} from '@modrinth/ui'
import type { ServerNotice as ServerNoticeType } from '@modrinth/utils'
@@ -180,11 +181,10 @@ defineExpose({ show, hide })
<span v-else class="mb-2"> No nodes assigned yet </span>
</div>
<div class="flex w-[45rem] items-center gap-2">
<input
<StyledInput
id="server-assign-field"
v-model="inputField"
class="w-full"
type="text"
wrapper-class="w-full"
autocomplete="off"
/>
<ButtonStyled color="green" color-fill="text">

View File

@@ -506,23 +506,6 @@ const requestedStatus = computed(() => props.project.requested_status ?? 'approv
padding: var(--spacing-card-md);
}
.resizable-textarea-wrapper {
margin-bottom: var(--spacing-card-sm);
textarea {
padding: var(--spacing-card-bg);
width: 100%;
}
.chips {
margin-bottom: var(--spacing-card-md);
}
.preview {
overflow-y: auto;
}
}
.thread-id {
margin-bottom: var(--spacing-card-md);
font-weight: bold;

View File

@@ -192,20 +192,19 @@
: formatMessage(messages.selectGameVersion)
}}
</template>
<div class="iconified-input mb-2 flex w-full">
<label for="game-versions-filtering" hidden>{{
formatMessage(messages.searchGameVersionsLabel)
}}</label>
<SearchIcon aria-hidden="true" />
<input
id="game-versions-filtering"
ref="gameVersionFilterInput"
v-model="versionFilter"
type="search"
autocomplete="off"
:placeholder="formatMessage(messages.searchGameVersions)"
/>
</div>
<label for="game-versions-filtering" hidden>{{
formatMessage(messages.searchGameVersionsLabel)
}}</label>
<StyledInput
id="game-versions-filtering"
ref="gameVersionFilterInput"
v-model="versionFilter"
type="search"
autocomplete="off"
:icon="SearchIcon"
:placeholder="formatMessage(messages.searchGameVersions)"
wrapper-class="mb-2 w-full"
/>
<ScrollablePanel :class="project.game_versions.length > 4 ? 'h-[15rem]' : ''">
<ButtonStyled
v-for="gameVersion in project.game_versions
@@ -615,11 +614,10 @@
"
/>
<template #menu>
<input
<StyledInput
v-model="displayCollectionsSearch"
type="text"
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
class="search-input menu-search"
wrapper-class="menu-search"
/>
<div v-if="collections.length > 0" class="collections-list text-primary">
<Checkbox
@@ -986,6 +984,7 @@ import {
provideProjectPageContext,
ScrollablePanel,
ServersPromo,
StyledInput,
TagItem,
useRelativeTime,
useVIntl,

View File

@@ -42,28 +42,26 @@
<label for="gallery-image-title">
<span class="label__title">Title</span>
</label>
<input
<StyledInput
id="gallery-image-title"
v-model="editTitle"
type="text"
maxlength="64"
:maxlength="64"
placeholder="Enter title..."
/>
<label for="gallery-image-desc">
<span class="label__title">Description</span>
</label>
<div class="textarea-wrapper">
<textarea
id="gallery-image-desc"
v-model="editDescription"
maxlength="255"
placeholder="Enter description..."
/>
</div>
<StyledInput
id="gallery-image-desc"
v-model="editDescription"
multiline
:maxlength="255"
placeholder="Enter description..."
/>
<label for="gallery-image-ordering">
<span class="label__title">Order Index</span>
</label>
<input
<StyledInput
id="gallery-image-ordering"
v-model="editOrder"
type="number"
@@ -342,6 +340,7 @@ import {
FileInput,
injectProjectPageContext,
NewModal as Modal,
StyledInput,
} from '@modrinth/ui'
import { useEventListener, useLocalStorage } from '@vueuse/core'

View File

@@ -42,28 +42,26 @@
<label for="gallery-image-title">
<span class="label__title">Title</span>
</label>
<input
<StyledInput
id="gallery-image-title"
v-model="editTitle"
type="text"
maxlength="64"
:maxlength="64"
placeholder="Enter title..."
/>
<label for="gallery-image-desc">
<span class="label__title">Description</span>
</label>
<div class="textarea-wrapper">
<textarea
id="gallery-image-desc"
v-model="editDescription"
maxlength="255"
placeholder="Enter description..."
/>
</div>
<StyledInput
id="gallery-image-desc"
v-model="editDescription"
multiline
:maxlength="255"
placeholder="Enter description..."
/>
<label for="gallery-image-ordering">
<span class="label__title">Order Index</span>
</label>
<input
<StyledInput
id="gallery-image-ordering"
v-model="editOrder"
type="number"
@@ -301,6 +299,7 @@ import {
FileInput,
injectProjectPageContext,
NewModal as Modal,
StyledInput,
} from '@modrinth/ui'
import { isPermission } from '~/utils/permissions.ts'

View File

@@ -5,6 +5,7 @@ import {
injectProjectPageContext,
type MessageDescriptor,
SettingsLabel,
StyledInput,
UnsavedChangesPopup,
useSavable,
useVIntl,
@@ -134,14 +135,13 @@ const placeholder = computed(() => placeholders[placeholderIndex.value] ?? place
:description="messages.nameDescription"
/>
<div class="flex">
<input
<StyledInput
id="project-name"
v-model="current.title"
:placeholder="formatMessage(placeholder.name)"
autocomplete="off"
maxlength="50"
class="flex-grow"
type="text"
:maxlength="50"
wrapper-class="flex-grow"
/>
</div>
</div>
@@ -151,27 +151,20 @@ const placeholder = computed(() => placeholders[placeholderIndex.value] ?? place
:title="messages.taglineTitle"
:description="messages.taglineDescription"
/>
<input
<StyledInput
id="project-tagline"
v-model="current.tagline"
:placeholder="formatMessage(placeholder.tagline)"
autocomplete="off"
maxlength="120"
class="w-full"
type="text"
:maxlength="120"
wrapper-class="w-full"
/>
</div>
<div class="mt-4">
<SettingsLabel id="project-url" :title="messages.urlTitle" />
<div class="text-input-wrapper">
<div class="text-input-wrapper__before">https://modrinth.com/project/</div>
<input
id="project-url"
v-model="current.url"
type="text"
maxlength="64"
autocomplete="off"
/>
<StyledInput id="project-url" v-model="current.url" :maxlength="64" autocomplete="off" />
</div>
</div>
</div>

View File

@@ -54,13 +54,7 @@
<label for="project-name">
<span class="label__title">Name</span>
</label>
<input
id="project-name"
v-model="name"
maxlength="2048"
type="text"
:disabled="!hasPermission"
/>
<StyledInput id="project-name" v-model="name" :maxlength="2048" :disabled="!hasPermission" />
<label for="project-slug">
<span class="label__title">URL</span>
@@ -71,11 +65,10 @@
$getProjectTypeForUrl(project.project_type, project.loaders)
}}/
</div>
<input
<StyledInput
id="project-slug"
v-model="slug"
type="text"
maxlength="64"
:maxlength="64"
autocomplete="off"
:disabled="!hasPermission"
/>
@@ -88,14 +81,14 @@
<TriangleAlertIcon class="my-auto" />
{{ summaryWarning }}
</div>
<div class="textarea-wrapper summary-input">
<textarea
id="project-summary"
v-model="summary"
maxlength="256"
:disabled="!hasPermission"
/>
</div>
<StyledInput
id="project-summary"
v-model="summary"
multiline
:maxlength="256"
:disabled="!hasPermission"
wrapper-class="summary-input"
/>
<template
v-if="
!flags.newProjectEnvironmentSettings &&
@@ -261,6 +254,7 @@ import {
ConfirmModal,
injectNotificationManager,
injectProjectPageContext,
StyledInput,
} from '@modrinth/ui'
import { formatProjectStatus, formatProjectType } from '@modrinth/utils'
import { Multiselect } from 'vue-multiselect'

View File

@@ -71,16 +71,16 @@
</label>
<div class="w-1/2">
<input
<StyledInput
id="license-url"
v-model="current.licenseUrl"
type="url"
maxlength="2048"
:maxlength="2048"
:placeholder="
current.license?.friendly !== 'Custom' ? `License URL (optional)` : `License URL`
"
:disabled="!hasPermission || licenseId === 'LicenseRef-Unknown'"
class="w-full"
wrapper-class="w-full"
/>
</div>
</div>
@@ -104,23 +104,21 @@
</label>
<div class="input-stack w-1/2">
<input
<StyledInput
v-if="!current.nonSpdxLicense"
id="license-spdx"
v-model="current.license.short"
class="w-full"
type="text"
maxlength="128"
wrapper-class="w-full"
:maxlength="128"
placeholder="SPDX identifier"
:disabled="!hasPermission"
/>
<input
<StyledInput
v-else
id="license-name"
v-model="current.license.short"
class="w-full"
type="text"
maxlength="128"
wrapper-class="w-full"
:maxlength="128"
placeholder="License name"
:disabled="!hasPermission"
/>
@@ -158,6 +156,7 @@ import {
Checkbox,
DropdownSelect,
injectProjectPageContext,
StyledInput,
UnsavedChangesPopup,
useSavable,
} from '@modrinth/ui'

View File

@@ -27,12 +27,12 @@
v-tooltip="`Link includes a domain which isn't common for this link type.`"
class="size-6 animate-pulse text-orange"
/>
<input
<StyledInput
id="project-issue-tracker"
v-model="issuesUrl"
type="url"
placeholder="Enter a valid URL"
maxlength="2048"
:maxlength="2048"
:disabled="!hasPermission"
/>
</div>
@@ -61,11 +61,11 @@
v-tooltip="`Link includes a domain which isn't common for this link type.`"
class="size-6 animate-pulse text-orange"
/>
<input
<StyledInput
id="project-source-code"
v-model="sourceUrl"
type="url"
maxlength="2048"
:maxlength="2048"
placeholder="Enter a valid URL"
:disabled="!hasPermission"
/>
@@ -90,11 +90,11 @@
v-tooltip="`Discord invites are not appropriate for this link type.`"
class="size-6 animate-pulse text-orange"
/>
<input
<StyledInput
id="project-wiki-page"
v-model="wikiUrl"
type="url"
maxlength="2048"
:maxlength="2048"
placeholder="Enter a valid URL"
:disabled="!hasPermission"
/>
@@ -114,11 +114,11 @@
v-tooltip="`You're using a link which isn't common for this link type.`"
class="size-6 animate-pulse text-orange"
/>
<input
<StyledInput
id="project-discord-invite"
v-model="discordUrl"
type="url"
maxlength="2048"
:maxlength="2048"
placeholder="Enter a valid URL"
:disabled="!hasPermission"
/>
@@ -135,13 +135,13 @@
:key="`donation-link-${index}`"
class="input-group donation-link-group"
>
<input
<StyledInput
v-model="donationLink.url"
type="url"
maxlength="2048"
:maxlength="2048"
placeholder="Enter a valid URL"
:disabled="!hasPermission"
@input="updateDonationLinks"
@update:model-value="updateDonationLinks"
/>
<DropdownSelect
v-model="donationLink.id"
@@ -174,7 +174,7 @@
<script setup>
import { SaveIcon, TriangleAlertIcon } from '@modrinth/assets'
import { commonLinkDomains, isCommonUrl, isDiscordUrl, isLinkShortener } from '@modrinth/moderation'
import { DropdownSelect, injectProjectPageContext } from '@modrinth/ui'
import { DropdownSelect, injectProjectPageContext, StyledInput } from '@modrinth/ui'
const tags = useGeneratedState()

View File

@@ -22,10 +22,9 @@
</span>
</span>
<div class="input-group">
<input
<StyledInput
id="username"
v-model="currentUsername"
type="text"
placeholder="Username"
:disabled="(currentMember?.permissions & MANAGE_INVITES) !== MANAGE_INVITES"
@keypress.enter="inviteTeamMember()"
@@ -98,10 +97,9 @@
The title of the role that this member plays for this project.
</span>
</label>
<input
<StyledInput
:id="`member-${allTeamMembers[index].user.username}-role`"
v-model="allTeamMembers[index].role"
type="text"
:disabled="(currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER"
/>
</div>
@@ -113,7 +111,7 @@
this project's revenue goes to this member.
</span>
</label>
<input
<StyledInput
:id="`member-${allTeamMembers[index].user.username}-monetization-weight`"
v-model="allTeamMembers[index].payouts_split"
type="number"
@@ -364,10 +362,9 @@
The title of the role that this member plays for this project.
</span>
</label>
<input
<StyledInput
:id="`member-${allOrgMembers[index].user.username}-role`"
v-model="allOrgMembers[index].role"
type="text"
:disabled="
(currentMember?.permissions & EDIT_MEMBER) !== EDIT_MEMBER ||
!allOrgMembers[index].override
@@ -382,7 +379,7 @@
this project's revenue goes to this member.
</span>
</label>
<input
<StyledInput
:id="`member-${allOrgMembers[index].user.username}-monetization-weight`"
v-model="allOrgMembers[index].payouts_split"
type="number"
@@ -550,6 +547,7 @@ import {
ConfirmModal,
injectNotificationManager,
injectProjectPageContext,
StyledInput,
Toggle,
} from '@modrinth/ui'
import { Multiselect } from 'vue-multiselect'

View File

@@ -65,11 +65,10 @@
/>
<div class="version-header">
<template v-if="isEditing">
<input
<StyledInput
v-model="version.name"
type="text"
placeholder="Enter a version title..."
maxlength="256"
:maxlength="256"
/>
</template>
<h2 :class="{ 'sr-only': isEditing }">
@@ -430,6 +429,7 @@ import {
ENVIRONMENTS_COPY,
injectNotificationManager,
injectProjectPageContext,
StyledInput,
} from '@modrinth/ui'
import { formatBytes, renderHighlightedString } from '@modrinth/utils'
import { Multiselect } from 'vue-multiselect'

View File

@@ -19,20 +19,14 @@
>
<h1 class="m-0 grow text-2xl font-extrabold">Manage affiliate links</h1>
<div class="flex items-center gap-2">
<div class="iconified-input">
<SearchIcon aria-hidden="true" />
<input
v-model="filterQuery"
class="card-shadow"
autocomplete="off"
spellcheck="false"
type="text"
:placeholder="`Search affiliates...`"
/>
<Button v-if="filterQuery" class="r-btn" @click="() => (filterQuery = '')">
<XIcon />
</Button>
</div>
<StyledInput
v-model="filterQuery"
:icon="SearchIcon"
type="text"
autocomplete="off"
placeholder="Search affiliates..."
clearable
/>
<ButtonStyled color="brand">
<button @click="createModal?.show">
<PlusIcon />
@@ -88,17 +82,17 @@
</div>
</template>
<script setup lang="ts">
import { PlusIcon, SearchIcon, XCircleIcon, XIcon } from '@modrinth/assets'
import { PlusIcon, SearchIcon, XCircleIcon } from '@modrinth/assets'
import {
Accordion,
Admonition,
AffiliateLinkCard,
AffiliateLinkCreateModal,
Avatar,
Button,
ButtonStyled,
ConfirmModal,
injectNotificationManager,
StyledInput,
} from '@modrinth/ui'
import type { AffiliateLink, User } from '@modrinth/utils'

View File

@@ -30,7 +30,7 @@
{{ selectedCharge.net }})
</span>
</label>
<input id="amount" v-model="refundAmount" type="number" autocomplete="off" />
<StyledInput id="amount" v-model="refundAmount" type="number" autocomplete="off" />
</div>
<div class="flex flex-col gap-2">
<label for="unprovision" class="flex flex-col gap-1">
@@ -107,7 +107,7 @@
<span class="text-lg font-semibold text-contrast">Days to credit</span>
<span>Enter the number of days to add to the next due date.</span>
</label>
<input id="days" v-model.number="creditDays" type="number" min="1" autocomplete="off" />
<StyledInput id="days" v-model="creditDays" type="number" :min="1" autocomplete="off" />
</div>
<div class="flex flex-col gap-2">
<label for="sendEmail" class="flex flex-col gap-1">
@@ -330,6 +330,7 @@ import {
DropdownSelect,
injectNotificationManager,
NewModal,
StyledInput,
Toggle,
useRelativeTime,
useVIntl,

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { CopyIcon, LibraryIcon, PlayIcon, SearchIcon } from '@modrinth/assets'
import { ButtonStyled, Card } from '@modrinth/ui'
import { ButtonStyled, Card, StyledInput } from '@modrinth/ui'
import { computed, onMounted, ref } from 'vue'
import docs from '~/templates/docs'
@@ -51,18 +51,14 @@ onMounted(() => {
<div class="normal-page__content">
<Card class="mb-6 flex flex-col gap-4">
<div class="flex flex-wrap items-center gap-3">
<div class="relative">
<SearchIcon
class="pointer-events-none absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-secondary"
/>
<input
id="doc-search"
v-model="query"
type="text"
placeholder="Search templates..."
class="w-72 rounded-lg border border-divider bg-bg px-7 py-2 text-sm text-primary placeholder-secondary focus:border-green focus:outline-none"
/>
</div>
<StyledInput
id="doc-search"
v-model="query"
type="search"
:icon="SearchIcon"
placeholder="Search templates..."
wrapper-class="w-72"
/>
<ButtonStyled color="brand">
<button :disabled="filtered.length === 0" @click="openAll">

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { CopyIcon, LibraryIcon, PlayIcon, SearchIcon } from '@modrinth/assets'
import { ButtonStyled, Card } from '@modrinth/ui'
import { ButtonStyled, Card, StyledInput } from '@modrinth/ui'
import { computed, onMounted, ref } from 'vue'
import emails from '~/templates/emails'
@@ -51,18 +51,14 @@ onMounted(() => {
<div class="normal-page__content">
<Card class="mb-6 flex flex-col gap-4">
<div class="flex flex-wrap items-center gap-3">
<div class="relative">
<SearchIcon
class="pointer-events-none absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-secondary"
/>
<input
id="email-search"
v-model="query"
type="text"
placeholder="Search templates..."
class="w-72 rounded-lg border border-divider bg-bg px-7 py-2 text-sm text-primary placeholder-secondary focus:border-green focus:outline-none"
/>
</div>
<StyledInput
id="email-search"
v-model="query"
type="search"
:icon="SearchIcon"
placeholder="Search templates..."
wrapper-class="w-72"
/>
<ButtonStyled color="brand">
<button :disabled="filtered.length === 0" @click="openAll">

View File

@@ -24,11 +24,10 @@
<label for="notice-title" class="flex flex-col gap-1">
<span class="text-lg font-semibold text-contrast"> Title </span>
</label>
<input
<StyledInput
id="notice-title"
v-model="newNoticeTitle"
placeholder="E.g. Maintenance"
type="text"
autocomplete="off"
/>
</div>
@@ -39,17 +38,20 @@
<span class="text-brand-red">*</span>
</span>
</label>
<input
<StyledInput
v-if="newNoticeSurvey"
id="notice-message"
v-model="newNoticeMessage"
placeholder="E.g. rXGtq2"
type="text"
autocomplete="off"
/>
<div v-else class="textarea-wrapper h-32">
<textarea id="notice-message" v-model="newNoticeMessage" />
</div>
<StyledInput
v-else
id="notice-message"
v-model="newNoticeMessage"
multiline
wrapper-class="h-32"
/>
</div>
<div v-if="!newNoticeSurvey" class="flex items-center justify-between gap-2">
<label for="dismissable-toggle" class="flex flex-col gap-1">
@@ -63,7 +65,7 @@
<span class="text-lg font-semibold text-contrast"> Announcement date </span>
<span>Leave blank for notice to be available immediately.</span>
</label>
<input
<StyledInput
id="scheduled-date"
v-model="newNoticeScheduledDate"
type="datetime-local"
@@ -75,7 +77,7 @@
<span class="text-lg font-semibold text-contrast"> Expiration date </span>
<span>The notice will automatically be deleted after this date.</span>
</label>
<input
<StyledInput
id="expiration-date"
v-model="newNoticeExpiresDate"
type="datetime-local"
@@ -271,6 +273,7 @@ import {
injectNotificationManager,
NewModal,
ServerNotice,
StyledInput,
TagItem,
Toggle,
useRelativeTime,

View File

@@ -10,11 +10,11 @@
<span class="text-brand-red">*</span>
</span>
</label>
<input
<StyledInput
id="name"
v-model="userEmail"
type="email"
maxlength="64"
:maxlength="64"
:placeholder="`Enter user email...`"
autocomplete="off"
/>
@@ -33,7 +33,7 @@
</template>
<script setup lang="ts">
import { MailIcon } from '@modrinth/assets'
import { ButtonStyled, injectNotificationManager } from '@modrinth/ui'
import { ButtonStyled, injectNotificationManager, StyledInput } from '@modrinth/ui'
const { addNotification } = injectNotificationManager()

View File

@@ -7,20 +7,18 @@
{{ formatMessage(methodChoiceMessages.description) }}
</p>
<div class="iconified-input">
<label for="email" hidden>
{{ formatMessage(commonMessages.emailUsernameLabel) }}
</label>
<MailIcon />
<input
id="email"
v-model="email"
type="text"
autocomplete="username"
class="auth-form__input"
:placeholder="formatMessage(commonMessages.emailLabel)"
/>
</div>
<label for="email" hidden>
{{ formatMessage(commonMessages.emailUsernameLabel) }}
</label>
<StyledInput
id="email"
v-model="email"
:icon="MailIcon"
type="text"
autocomplete="username"
:placeholder="formatMessage(commonMessages.emailLabel)"
wrapper-class="w-full"
/>
<HCaptcha v-if="globals?.captcha_enabled" ref="captcha" v-model="token" />
@@ -35,33 +33,29 @@
<template v-else-if="step === 'passed_challenge'">
<p>{{ formatMessage(postChallengeMessages.description) }}</p>
<div class="iconified-input">
<label for="password" hidden>{{ formatMessage(commonMessages.passwordLabel) }}</label>
<KeyIcon />
<input
id="password"
v-model="newPassword"
type="password"
autocomplete="new-password"
class="auth-form__input"
:placeholder="formatMessage(commonMessages.passwordLabel)"
/>
</div>
<label for="password" hidden>{{ formatMessage(commonMessages.passwordLabel) }}</label>
<StyledInput
id="password"
v-model="newPassword"
:icon="KeyIcon"
type="password"
autocomplete="new-password"
:placeholder="formatMessage(commonMessages.passwordLabel)"
wrapper-class="w-full"
/>
<div class="iconified-input">
<label for="confirm-password" hidden>
{{ formatMessage(commonMessages.passwordLabel) }}
</label>
<KeyIcon />
<input
id="confirm-password"
v-model="confirmNewPassword"
type="password"
autocomplete="new-password"
class="auth-form__input"
:placeholder="formatMessage(postChallengeMessages.confirmPasswordLabel)"
/>
</div>
<label for="confirm-password" hidden>
{{ formatMessage(commonMessages.passwordLabel) }}
</label>
<StyledInput
id="confirm-password"
v-model="confirmNewPassword"
:icon="KeyIcon"
type="password"
autocomplete="new-password"
:placeholder="formatMessage(postChallengeMessages.confirmPasswordLabel)"
wrapper-class="w-full"
/>
<button class="auth-form__input btn btn-primary continue-btn" @click="changePassword">
{{ formatMessage(postChallengeMessages.action) }}
@@ -72,7 +66,13 @@
</template>
<script setup>
import { KeyIcon, MailIcon, SendIcon } from '@modrinth/assets'
import { commonMessages, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
import {
commonMessages,
defineMessages,
injectNotificationManager,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import HCaptcha from '@/components/ui/HCaptcha.vue'

View File

@@ -13,15 +13,13 @@
{{ formatMessage(messages.twoFactorCodeLabelDescription) }}
</span>
</label>
<input
<StyledInput
id="two-factor-code"
v-model="twoFactorCode"
maxlength="11"
type="text"
:maxlength="11"
inputmode="numeric"
:placeholder="formatMessage(messages.twoFactorCodeInputPlaceholder)"
autocomplete="one-time-code"
autofocus
@keyup.enter="begin2FASignIn"
/>
@@ -62,32 +60,28 @@
<h1>{{ formatMessage(messages.usePasswordLabel) }}</h1>
<section class="auth-form">
<div class="iconified-input">
<label for="email" hidden>{{ formatMessage(commonMessages.emailUsernameLabel) }}</label>
<MailIcon />
<input
id="email"
v-model="email"
type="text"
inputmode="email"
autocomplete="username"
class="auth-form__input"
:placeholder="formatMessage(commonMessages.emailUsernameLabel)"
/>
</div>
<label for="email" hidden>{{ formatMessage(commonMessages.emailUsernameLabel) }}</label>
<StyledInput
id="email"
v-model="email"
:icon="MailIcon"
type="text"
inputmode="email"
autocomplete="username"
:placeholder="formatMessage(commonMessages.emailUsernameLabel)"
wrapper-class="w-full"
/>
<div class="iconified-input">
<label for="password" hidden>{{ formatMessage(commonMessages.passwordLabel) }}</label>
<KeyIcon />
<input
id="password"
v-model="password"
type="password"
autocomplete="current-password"
class="auth-form__input"
:placeholder="formatMessage(commonMessages.passwordLabel)"
/>
</div>
<label for="password" hidden>{{ formatMessage(commonMessages.passwordLabel) }}</label>
<StyledInput
id="password"
v-model="password"
:icon="KeyIcon"
type="password"
autocomplete="current-password"
:placeholder="formatMessage(commonMessages.passwordLabel)"
wrapper-class="w-full"
/>
<HCaptcha v-if="globals?.captcha_enabled" ref="captcha" v-model="token" />
@@ -147,6 +141,7 @@ import {
defineMessages,
injectNotificationManager,
IntlFormatted,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'

View File

@@ -32,59 +32,49 @@
<h1>{{ formatMessage(messages.createAccountTitle) }}</h1>
<section class="auth-form">
<div class="iconified-input">
<label for="email" hidden>{{ formatMessage(commonMessages.emailLabel) }}</label>
<MailIcon />
<input
id="email"
v-model="email"
type="email"
autocomplete="username"
class="auth-form__input"
:placeholder="formatMessage(commonMessages.emailLabel)"
/>
</div>
<label for="email" hidden>{{ formatMessage(commonMessages.emailLabel) }}</label>
<StyledInput
id="email"
v-model="email"
:icon="MailIcon"
type="email"
autocomplete="username"
:placeholder="formatMessage(commonMessages.emailLabel)"
wrapper-class="w-full"
/>
<div class="iconified-input">
<label for="username" hidden>{{ formatMessage(commonMessages.usernameLabel) }}</label>
<UserIcon />
<input
id="username"
v-model="username"
type="text"
autocomplete="username"
class="auth-form__input"
:placeholder="formatMessage(commonMessages.usernameLabel)"
/>
</div>
<label for="username" hidden>{{ formatMessage(commonMessages.usernameLabel) }}</label>
<StyledInput
id="username"
v-model="username"
:icon="UserIcon"
type="text"
autocomplete="username"
:placeholder="formatMessage(commonMessages.usernameLabel)"
wrapper-class="w-full"
/>
<div class="iconified-input">
<label for="password" hidden>{{ formatMessage(commonMessages.passwordLabel) }}</label>
<KeyIcon />
<input
id="password"
v-model="password"
class="auth-form__input"
type="password"
autocomplete="new-password"
:placeholder="formatMessage(commonMessages.passwordLabel)"
/>
</div>
<label for="password" hidden>{{ formatMessage(commonMessages.passwordLabel) }}</label>
<StyledInput
id="password"
v-model="password"
:icon="KeyIcon"
type="password"
autocomplete="new-password"
:placeholder="formatMessage(commonMessages.passwordLabel)"
wrapper-class="w-full"
/>
<div class="iconified-input">
<label for="confirm-password" hidden>{{
formatMessage(commonMessages.passwordLabel)
}}</label>
<KeyIcon />
<input
id="confirm-password"
v-model="confirmPassword"
type="password"
autocomplete="new-password"
class="auth-form__input"
:placeholder="formatMessage(commonMessages.confirmPasswordLabel)"
/>
</div>
<label for="confirm-password" hidden>{{ formatMessage(commonMessages.passwordLabel) }}</label>
<StyledInput
id="confirm-password"
v-model="confirmPassword"
:icon="KeyIcon"
type="password"
autocomplete="new-password"
:placeholder="formatMessage(commonMessages.confirmPasswordLabel)"
wrapper-class="w-full"
/>
<Checkbox
v-model="subscribe"
@@ -153,6 +143,7 @@ import {
defineMessages,
injectNotificationManager,
IntlFormatted,
StyledInput,
useVIntl,
} from '@modrinth/ui'

View File

@@ -75,13 +75,12 @@
<label class="mb-2 block text-lg font-semibold text-contrast" for="collection-title">
{{ formatMessage(commonMessages.titleLabel) }}
</label>
<input
<StyledInput
id="collection-title"
v-model="current.name"
maxlength="255"
type="text"
:maxlength="255"
autocomplete="off"
class="w-full"
wrapper-class="w-full"
/>
</div>
<label
@@ -90,9 +89,13 @@
>
{{ formatMessage(commonMessages.descriptionLabel) }}
</label>
<div class="textarea-wrapper h-24">
<textarea id="collection-description" v-model="current.description" maxlength="255" />
</div>
<StyledInput
id="collection-description"
v-model="current.description"
multiline
:maxlength="255"
wrapper-class="h-24"
/>
<label for="visibility" class="mb-2 mt-4 block text-lg font-semibold text-contrast">
{{ formatMessage(commonMessages.visibilityLabel) }}
</label>
@@ -405,6 +408,7 @@ import {
ProjectCardList,
RadioButtons,
SidebarCard,
StyledInput,
useRelativeTime,
useSavable,
useVIntl,

View File

@@ -18,20 +18,14 @@
{{ formatMessage(messages.yourAffiliateLinks) }}
</h1>
<div class="flex items-center gap-2">
<div class="iconified-input">
<SearchIcon aria-hidden="true" />
<input
v-model="filterQuery"
class="card-shadow"
autocomplete="off"
spellcheck="false"
type="text"
:placeholder="formatMessage(messages.searchAffiliateLinks)"
/>
<Button v-if="filterQuery" class="r-btn" @click="() => (filterQuery = '')">
<XIcon />
</Button>
</div>
<StyledInput
v-model="filterQuery"
:icon="SearchIcon"
type="text"
autocomplete="off"
:placeholder="formatMessage(messages.searchAffiliateLinks)"
clearable
/>
<ButtonStyled color="brand">
<button @click="createModal?.show">
<PlusIcon />
@@ -63,16 +57,16 @@
</div>
</template>
<script setup lang="ts">
import { PlusIcon, SearchIcon, XCircleIcon, XIcon } from '@modrinth/assets'
import { PlusIcon, SearchIcon, XCircleIcon } from '@modrinth/assets'
import {
Admonition,
AffiliateLinkCard,
AffiliateLinkCreateModal,
Button,
ButtonStyled,
ConfirmModal,
defineMessages,
injectNotificationManager,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import type { AffiliateLink } from '@modrinth/utils'

View File

@@ -3,18 +3,18 @@
<CollectionCreateModal ref="modal_creation" />
<h2 class="text-2xl">{{ formatMessage(commonMessages.collectionsLabel) }}</h2>
<div class="search-row">
<div class="iconified-input">
<div class="flex-grow">
<label for="search-input" hidden>{{ formatMessage(messages.searchInputLabel) }}</label>
<SearchIcon aria-hidden="true" />
<input id="search-input" v-model="filterQuery" type="text" />
<Button
v-if="filterQuery"
class="r-btn"
aria-label="Clear search"
@click="() => (filterQuery = '')"
>
<XIcon aria-hidden="true" />
</Button>
<StyledInput
id="search-input"
v-model="filterQuery"
:icon="SearchIcon"
type="text"
clearable
placeholder="Search collections..."
wrapper-class="w-full"
input-class="h-8"
/>
</div>
<Button color="primary" @click="(event) => $refs.modal_creation.show(event)">
<PlusIcon aria-hidden="true" />
@@ -106,7 +106,7 @@ import {
SearchIcon,
XIcon,
} from '@modrinth/assets'
import { Avatar, Button, commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
import { Avatar, Button, commonMessages, defineMessages, StyledInput, useVIntl } from '@modrinth/ui'
import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.vue'

View File

@@ -15,7 +15,7 @@
<span class="label__title">Issue tracker</span>
</label>
<div class="input-group shrink-first">
<input
<StyledInput
id="issue-tracker-input"
v-model="editLinks.issues.val"
:disabled="editLinks.issues.clear"
@@ -23,7 +23,7 @@
:placeholder="
editLinks.issues.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
"
maxlength="2048"
:maxlength="2048"
/>
<button
v-tooltip="'Clear link'"
@@ -42,12 +42,12 @@
<span class="label__title">Source code</span>
</label>
<div class="input-group shrink-first">
<input
<StyledInput
id="source-code-input"
v-model="editLinks.source.val"
:disabled="editLinks.source.clear"
type="url"
maxlength="2048"
:maxlength="2048"
:placeholder="
editLinks.source.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
"
@@ -69,12 +69,12 @@
<span class="label__title">Wiki page</span>
</label>
<div class="input-group shrink-first">
<input
<StyledInput
id="wiki-page-input"
v-model="editLinks.wiki.val"
:disabled="editLinks.wiki.clear"
type="url"
maxlength="2048"
:maxlength="2048"
:placeholder="
editLinks.wiki.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
"
@@ -93,12 +93,12 @@
<span class="label__title">Discord invite</span>
</label>
<div class="input-group shrink-first">
<input
<StyledInput
id="discord-invite-input"
v-model="editLinks.discord.val"
:disabled="editLinks.discord.clear"
type="url"
maxlength="2048"
:maxlength="2048"
:placeholder="
editLinks.discord.clear
? 'Existing link will be cleared'
@@ -335,6 +335,7 @@ import {
injectNotificationManager,
NewModal,
ProjectStatusBadge,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import { formatProjectType } from '@modrinth/utils'

View File

@@ -18,7 +18,6 @@ import {
} from '@modrinth/assets'
import {
Avatar,
Button,
ButtonStyled,
Checkbox,
defineMessages,
@@ -31,6 +30,7 @@ import {
SearchFilterControl,
SearchSidebarFilter,
type SortType,
StyledInput,
Toggle,
useSearch,
useVIntl,
@@ -583,30 +583,18 @@ useSeoMeta({
</aside>
<section class="normal-page__content">
<div class="flex flex-col gap-3">
<div class="iconified-input w-full">
<SearchIcon aria-hidden="true" class="text-lg" />
<input
v-model="query"
class="h-12"
autocomplete="off"
spellcheck="false"
type="text"
:placeholder="`Search ${projectType?.display ?? 'project'}s...`"
@input="throttledSearch()"
/>
<Button
v-if="query"
class="r-btn"
@click="
() => {
query = ''
updateSearchResults()
}
"
>
<XIcon />
</Button>
</div>
<StyledInput
v-model="query"
:icon="SearchIcon"
type="text"
autocomplete="off"
:placeholder="`Search ${projectType?.display ?? 'project'}s...`"
clearable
wrapper-class="w-full"
input-class="!h-12"
@input="throttledSearch()"
@clear="updateSearchResults()"
/>
<div class="flex flex-wrap items-center gap-2">
<DropdownSelect
v-slot="{ selected }"

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { SearchIcon } from '@modrinth/assets'
import { Toggle } from '@modrinth/ui'
import { StyledInput, Toggle } from '@modrinth/ui'
import Fuse from 'fuse.js'
import { computed, ref, shallowReactive } from 'vue'
@@ -38,15 +38,13 @@ useSeoMeta({
<template>
<div class="mx-auto my-4 box-border w-[calc(100%-2rem)] max-w-[800px]">
<h1 class="mb-4 text-2xl font-bold text-contrast">Feature flags</h1>
<div class="relative mb-2">
<SearchIcon
class="pointer-events-none absolute left-3 top-1/2 size-5 -translate-y-1/2 text-secondary"
/>
<input
<div class="mb-2">
<StyledInput
v-model="searchQuery"
type="search"
:icon="SearchIcon"
placeholder="Search flags..."
class="w-full rounded-xl bg-bg-raised py-2 pl-10 pr-4"
wrapper-class="w-full rounded-xl bg-bg-raised"
/>
</div>
<div class="flex flex-col gap-2">

View File

@@ -41,17 +41,14 @@
<div class="sticky top-0 z-20 -mt-3 flex items-center justify-between bg-bg py-3">
<div class="flex w-full flex-col-reverse items-center gap-2 sm:flex-row">
<div class="flex w-full items-center gap-2">
<div class="relative flex-1 text-sm">
<div class="flex-1 text-sm">
<label class="sr-only" for="search">Search</label>
<SearchIcon
class="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2"
aria-hidden="true"
/>
<input
<StyledInput
id="search"
v-model="searchInput"
class="!h-9 !min-h-0 w-full border-[1px] border-solid border-button-border pl-9"
wrapper-class="w-full"
type="search"
:icon="SearchIcon"
name="search"
autocomplete="off"
:placeholder="`Search ${localMods.length} ${type.toLocaleLowerCase()}s...`"
@@ -356,6 +353,7 @@ import {
ButtonStyled,
injectModrinthClient,
injectNotificationManager,
StyledInput,
Toggle,
} from '@modrinth/ui'
import type { Mod } from '@modrinth/utils'

View File

@@ -8,12 +8,11 @@
<span> This name is only visible on Modrinth.</span>
</label>
<div class="flex flex-col gap-2">
<input
<StyledInput
id="server-name-field"
v-model="serverName"
class="w-full md:w-[50%]"
maxlength="48"
minlength="1"
wrapper-class="w-full md:w-[50%]"
:maxlength="48"
@keyup.enter="!serverName && saveGeneral"
/>
<span v-if="!serverName" class="text-sm text-rose-400">
@@ -42,11 +41,11 @@
<span> Your friends can connect to your server using this URL. </span>
</label>
<div class="flex w-full items-center gap-2 md:w-[60%]">
<input
<StyledInput
id="server-subdomain"
v-model="serverSubdomain"
class="h-[50%] w-[63%]"
maxlength="32"
wrapper-class="h-[50%] w-[63%]"
:maxlength="32"
@keyup.enter="saveGeneral"
/>
.modrinth.gg
@@ -116,7 +115,12 @@
<script setup lang="ts">
import { EditIcon, TransferIcon } from '@modrinth/assets'
import { injectModrinthClient, injectNotificationManager, ServerIcon } from '@modrinth/ui'
import {
injectModrinthClient,
injectNotificationManager,
ServerIcon,
StyledInput,
} from '@modrinth/ui'
import ButtonStyled from '@modrinth/ui/src/components/base/ButtonStyled.vue'
import SaveBanner from '~/components/ui/servers/SaveBanner.vue'

View File

@@ -3,13 +3,12 @@
<NewModal ref="newAllocationModal" header="New allocation">
<form class="flex flex-col gap-2 md:w-[600px]" @submit.prevent="addNewAllocation">
<label for="new-allocation-name" class="font-semibold text-contrast"> Name </label>
<input
<StyledInput
id="new-allocation-name"
ref="newAllocationInput"
v-model="newAllocationName"
type="text"
class="bg-bg-input w-full rounded-lg p-4"
maxlength="32"
wrapper-class="w-full"
:maxlength="32"
placeholder="e.g. Secondary allocation"
/>
<div class="mb-1 mt-4 flex justify-start gap-4">
@@ -28,13 +27,12 @@
<NewModal ref="editAllocationModal" header="Edit allocation">
<form class="flex flex-col gap-2 md:w-[600px]" @submit.prevent="editAllocation">
<label for="edit-allocation-name" class="font-semibold text-contrast"> Name </label>
<input
<StyledInput
id="edit-allocation-name"
ref="editAllocationInput"
v-model="newAllocationName"
type="text"
class="bg-bg-input w-full rounded-lg p-4"
maxlength="32"
wrapper-class="w-full"
:maxlength="32"
placeholder="e.g. Secondary allocation"
/>
<div class="mb-1 mt-4 flex justify-start gap-4">
@@ -107,13 +105,11 @@
</ButtonStyled>
</div>
<input
<StyledInput
id="user-domain"
v-model="userDomain"
class="w-full md:w-[50%]"
maxlength="64"
minlength="1"
type="text"
wrapper-class="w-full md:w-[50%]"
:maxlength="64"
:placeholder="exampleDomain"
/>
@@ -279,6 +275,7 @@ import {
CopyCode,
injectNotificationManager,
NewModal,
StyledInput,
} from '@modrinth/ui'
import { computed, nextTick, ref } from 'vue'

View File

@@ -21,17 +21,14 @@
</div>
</div>
<div class="flex flex-col gap-4 rounded-2xl bg-table-alternateRow p-4">
<div class="relative w-full text-sm">
<div class="w-full text-sm">
<label for="search-server-properties" class="sr-only">Search server properties</label>
<SearchIcon
class="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2"
aria-hidden="true"
/>
<input
<StyledInput
id="search-server-properties"
v-model="searchInput"
class="w-full pl-9"
wrapper-class="w-full"
type="search"
:icon="SearchIcon"
name="search"
autocomplete="off"
placeholder="Search server properties..."
@@ -72,41 +69,44 @@
v-else-if="typeof property === 'number' && index !== 'level-seed' && index !== 'seed'"
class="mt-2 w-full sm:w-[320px]"
>
<input
<StyledInput
:id="`server-property-${index}`"
v-model.number="liveProperties[index]"
:model-value="liveProperties[index]"
type="number"
class="w-full border p-2"
wrapper-class="w-full"
:aria-labelledby="`property-label-${index}`"
@update:model-value="liveProperties[index] = $event"
/>
</div>
<div
v-else-if="index === 'level-seed' || index === 'seed'"
class="mt-2 w-full sm:w-[320px]"
>
<input
<StyledInput
:id="`server-property-${index}`"
v-model="liveProperties[index]"
type="text"
class="w-full rounded-xl border p-2"
:model-value="liveProperties[index]"
wrapper-class="w-full"
:aria-labelledby="`property-label-${index}`"
@update:model-value="liveProperties[index] = $event"
/>
</div>
<div v-else-if="isComplexProperty(property)" class="mt-2 w-full sm:w-[320px]">
<textarea
<StyledInput
:id="`server-property-${index}`"
v-model="liveProperties[index]"
class="w-full resize-y rounded-xl border p-2"
multiline
resize="vertical"
input-class="p-2"
:aria-labelledby="`property-label-${index}`"
></textarea>
/>
</div>
<div v-else class="mt-2 flex w-full justify-end sm:w-[320px]">
<input
<StyledInput
:id="`server-property-${index}`"
v-model="liveProperties[index]"
type="text"
class="w-full rounded-xl border p-2"
:model-value="liveProperties[index]"
wrapper-class="w-full"
:aria-labelledby="`property-label-${index}`"
@update:model-value="liveProperties[index] = $event"
/>
</div>
</div>
@@ -132,7 +132,13 @@
<script setup lang="ts">
import { EyeIcon, SearchIcon } from '@modrinth/assets'
import { Combobox, injectModrinthClient, injectNotificationManager, Toggle } from '@modrinth/ui'
import {
Combobox,
injectModrinthClient,
injectNotificationManager,
StyledInput,
Toggle,
} from '@modrinth/ui'
import Fuse from 'fuse.js'
import { computed, inject, ref, watch } from 'vue'

View File

@@ -51,10 +51,12 @@
</button>
</ButtonStyled>
</div>
<textarea
<StyledInput
id="startup-command-field"
v-model="invocation"
class="min-h-[270px] w-full resize-y font-[family-name:var(--mono-font)]"
multiline
resize="vertical"
input-class="min-h-[270px] font-[family-name:var(--mono-font)]"
/>
</div>
@@ -108,7 +110,13 @@
<script setup lang="ts">
import { IssuesIcon, UpdatedIcon } from '@modrinth/assets'
import { ButtonStyled, Combobox, injectNotificationManager, Toggle } from '@modrinth/ui'
import {
ButtonStyled,
Combobox,
injectNotificationManager,
StyledInput,
Toggle,
} from '@modrinth/ui'
import SaveBanner from '~/components/ui/servers/SaveBanner.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'

View File

@@ -91,21 +91,20 @@
<div class="blob-demonstration gradient-border bigger">
<div class="demo-search">
<div class="search-controls">
<div class="iconified-input">
<label class="hidden" for="search">{{
formatMessage(commonMessages.searchLabel)
}}</label>
<SearchIcon aria-hidden="true" />
<input
id="search"
v-model="searchQuery"
type="search"
name="search"
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
autocomplete="off"
@input="updateSearchProjects"
/>
</div>
<label class="hidden" for="search">{{
formatMessage(commonMessages.searchLabel)
}}</label>
<StyledInput
id="search"
v-model="searchQuery"
:icon="SearchIcon"
type="search"
name="search"
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
autocomplete="off"
wrapper-class="w-full"
@input="updateSearchProjects"
/>
<div class="sort-by">
<span class="label">{{ formatMessage(commonMessages.sortByLabel) }}</span>
<Multiselect
@@ -447,6 +446,7 @@ import {
defineMessages,
IntlFormatted,
ProjectCard,
StyledInput,
useRelativeTime,
useVIntl,
} from '@modrinth/ui'

View File

@@ -102,7 +102,7 @@
<tr>
<td>Revenue earned on</td>
<td>
<input id="revenue-date-picker" v-model="rawSelectedDate" type="date" />
<StyledInput id="revenue-date-picker" v-model="rawSelectedDate" type="date" />
<noscript
>(JavaScript must be enabled for the date picker to function, example date:
2024-07-15)
@@ -162,6 +162,7 @@
</template>
<script lang="ts" setup>
import { StyledInput } from '@modrinth/ui'
import { formatDate, formatMoney } from '@modrinth/utils'
import dayjs from 'dayjs'
import { computed, ref } from 'vue'

View File

@@ -1,21 +1,17 @@
<template>
<div class="flex flex-col gap-4">
<div class="flex flex-col justify-between gap-3 lg:flex-row">
<div class="iconified-input flex-1 lg:max-w-md">
<SearchIcon aria-hidden="true" class="text-lg" />
<input
v-model="query"
class="h-[40px]"
autocomplete="off"
spellcheck="false"
type="text"
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
@input="goToPage(1)"
/>
<Button v-if="query" class="r-btn" @click="() => (query = '')">
<XIcon />
</Button>
</div>
<StyledInput
v-model="query"
:icon="SearchIcon"
type="text"
autocomplete="off"
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
clearable
wrapper-class="flex-1 lg:max-w-52"
input-class="h-[40px]"
@input="goToPage(1)"
/>
<div v-if="totalPages > 1" class="hidden flex-1 justify-center lg:flex">
<Pagination :page="currentPage" :count="totalPages" @switch-page="goToPage" />
@@ -99,16 +95,8 @@
</div>
</template>
<script setup lang="ts">
import { ListFilterIcon, ScaleIcon, SearchIcon, SortAscIcon, SortDescIcon } from '@modrinth/assets'
import {
ListFilterIcon,
ScaleIcon,
SearchIcon,
SortAscIcon,
SortDescIcon,
XIcon,
} from '@modrinth/assets'
import {
Button,
ButtonStyled,
Combobox,
type ComboboxOption,
@@ -116,6 +104,7 @@ import {
defineMessages,
injectNotificationManager,
Pagination,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import Fuse from 'fuse.js'

View File

@@ -1,21 +1,17 @@
<template>
<div class="flex flex-col gap-4">
<div class="flex flex-col justify-between gap-3 lg:flex-row">
<div class="iconified-input flex-1 lg:max-w-md">
<SearchIcon aria-hidden="true" class="text-lg" />
<input
v-model="query"
class="h-[40px]"
autocomplete="off"
spellcheck="false"
type="text"
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
@input="goToPage(1)"
/>
<Button v-if="query" class="r-btn" @click="() => (query = '')">
<XIcon />
</Button>
</div>
<StyledInput
v-model="query"
:icon="SearchIcon"
type="text"
autocomplete="off"
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
clearable
wrapper-class="flex-1 lg:max-w-52"
input-class="h-[40px]"
@input="goToPage(1)"
/>
<div v-if="totalPages > 1" class="hidden flex-1 justify-center lg:flex">
<Pagination :page="currentPage" :count="totalPages" @switch-page="goToPage" />
@@ -76,14 +72,14 @@
</template>
<script setup lang="ts">
import { ListFilterIcon, SearchIcon, SortAscIcon, SortDescIcon, XIcon } from '@modrinth/assets'
import { ListFilterIcon, SearchIcon, SortAscIcon, SortDescIcon } from '@modrinth/assets'
import type { ExtendedReport } from '@modrinth/moderation'
import {
Button,
Combobox,
type ComboboxOption,
commonMessages,
Pagination,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import type { Report } from '@modrinth/utils'

View File

@@ -7,16 +7,15 @@ import {
SearchIcon,
SortAscIcon,
SortDescIcon,
XIcon,
} from '@modrinth/assets'
import {
Button,
Combobox,
type ComboboxOption,
commonMessages,
FloatingPanel,
injectModrinthClient,
Pagination,
StyledInput,
Toggle,
useVIntl,
} from '@modrinth/ui'
@@ -515,21 +514,17 @@ watch([currentSortType, currentResponseFilter, inOtherQueueFilter, currentFilter
/> -->
<div class="flex flex-col justify-between gap-2 lg:flex-row">
<div class="iconified-input flex-1 lg:max-w-56">
<SearchIcon aria-hidden="true" class="text-lg" />
<input
v-model="query"
class="!h-10"
autocomplete="off"
spellcheck="false"
type="text"
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
@input="goToPage(1)"
/>
<Button v-if="query" class="r-btn" @click="() => (query = '')">
<XIcon />
</Button>
</div>
<StyledInput
v-model="query"
:icon="SearchIcon"
type="text"
autocomplete="off"
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
clearable
wrapper-class="flex-1 lg:max-w-52"
input-class="!h-10"
@input="goToPage(1)"
/>
<div v-if="totalPages > 1" class="hidden flex-1 justify-center lg:flex">
<LoaderCircleIcon

View File

@@ -6,6 +6,7 @@ import {
ConfirmModal,
FileInput,
injectNotificationManager,
StyledInput,
UnsavedChangesPopup,
useSavable,
} from '@modrinth/ui'
@@ -187,11 +188,10 @@ const onDeleteOrganization = useClientTry(async () => {
<label for="project-name">
<span class="label__title">Name</span>
</label>
<input
<StyledInput
id="project-name"
v-model="current.name"
maxlength="2048"
type="text"
:maxlength="2048"
:disabled="!hasPermission"
/>
@@ -200,11 +200,10 @@ const onDeleteOrganization = useClientTry(async () => {
</label>
<div class="text-input-wrapper">
<div class="text-input-wrapper__before">https://modrinth.com/organization/</div>
<input
<StyledInput
id="project-slug"
v-model="current.slug"
type="text"
maxlength="64"
:maxlength="64"
autocomplete="off"
:disabled="!hasPermission"
/>
@@ -213,14 +212,14 @@ const onDeleteOrganization = useClientTry(async () => {
<label for="project-summary">
<span class="label__title">Summary</span>
</label>
<div class="textarea-wrapper summary-input">
<textarea
id="project-summary"
v-model="current.summary"
maxlength="256"
:disabled="!hasPermission"
/>
</div>
<StyledInput
id="project-summary"
v-model="current.summary"
multiline
:maxlength="256"
:disabled="!hasPermission"
wrapper-class="summary-input"
/>
</div>
<div class="universal-card">
<div class="label">

View File

@@ -14,10 +14,9 @@
</span>
</span>
<div class="input-group">
<input
<StyledInput
id="username"
v-model="currentUsername"
type="text"
placeholder="Username"
:disabled="
!isPermission(
@@ -101,10 +100,9 @@
The title of the role that this member plays for this organization.
</span>
</label>
<input
<StyledInput
:id="`member-${member.user.id}-role`"
v-model="member.role"
type="text"
:disabled="
!isPermission(
currentMember.organization_permissions,
@@ -121,7 +119,7 @@
the organization projects' revenue goes to this member.
</span>
</label>
<input
<StyledInput
:id="`member-${member.user.id}-monetization-weight`"
v-model="member.payouts_split"
type="number"
@@ -227,7 +225,14 @@ import {
UserPlusIcon,
UserXIcon as UserRemoveIcon,
} from '@modrinth/assets'
import { Avatar, Badge, Button, Checkbox, injectNotificationManager } from '@modrinth/ui'
import {
Avatar,
Badge,
Button,
Checkbox,
injectNotificationManager,
StyledInput,
} from '@modrinth/ui'
import { ref } from 'vue'
import { removeTeamMember } from '~/helpers/teams.js'

View File

@@ -15,7 +15,7 @@
<span class="label__title">Issue tracker</span>
</label>
<div class="input-group shrink-first">
<input
<StyledInput
id="issue-tracker-input"
v-model="editLinks.issues.val"
:disabled="editLinks.issues.clear"
@@ -23,7 +23,7 @@
:placeholder="
editLinks.issues.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
"
maxlength="2048"
:maxlength="2048"
/>
<button
v-tooltip="'Clear link'"
@@ -42,12 +42,12 @@
<span class="label__title">Source code</span>
</label>
<div class="input-group shrink-first">
<input
<StyledInput
id="source-code-input"
v-model="editLinks.source.val"
:disabled="editLinks.source.clear"
type="url"
maxlength="2048"
:maxlength="2048"
:placeholder="
editLinks.source.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
"
@@ -69,12 +69,12 @@
<span class="label__title">Wiki page</span>
</label>
<div class="input-group shrink-first">
<input
<StyledInput
id="wiki-page-input"
v-model="editLinks.wiki.val"
:disabled="editLinks.wiki.clear"
type="url"
maxlength="2048"
:maxlength="2048"
:placeholder="
editLinks.wiki.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
"
@@ -93,12 +93,12 @@
<span class="label__title">Discord invite</span>
</label>
<div class="input-group shrink-first">
<input
<StyledInput
id="discord-invite-input"
v-model="editLinks.discord.val"
:disabled="editLinks.discord.clear"
type="url"
maxlength="2048"
:maxlength="2048"
:placeholder="
editLinks.discord.clear
? 'Existing link will be cleared'
@@ -334,6 +334,7 @@ import {
injectNotificationManager,
NewModal,
ProjectStatusBadge,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import { formatProjectType } from '@modrinth/utils'

View File

@@ -136,14 +136,13 @@
}}
</span>
<div class="flex gap-4">
<input
<StyledInput
id="report-item-id"
v-model="reportItemID"
type="text"
placeholder="ex: Dc7EYhxG"
autocomplete="off"
:disabled="reportItem === ''"
class="w-40"
wrapper-class="w-40"
@blur="
() => {
prefilled = false
@@ -293,6 +292,7 @@ import {
type MessageDescriptor,
RadialHeader,
RadioButtons,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import type { Project, Report, User, Version } from '@modrinth/utils'

View File

@@ -13,10 +13,10 @@
<div class="universal-modal">
<p>Your account information is not displayed publicly.</p>
<label for="email-input"><span class="label__title">Email address</span> </label>
<input
<StyledInput
id="email-input"
v-model="email"
maxlength="2048"
:maxlength="2048"
type="email"
:placeholder="`Enter your email address...`"
@keyup.enter="saveEmail()"
@@ -58,21 +58,21 @@
<label v-else-if="auth.user.has_password" for="old-password">
<span class="label__title">Old password</span>
</label>
<input
<StyledInput
v-if="auth.user.has_password"
id="old-password"
v-model="oldPassword"
maxlength="2048"
:maxlength="2048"
type="password"
autocomplete="current-password"
:placeholder="`${removePasswordMode ? 'Confirm' : 'Old'} password`"
/>
<template v-if="!removePasswordMode">
<label for="new-password"><span class="label__title">New password</span></label>
<input
<StyledInput
id="new-password"
v-model="newPassword"
maxlength="2048"
:maxlength="2048"
type="password"
autocomplete="new-password"
placeholder="New password"
@@ -80,10 +80,10 @@
<label for="confirm-new-password"
><span class="label__title">Confirm new password</span></label
>
<input
<StyledInput
id="confirm-new-password"
v-model="confirmNewPassword"
maxlength="2048"
:maxlength="2048"
type="password"
autocomplete="new-password"
placeholder="Confirm new password"
@@ -145,11 +145,10 @@
<span class="label__title">Enter two-factor code</span>
<span class="label__description">Please enter a two-factor code to proceed.</span>
</label>
<input
<StyledInput
id="two-factor-code"
v-model="twoFactorCode"
maxlength="11"
type="text"
:maxlength="11"
placeholder="Enter code..."
@keyup.enter="removeTwoFactor()"
/>
@@ -197,11 +196,10 @@
>Enter the one-time code from authenticator to verify access.
</span>
</label>
<input
<StyledInput
id="verify-code"
v-model="twoFactorCode"
maxlength="6"
type="text"
:maxlength="6"
autocomplete="one-time-code"
placeholder="Enter code..."
@keyup.enter="verifyTwoFactorCode()"
@@ -421,7 +419,7 @@ import {
UpdatedIcon,
XIcon,
} from '@modrinth/assets'
import { ConfirmModal, injectNotificationManager } from '@modrinth/ui'
import { ConfirmModal, injectNotificationManager, StyledInput } from '@modrinth/ui'
import KeyIcon from 'assets/icons/auth/key.svg'
import DiscordIcon from 'assets/icons/auth/sso-discord.svg'
import GithubIcon from 'assets/icons/auth/sso-github.svg'

View File

@@ -12,11 +12,10 @@
<label for="app-name"
><span class="label__title">{{ formatMessage(messages.nameLabel) }}</span>
</label>
<input
<StyledInput
id="app-name"
v-model="name"
maxlength="2048"
type="text"
:maxlength="2048"
autocomplete="off"
:placeholder="formatMessage(messages.namePlaceholder)"
/>
@@ -38,11 +37,11 @@
<label v-if="editingId" for="app-url">
<span class="label__title">{{ formatMessage(messages.urlLabel) }}</span>
</label>
<input
<StyledInput
v-if="editingId"
id="app-url"
v-model="url"
maxlength="255"
:maxlength="255"
type="url"
autocomplete="off"
:placeholder="formatMessage(messages.urlPlaceholder)"
@@ -50,15 +49,15 @@
<label v-if="editingId" for="app-description">
<span class="label__title">{{ formatMessage(messages.descriptionLabel) }}</span>
</label>
<textarea
<StyledInput
v-if="editingId"
id="app-description"
v-model="description"
class="description-textarea"
maxlength="255"
type="text"
multiline
:maxlength="255"
autocomplete="off"
:placeholder="formatMessage(messages.descriptionPlaceholder)"
input-class="h-24 resize-y"
/>
<label for="app-scopes"
><span class="label__title">{{ formatMessage(messages.scopesLabel) }}</span>
@@ -88,9 +87,9 @@
<div class="uri-input-list">
<div v-for="(_, index) in redirectUris" :key="index">
<div class="input-group url-input-group-fixes">
<input
<StyledInput
v-model="redirectUris[index]"
maxlength="2048"
:maxlength="2048"
type="url"
autocomplete="off"
:placeholder="formatMessage(messages.redirectUriPlaceholder)"
@@ -257,6 +256,7 @@ import {
injectNotificationManager,
IntlFormatted,
normalizeChildren,
StyledInput,
useVIntl,
} from '@modrinth/ui'
@@ -697,11 +697,6 @@ async function removeApp() {
}
</script>
<style lang="scss" scoped>
.description-textarea {
height: 6rem;
resize: vertical;
}
.secret_disclaimer {
font-size: var(--font-size-sm);
}

View File

@@ -19,11 +19,10 @@
<label for="pat-name">
<span class="label__title">{{ formatMessage(createModalMessages.nameLabel) }}</span>
</label>
<input
<StyledInput
id="pat-name"
v-model="name"
maxlength="2048"
type="email"
:maxlength="2048"
:placeholder="formatMessage(createModalMessages.namePlaceholder)"
/>
<label for="pat-scopes">
@@ -51,7 +50,7 @@
<label for="pat-name" class="mt-4">
<span class="label__title">{{ formatMessage(createModalMessages.expiresLabel) }}</span>
</label>
<input id="pat-name" v-model="expires" type="date" />
<StyledInput id="pat-expires" v-model="expires" type="date" />
<p></p>
<div class="input-group push-right">
<button class="iconified-button" @click="$refs.patModal.hide()">
@@ -222,6 +221,7 @@ import {
defineMessages,
injectNotificationManager,
IntlFormatted,
StyledInput,
useRelativeTime,
useVIntl,
} from '@modrinth/ui'

View File

@@ -56,15 +56,15 @@
{{ formatMessage(messages.usernameDescription) }}
</span>
</label>
<input id="username-field" v-model="current.username" type="text" />
<StyledInput id="username-field" v-model="current.username" />
<label for="bio-field">
<span class="label__title">{{ formatMessage(messages.bioTitle) }}</span>
<span class="label__description">
{{ formatMessage(messages.bioDescription) }}
</span>
</label>
<textarea id="bio-field" v-model="current.bio" type="text" />
<div class="input-group">
<StyledInput id="bio-field" v-model="current.bio" multiline />
<div class="input-group mt-4">
<Button :link="`/user/${auth.user.username}`">
<UserIcon /> {{ formatMessage(commonMessages.visitYourProfile) }}
</Button>
@@ -90,6 +90,7 @@ import {
FileInput,
injectNotificationManager,
IntlFormatted,
StyledInput,
UnsavedChangesPopup,
useSavable,
useVIntl,
@@ -250,10 +251,4 @@ async function save() {
gap: var(--gap-lg);
margin-top: var(--gap-md);
}
textarea {
height: 6rem;
width: 40rem;
margin-bottom: var(--gap-lg);
}
</style>