mirror of
https://github.com/modrinth/code.git
synced 2026-08-30 19:46:33 +00:00
fix: qa
This commit is contained in:
@@ -3,12 +3,12 @@
|
||||
:id="id"
|
||||
type="button"
|
||||
role="switch"
|
||||
:aria-checked="modelValue"
|
||||
:aria-checked="indeterminate ? 'mixed' : modelValue"
|
||||
:disabled="disabled"
|
||||
class="group inline-flex shrink-0 touch-manipulation items-center rounded-full m-0 p-1 transition-all duration-200 cursor-pointer border-none"
|
||||
:class="[
|
||||
small ? 'h-5 !w-[40px]' : 'h-6 !w-[48px]',
|
||||
modelValue ? 'bg-brand' : 'bg-button-bg',
|
||||
indeterminate || modelValue ? 'bg-brand' : 'bg-button-bg',
|
||||
disabled ? 'opacity-50 cursor-not-allowed' : '',
|
||||
]"
|
||||
@click="toggle"
|
||||
@@ -16,14 +16,21 @@
|
||||
<span
|
||||
class="rounded-full transition-all duration-200"
|
||||
:class="[
|
||||
small ? 'w-3 h-3' : 'w-4 h-4',
|
||||
modelValue
|
||||
indeterminate
|
||||
? small
|
||||
? 'translate-x-[20px] bg-black/90'
|
||||
: 'translate-x-[24px] bg-black/90'
|
||||
: 'bg-gray',
|
||||
? 'h-0.5 w-2.5 translate-x-[11px] bg-black/90'
|
||||
: 'h-0.5 w-3 translate-x-[14px] bg-black/90'
|
||||
: modelValue
|
||||
? small
|
||||
? 'h-3 w-3 translate-x-[20px] bg-black/90'
|
||||
: 'h-4 w-4 translate-x-[24px] bg-black/90'
|
||||
: small
|
||||
? 'h-3 w-3 bg-gray'
|
||||
: 'h-4 w-4 bg-gray',
|
||||
disabled
|
||||
? ''
|
||||
: indeterminate
|
||||
? 'group-hover:scale-x-110 group-active:scale-x-75'
|
||||
: small
|
||||
? 'group-hover:w-[14px] group-hover:h-[14px] group-hover:m-[-1px] group-active:w-[10px] group-active:h-[10px] group-active:m-[1px]'
|
||||
: 'group-hover:w-[18px] group-hover:h-[18px] group-hover:m-[-1px] group-active:w-[14px] group-active:h-[14px] group-active:m-[1px]',
|
||||
@@ -37,6 +44,7 @@ const props = defineProps<{
|
||||
id?: string
|
||||
disabled?: boolean
|
||||
small?: boolean
|
||||
indeterminate?: boolean
|
||||
}>()
|
||||
|
||||
const modelValue = defineModel<boolean>()
|
||||
|
||||
@@ -54,6 +54,10 @@ const messages = defineMessages({
|
||||
id: 'content.card.frozen',
|
||||
defaultMessage: 'This project is locked to its current version until unfrozen.',
|
||||
},
|
||||
useEnabledForControls: {
|
||||
id: 'content.card.use-enabled-for-controls',
|
||||
defaultMessage: 'Use the Enabled for controls to enable or disable this content.',
|
||||
},
|
||||
})
|
||||
|
||||
interface Props {
|
||||
@@ -137,7 +141,20 @@ const versionNumberRef = ref<HTMLElement | null>(null)
|
||||
const fileNameRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const isDisabled = computed(() => props.disabled || props.installing)
|
||||
const isToggleDisabled = computed(() => isDisabled.value || props.toggleDisabled)
|
||||
const isToggleIndeterminate = computed(
|
||||
() =>
|
||||
props.enabledFor !== undefined && props.enabledFor.server !== props.enabledFor.player,
|
||||
)
|
||||
const isToggleDisabled = computed(
|
||||
() => isDisabled.value || props.toggleDisabled || isToggleIndeterminate.value,
|
||||
)
|
||||
const toggleTooltip = computed(() => {
|
||||
if (isToggleIndeterminate.value) {
|
||||
return formatMessage(messages.useEnabledForControls)
|
||||
}
|
||||
if (!isToggleDisabled.value) return undefined
|
||||
return props.toggleDisabledTooltip ?? props.disabledTooltip ?? undefined
|
||||
})
|
||||
const isEnabledForDisabled = computed(() => !props.enabledFor?.server && !props.enabledFor?.player)
|
||||
|
||||
const clientWarningMessage = computed(() => {
|
||||
@@ -368,7 +385,7 @@ const installTooltip = computed(() => {
|
||||
<div
|
||||
v-if="!hideActions"
|
||||
class="flex shrink-0 items-center justify-end gap-2 transition-colors duration-200"
|
||||
:class="enabledFor ? 'w-[112px]' : 'min-w-[160px]'"
|
||||
:class="enabledFor ? 'w-[168px]' : 'min-w-[160px]'"
|
||||
>
|
||||
<slot name="additionalButtonsLeft" />
|
||||
|
||||
@@ -431,14 +448,11 @@ const installTooltip = computed(() => {
|
||||
</div>
|
||||
|
||||
<Toggle
|
||||
v-if="enabled !== undefined && !hideToggle && !enabledFor"
|
||||
v-tooltip="
|
||||
isToggleDisabled && (toggleDisabledTooltip || disabledTooltip)
|
||||
? (toggleDisabledTooltip ?? disabledTooltip)
|
||||
: undefined
|
||||
"
|
||||
v-if="enabled !== undefined && !hideToggle"
|
||||
v-tooltip="toggleTooltip"
|
||||
:model-value="enabled"
|
||||
:disabled="isToggleDisabled"
|
||||
:indeterminate="isToggleIndeterminate"
|
||||
:aria-label="project.title"
|
||||
class="my-auto"
|
||||
@update:model-value="(val) => emit('update:enabled', val as boolean)"
|
||||
|
||||
@@ -24,8 +24,7 @@ const messages = defineMessages({
|
||||
},
|
||||
enabledForDescription: {
|
||||
id: 'content.enabled-for.description',
|
||||
defaultMessage:
|
||||
'Choose whether this content runs on the server, is sent to players, or is disabled in both places.',
|
||||
defaultMessage: 'Choose where this content is enabled. Turn both off to disable it.',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -300,7 +299,7 @@ function handleSort(column: ContentCardTableSortColumn) {
|
||||
v-if="hasAnyActions"
|
||||
role="columnheader"
|
||||
class="shrink-0 text-right"
|
||||
:class="hasEnabledForColumn ? 'w-[112px]' : 'min-w-[160px]'"
|
||||
:class="hasEnabledForColumn ? 'w-[168px]' : 'min-w-[160px]'"
|
||||
>
|
||||
<span class="font-semibold text-secondary">{{
|
||||
formatMessage(commonMessages.actionsLabel)
|
||||
|
||||
@@ -17,7 +17,15 @@ const messages = defineMessages({
|
||||
},
|
||||
locked: {
|
||||
id: 'content.enabled-for.locked',
|
||||
defaultMessage: 'Unlock the environment controls from the row menu to change this selection.',
|
||||
defaultMessage: 'These choices are locked. Select Unlock from the row menu to change them.',
|
||||
},
|
||||
requiredHere: {
|
||||
id: 'content.enabled-for.required-here',
|
||||
defaultMessage: 'This content needs to be enabled here to work correctly.',
|
||||
},
|
||||
notSupportedHere: {
|
||||
id: 'content.enabled-for.not-supported-here',
|
||||
defaultMessage: "This content isn't designed to be enabled here.",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -50,6 +58,12 @@ function isDisabled(side: ContentSide) {
|
||||
return props.disabled || disabledSides.value.has(side)
|
||||
}
|
||||
|
||||
function getDisabledTooltip(side: ContentSide) {
|
||||
if (props.disabled) return props.disabledTooltip
|
||||
if (!disabledSides.value.has(side)) return undefined
|
||||
return formatMessage(isSelected(side) ? messages.requiredHere : messages.notSupportedHere)
|
||||
}
|
||||
|
||||
function toggle(side: ContentSide) {
|
||||
if (isDisabled(side)) return
|
||||
emit('update:model-value', side, !isSelected(side))
|
||||
@@ -61,11 +75,7 @@ function toggle(side: ContentSide) {
|
||||
<button
|
||||
v-for="side in sides"
|
||||
:key="side"
|
||||
v-tooltip="
|
||||
isDisabled(side)
|
||||
? (disabledTooltip ?? modelValue.disabledTooltip ?? formatMessage(messages.locked))
|
||||
: undefined
|
||||
"
|
||||
v-tooltip="getDisabledTooltip(side)"
|
||||
type="button"
|
||||
class="flex h-8 items-center rounded-xl border border-solid px-3 text-sm font-medium transition-[background-color,border-color,color,opacity,transform] duration-100 focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-brand-shadow"
|
||||
:class="[
|
||||
|
||||
+4
-5
@@ -119,8 +119,7 @@ const messages = defineMessages({
|
||||
},
|
||||
enabledForDescription: {
|
||||
id: 'content.enabled-for.description',
|
||||
defaultMessage:
|
||||
'Choose whether this content runs on the server, is sent to players, or is disabled in both places.',
|
||||
defaultMessage: 'Choose where this content is enabled. Turn both off to disable it.',
|
||||
},
|
||||
pleaseWait: {
|
||||
id: 'content.enabled-for.please-wait',
|
||||
@@ -300,7 +299,7 @@ const tableItems = computed<ContentCardTableItem[]>(() =>
|
||||
link: item.source.link ?? sourceProjectLink(item.source.project),
|
||||
}
|
||||
: undefined,
|
||||
...(props.enableToggle ? { enabled: item.enabled } : {}),
|
||||
...(props.enableToggle || props.enableEnabledFor ? { enabled: item.enabled } : {}),
|
||||
...(props.enableEnabledFor ? { enabledFor: item.enabledFor } : {}),
|
||||
locked: item.locked,
|
||||
installing: item.installing === true,
|
||||
@@ -638,7 +637,7 @@ defineExpose({ show, showLoading, hide, getState, restore, updateItem, setItems
|
||||
<div
|
||||
v-if="showTableActions"
|
||||
class="shrink-0 text-right"
|
||||
:class="props.enableEnabledFor ? 'w-[112px]' : 'min-w-[160px]'"
|
||||
:class="props.enableEnabledFor ? 'w-[168px]' : 'min-w-[160px]'"
|
||||
>
|
||||
<span class="font-semibold text-secondary">{{
|
||||
formatMessage(commonMessages.actionsLabel)
|
||||
@@ -656,7 +655,7 @@ defineExpose({ show, showLoading, hide, getState, restore, updateItem, setItems
|
||||
hide-header
|
||||
flat
|
||||
v-on="
|
||||
props.enableToggle
|
||||
props.enableToggle || props.enableEnabledFor
|
||||
? { 'update:enabled': (id: string, val: boolean) => handleEnabledChange(id, val) }
|
||||
: {}
|
||||
"
|
||||
|
||||
@@ -754,7 +754,7 @@ async function confirmDisable() {
|
||||
if (itemsToDisable.length === 1) {
|
||||
const item = itemsToDisable[0]
|
||||
const id = getItemId(item)
|
||||
markChanging(id)
|
||||
if (!item.enabledFor) markChanging(id)
|
||||
try {
|
||||
if (ctx.bulkDisableItems) {
|
||||
await ctx.bulkDisableItems(itemsToDisable)
|
||||
@@ -762,7 +762,7 @@ async function confirmDisable() {
|
||||
await ctx.toggleEnabled?.(item)
|
||||
}
|
||||
} finally {
|
||||
unmarkChanging(id)
|
||||
if (!item.enabledFor) unmarkChanging(id)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -781,11 +781,11 @@ async function handleToggleEnabledById(id: string, _value: boolean) {
|
||||
await promptDisableItems([item])
|
||||
return
|
||||
}
|
||||
markChanging(id)
|
||||
if (!item.enabledFor) markChanging(id)
|
||||
try {
|
||||
await ctx.toggleEnabled(item)
|
||||
} finally {
|
||||
unmarkChanging(id)
|
||||
if (!item.enabledFor) unmarkChanging(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -844,12 +844,7 @@ async function handleSetEnabledForById(
|
||||
const item = ctx.items.value.find((candidate) => getItemId(candidate) === id)
|
||||
if (!item) return
|
||||
|
||||
markChanging(id)
|
||||
try {
|
||||
await ctx.setEnabledFor(item, side, enabled)
|
||||
} finally {
|
||||
unmarkChanging(id)
|
||||
}
|
||||
await ctx.setEnabledFor(item, side, enabled)
|
||||
}
|
||||
|
||||
// Bulk updating
|
||||
|
||||
@@ -57,7 +57,6 @@ export interface ContentEnabledForState {
|
||||
player: boolean
|
||||
locked: boolean
|
||||
disabledSides?: ContentSide[]
|
||||
disabledTooltip?: string | null
|
||||
warningTooltip?: string | null
|
||||
}
|
||||
|
||||
|
||||
@@ -104,14 +104,10 @@ const messages = defineMessages({
|
||||
id: 'hosting.content.failed-to-install',
|
||||
defaultMessage: 'Failed to install content',
|
||||
},
|
||||
sideLocked: {
|
||||
id: 'hosting.content.enabled-for.side-locked',
|
||||
defaultMessage: 'This option conflicts with the detected environment.',
|
||||
},
|
||||
unknownEnvironment: {
|
||||
id: 'hosting.content.enabled-for.unknown-environment',
|
||||
defaultMessage:
|
||||
'We could not determine where this content should run. Check that the selected environments are correct.',
|
||||
"We couldn't tell where this content should be enabled. Review the Server and Player choices.",
|
||||
},
|
||||
lockEnvironment: {
|
||||
id: 'hosting.content.enabled-for.lock-environment',
|
||||
@@ -331,21 +327,31 @@ function hasDetectedEnvironment(addon: Archon.Content.v1.Addon) {
|
||||
return environment !== undefined && environment !== 'unknown'
|
||||
}
|
||||
|
||||
function getLockedSides(addon: Archon.Content.v1.Addon): ContentSide[] {
|
||||
if (addon.side_toggle_unlocked || !hasDetectedEnvironment(addon)) return []
|
||||
|
||||
function getEnvironmentLockedSides(addon: Archon.Content.v1.Addon): ContentSide[] {
|
||||
switch (getAddonEnvironment(addon)) {
|
||||
case 'client_and_server':
|
||||
case 'client_only':
|
||||
case 'singleplayer_only':
|
||||
return ['server']
|
||||
case 'server_only':
|
||||
case 'dedicated_server_only':
|
||||
return ['server', 'player']
|
||||
case 'client_only_server_optional':
|
||||
return ['player']
|
||||
case 'server_only_client_optional':
|
||||
return ['server']
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function getLockedSides(addon: Archon.Content.v1.Addon): ContentSide[] {
|
||||
return addon.side_toggle_unlocked ? [] : getEnvironmentLockedSides(addon)
|
||||
}
|
||||
|
||||
function hasEnvironmentLock(addon: Archon.Content.v1.Addon) {
|
||||
return getEnvironmentLockedSides(addon).length > 0
|
||||
}
|
||||
|
||||
function getEnabledForWarning(addon: Archon.Content.v1.Addon) {
|
||||
if (addon.pack_client_retained) return formatMessage(commonMessages.clientRetainedWarning)
|
||||
if (addon.pack_client_depends) return formatMessage(commonMessages.clientDependsWarning)
|
||||
@@ -618,24 +624,26 @@ const deleteMutation = useMutation({
|
||||
|
||||
type SetEnabledForVariables = {
|
||||
addon: Archon.Content.v1.Addon
|
||||
side: ContentSide
|
||||
sides: ContentSide[]
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
const setEnabledForMutation = useMutation({
|
||||
mutationFn: async ({ addon, side, enabled }: SetEnabledForVariables) => {
|
||||
mutationFn: async ({ addon, sides, enabled }: SetEnabledForVariables) => {
|
||||
const request: Archon.Content.v1.SetAddonEnabledRequest = {
|
||||
filename: addon.filename,
|
||||
kind: addon.kind,
|
||||
enabled,
|
||||
}
|
||||
if (side === 'server') {
|
||||
await client.archon.content_v1.setAddonEnabledServer(serverId, worldId.value!, request)
|
||||
} else {
|
||||
await client.archon.content_v1.setAddonEnabledPlayer(serverId, worldId.value!, request)
|
||||
}
|
||||
await Promise.all(
|
||||
sides.map((side) =>
|
||||
side === 'server'
|
||||
? client.archon.content_v1.setAddonEnabledServer(serverId, worldId.value!, request)
|
||||
: client.archon.content_v1.setAddonEnabledPlayer(serverId, worldId.value!, request),
|
||||
),
|
||||
)
|
||||
},
|
||||
onMutate: async ({ addon, side, enabled }) => {
|
||||
onMutate: async ({ addon, sides, enabled }) => {
|
||||
const targetQueryKey = getAddonQueryKey(addon)
|
||||
await queryClient.cancelQueries({ queryKey: targetQueryKey })
|
||||
const previousData = queryClient.getQueryData<Archon.Content.v1.Addons>(targetQueryKey)
|
||||
@@ -645,9 +653,11 @@ const setEnabledForMutation = useMutation({
|
||||
...oldData,
|
||||
addons: (oldData.addons ?? []).map((candidate) => {
|
||||
if (candidate.filename !== addon.filename || candidate.kind !== addon.kind) return candidate
|
||||
return side === 'server'
|
||||
? { ...candidate, disabled_server: !enabled }
|
||||
: { ...candidate, disabled_player: !enabled }
|
||||
return {
|
||||
...candidate,
|
||||
...(sides.includes('server') ? { disabled_server: !enabled } : {}),
|
||||
...(sides.includes('player') ? { disabled_player: !enabled } : {}),
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
@@ -718,7 +728,19 @@ async function handleSetEnabledFor(item: ContentItem, side: ContentSide, enabled
|
||||
if (contentActionDisabled.value) return
|
||||
const addon = getAddonForItem(item)
|
||||
if (!addon) return
|
||||
await setEnabledForMutation.mutateAsync({ addon, side, enabled })
|
||||
await setEnabledForMutation.mutateAsync({ addon, sides: [side], enabled })
|
||||
}
|
||||
|
||||
async function handleToggleEnabled(item: ContentItem) {
|
||||
if (contentActionDisabled.value || !item.enabledFor) return
|
||||
if (item.enabledFor.server !== item.enabledFor.player) return
|
||||
const addon = getAddonForItem(item)
|
||||
if (!addon) return
|
||||
await setEnabledForMutation.mutateAsync({
|
||||
addon,
|
||||
sides: ['server', 'player'],
|
||||
enabled: !item.enabledFor.server,
|
||||
})
|
||||
}
|
||||
|
||||
async function handleModpackSetEnabledFor(
|
||||
@@ -726,13 +748,18 @@ async function handleModpackSetEnabledFor(
|
||||
side: ContentSide,
|
||||
enabled: boolean,
|
||||
) {
|
||||
modpackContentModal.value?.updateItem(item.file_name, { disabled: true })
|
||||
try {
|
||||
await handleSetEnabledFor(item, side, enabled)
|
||||
} catch {
|
||||
return
|
||||
} finally {
|
||||
modpackContentModal.value?.updateItem(item.file_name, { disabled: false })
|
||||
}
|
||||
}
|
||||
|
||||
async function handleModpackToggleEnabled(item: ContentItem) {
|
||||
try {
|
||||
await handleToggleEnabled(item)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -946,6 +973,7 @@ function addonToContentItem(addon: AddonWithUiState): ContentItem {
|
||||
: undefined
|
||||
const serverEnabled = !addon.disabled_server
|
||||
const playerEnabled = !addon.disabled_player
|
||||
const lockedSides = getLockedSides(addon)
|
||||
return {
|
||||
project: {
|
||||
...(projectMetadata ?? {}),
|
||||
@@ -976,9 +1004,8 @@ function addonToContentItem(addon: AddonWithUiState): ContentItem {
|
||||
enabledFor: {
|
||||
server: serverEnabled,
|
||||
player: playerEnabled,
|
||||
locked: hasDetectedEnvironment(addon) && !addon.side_toggle_unlocked,
|
||||
disabledSides: getLockedSides(addon),
|
||||
disabledTooltip: formatMessage(messages.sideLocked),
|
||||
locked: lockedSides.length > 0,
|
||||
disabledSides: lockedSides,
|
||||
warningTooltip: getEnabledForWarning(addon),
|
||||
},
|
||||
embeddedIcon,
|
||||
@@ -1265,7 +1292,7 @@ function getOverflowOptions(item: ContentItem): OverflowMenuOption[] {
|
||||
})
|
||||
}
|
||||
|
||||
if (addon && hasDetectedEnvironment(addon)) {
|
||||
if (addon && hasEnvironmentLock(addon)) {
|
||||
if (options.length > 0) options.push({ type: 'divider' })
|
||||
options.push({
|
||||
id: 'toggle-side-lock',
|
||||
@@ -1291,7 +1318,10 @@ provideContentManager({
|
||||
disableAddContent: computed(() => !canSetup.value),
|
||||
disableAddContentTooltip: permissionDeniedMessage.value,
|
||||
contentTypeLabel: type,
|
||||
toggleEnabled: handleToggleEnabled,
|
||||
setEnabledFor: handleSetEnabledFor,
|
||||
canToggleItem: (item) =>
|
||||
item.enabledFor !== undefined && item.enabledFor.server === item.enabledFor.player,
|
||||
deleteItem: handleDeleteItem,
|
||||
bulkDeleteItems: handleBulkDelete,
|
||||
refresh: async () => {
|
||||
@@ -1332,7 +1362,6 @@ provideContentManager({
|
||||
enabled: item.enabled,
|
||||
enabledFor: item.enabledFor,
|
||||
embeddedIcon: item.embeddedIcon,
|
||||
hideToggle: true,
|
||||
}
|
||||
},
|
||||
filterPersistKey: `server:${serverId}:${worldId.value}`,
|
||||
@@ -1367,6 +1396,7 @@ provideContentManager({
|
||||
:get-overflow-options="getOverflowOptions"
|
||||
:action-disabled="contentActionDisabled"
|
||||
:action-disabled-tooltip="contentActionBusyMessage ?? undefined"
|
||||
@update:enabled="handleModpackToggleEnabled"
|
||||
@update:enabled-for="handleModpackSetEnabledFor"
|
||||
@hide="isModpackContentModalOpen = false"
|
||||
/>
|
||||
|
||||
@@ -8,7 +8,8 @@ const meta = {
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Toggle uses touch manipulation so double tapping on mobile does not zoom.',
|
||||
component:
|
||||
'Toggle uses touch manipulation so double tapping on mobile does not zoom. Set indeterminate to show a line between its on and off states.',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -30,6 +31,13 @@ export const Checked: Story = {
|
||||
},
|
||||
}
|
||||
|
||||
export const Indeterminate: Story = {
|
||||
args: {
|
||||
modelValue: false,
|
||||
indeterminate: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
modelValue: false,
|
||||
@@ -63,6 +71,9 @@ export const AllStates: Story = {
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<Toggle :model-value="true" /> On
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<Toggle :model-value="false" :indeterminate="true" /> Indeterminate
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<Toggle :model-value="false" :disabled="true" /> Disabled
|
||||
</div>
|
||||
@@ -73,6 +84,9 @@ export const AllStates: Story = {
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<Toggle :model-value="true" :small="true" /> On
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<Toggle :model-value="false" :indeterminate="true" :small="true" /> Indeterminate
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<Toggle :model-value="false" :small="true" :disabled="true" /> Disabled
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user