From edb2cba204dd09b02f2795ce620f16c6b7a64cbe Mon Sep 17 00:00:00 2001 From: Dinh-Van Colomban Date: Mon, 10 Feb 2025 22:50:05 +0100 Subject: [PATCH] feat(button): rework of button elevation --- demo/components/DemoButtons.svelte | 16 +- demo/components/DemoTooltips.svelte | 2 +- src/lib/buttons/NeoButton.svelte | 215 +++++++++++----------- src/lib/buttons/NeoCheckboxButton.svelte | 4 +- src/lib/buttons/NeoRadioButton.svelte | 4 +- src/lib/buttons/NeoSwitchButton.svelte | 2 +- src/lib/buttons/neo-button.model.ts | 83 +++++++-- src/lib/cards/NeoCard.svelte | 22 ++- src/lib/inputs/NeoColorPicker.svelte | 8 +- src/lib/inputs/NeoDateTime.svelte | 10 +- src/lib/inputs/NeoFilePicker.svelte | 12 +- src/lib/inputs/NeoNativeSelect.svelte | 8 +- src/lib/inputs/NeoNumberStep.svelte | 8 +- src/lib/inputs/NeoPassword.svelte | 8 +- src/lib/inputs/NeoRange.svelte | 3 +- src/lib/inputs/NeoSelect.svelte | 11 +- src/lib/inputs/NeoTextarea.svelte | 13 +- src/lib/inputs/common/NeoAffix.svelte | 3 +- src/lib/inputs/common/NeoBaseInput.svelte | 4 + src/lib/inputs/common/NeoInput.svelte | 16 +- src/lib/inputs/common/neo-input.model.ts | 5 - src/lib/list/NeoListBaseItem.svelte | 4 +- src/lib/list/NeoListSearch.svelte | 2 +- src/lib/nav/NeoTab.svelte | 1 + src/lib/pill/NeoPill.svelte | 8 +- src/lib/styles/common/colors.scss | 6 - src/lib/utils/shadow.utils.ts | 12 +- 27 files changed, 278 insertions(+), 212 deletions(-) diff --git a/demo/components/DemoButtons.svelte b/demo/components/DemoButtons.svelte index dcaa658..74bdfc3 100644 --- a/demo/components/DemoButtons.svelte +++ b/demo/components/DemoButtons.svelte @@ -14,6 +14,7 @@ import NeoCheckboxButton from '~/buttons/NeoCheckboxButton.svelte'; import NeoRadioButton from '~/buttons/NeoRadioButton.svelte'; import NeoSwitchButton from '~/buttons/NeoSwitchButton.svelte'; + import { NeoFlatButton, NeoRaisedButton, NeoTextButton } from '~/buttons/neo-button.model.js'; import IconAccount from '~/icons/IconAccount.svelte'; import NeoSelect from '~/inputs/NeoSelect.svelte'; import { displayValue } from '~/inputs/neo-select.model'; @@ -24,8 +25,6 @@ disabled: false, skeleton: false, loading: false, - inset: false, - shallow: false, glass: false, tinted: false, color: '', @@ -35,10 +34,15 @@ const columns = [ { label: 'Default' }, - { label: 'Rounded', props: { rounded: true, shallow: true } }, - { label: 'Flat', props: { flat: true, shallow: true } }, - { label: 'Text', props: { text: true, shallow: true } }, + { label: 'Rounded', props: { rounded: true } }, + { label: 'Flat', props: NeoFlatButton }, + { label: 'Text', props: NeoTextButton }, + { label: 'Raise', props: NeoRaisedButton }, ]; + + // TODO : button group + // TODO : select for hover, active, elevation + // TODO - support override {#snippet icon()} @@ -83,8 +87,6 @@ Glass Tinted - Inset - Shallow Disabled Loading Skeleton diff --git a/demo/components/DemoTooltips.svelte b/demo/components/DemoTooltips.svelte index ccb8682..a3b98ca 100644 --- a/demo/components/DemoTooltips.svelte +++ b/demo/components/DemoTooltips.svelte @@ -232,7 +232,7 @@ width="min" tooltipProps={{ ...options, openOnFocus: false, openOnHover: false }} > - + {#key complexSelected?.item?.id}
import { width } from '@dvcol/svelte-utils/transition'; - import type { NeoButtonProps } from '~/buttons/neo-button.model.js'; + import { type NeoButtonProps, NeoTextButton } from '~/buttons/neo-button.model.js'; import IconCircleLoading from '~/icons/IconCircleLoading.svelte'; import { toAction, toActionProps, toTransition, toTransitionProps } from '~/utils/action.utils.js'; import { getColorVariable } from '~/utils/colors.utils.js'; + import { + coerce, + computeGlassFilter, + computeHoverShadowElevation, + computeShadowElevation, + DefaultShadowActiveElevation, + DefaultShadowElevation, + DefaultShadowHoverElevation, + isShadowFlat, + } from '~/utils/shadow.utils.js'; import { quickDurationProps } from '~/utils/transition.utils.js'; /* eslint-disable prefer-const -- necessary for binding checked */ @@ -30,17 +40,16 @@ // Styles start, color, - ghost, text, - flat, + ghost, glass, tinted, rounded, - inset, - shallow, + borderless = text || ghost, reverse, coalesce, pulse, + scale = !ghost, // Flex justify, @@ -63,21 +72,50 @@ use, // Other props - ...rest + ..._rest }: NeoButtonProps = $props(); /* eslint-enable prefer-const */ + // TODO - Rework button group / nav + + const { + elevation: _elevation = DefaultShadowElevation, + hover: _hover = DefaultShadowHoverElevation, + active: _active = DefaultShadowActiveElevation, + pressed: _pressed, + ...rest + } = $derived.by(() => { + if (text || ghost) return { ...NeoTextButton, ..._rest }; + return _rest; + }); + + const elevation = $derived(coerce(_elevation)); + const hover = $derived(coerce(_hover)); + const active = $derived(coerce(_active)); + const activePressed = $derived(_pressed ?? elevation + hover > 0); + + const filter = $derived(computeGlassFilter(elevation, glass)); + const boxShadow = $derived(computeShadowElevation(elevation, { glass })); + const hoverShadow = $derived(computeHoverShadowElevation(elevation, hover, { glass }) ?? boxShadow); + const activeShadow = $derived( + computeShadowElevation(active, { glass, pressed: !glass && activePressed, active: glass && activePressed }) ?? boxShadow, + ); + + const activeFlat = $derived(isShadowFlat(activeShadow)); + const hoverFlat = $derived(isShadowFlat(boxShadow) && !isShadowFlat(hoverShadow)); + const flatHover = $derived(isShadowFlat(hoverShadow) && !isShadowFlat(boxShadow)); + let enter = $state(false); - let active = $state(false); - const pressed = $derived(enter || active || checked); + let clicked = $state(false); + const pressed = $derived(enter || clicked || checked); const empty = $derived(only || (!children && !label)); let timeout: ReturnType; const onActive = () => { clearTimeout(timeout); - active = true; + clicked = true; timeout = setTimeout(() => { - active = false; + clicked = false; }, 150); }; @@ -139,15 +177,24 @@ class:neo-skeleton={skeleton} class:neo-start={start} class:neo-glass={glass} + class:neo-scale={scale} class:neo-tinted={tinted} - class:neo-flat={flat || text || ghost} + class:neo-flat={!elevation} + class:neo-hover={hover} + class:neo-hover-flat={hoverFlat} + class:neo-flat-hover={flatHover} + class:neo-flat-active={activeFlat} class:neo-ghost={ghost} - class:neo-borderless={text || (ghost && !flat)} - class:neo-inset={inset} - class:neo-shallow={shallow} + class:neo-borderless={borderless} + class:neo-inset={elevation < 0} + class:neo-inset-hover={elevation + hover < 0} class:neo-rounded={rounded} class:neo-empty={empty} style:--neo-btn-text-color={getColorVariable(color)} + style:--neo-btn-backdrop-filter={filter} + style:--neo-btn-box-shadow={boxShadow} + style:--neo-btn-box-shadow-hover={hoverShadow} + style:--neo-btn-box-shadow-active={activeShadow} style:justify-content={justify} style:align-items={align} style:flex @@ -220,19 +267,6 @@ cursor: wait; } - &.neo-inset { - --neo-btn-box-shadow-active: var(--neo-box-shadow-inset-1); - --neo-btn-box-shadow-focus-active: var(--neo-box-shadow-inset-1); - } - - &.neo-shallow { - --neo-btn-box-shadow-active: var(--neo-box-shadow-pressed-1); - --neo-btn-box-shadow-focus-active: var(--neo-box-shadow-pressed-1); - --neo-btn-box-shadow-active-flat: var(--neo-box-shadow-inset-2); - --neo-btn-box-shadow-active-flat-toggle: var(--neo-box-shadow-inset-2); - --neo-btn-scale-pressed: 1; - } - .neo-icon, .neo-content { display: inline-flex; @@ -266,6 +300,7 @@ &.neo-ghost { justify-content: unset; + margin: var(--neo-btn-margin, 0); padding: var(--neo-btn-padding, 0); .neo-content { @@ -274,16 +309,24 @@ } } + &:focus-visible { + outline: var(--neo-border-width, 1px) solid var(--neo-border-color-focused); + } + + &:focus-visible, &:hover { box-shadow: var(--neo-btn-box-shadow-hover, var(--neo-box-shadow-raised-2)); } + &:focus-visible .neo-content, &:hover .neo-content { color: var(--neo-btn-text-color-hover, var(--neo-text-color-hover)); } - &:focus-visible .neo-content { - color: var(--neo-btn-text-color-focused, var(--neo-text-color-focused)); + &:disabled .neo-content, + &[disabled]:not([disabled='false']) .neo-content { + color: var(--neo-btn-text-color-disabled, var(--neo-text-color-disabled)); + scale: 1; } &.neo-pressed, @@ -298,110 +341,58 @@ border-radius 0.3s ease, box-shadow 0.15s ease-out; - .neo-content { + &.neo-scale .neo-content { color: var(--neo-btn-text-color-active, var(--neo-text-color-active)); scale: var(--neo-btn-scale-pressed, 0.98); } } - &:disabled:disabled .neo-content, - &[disabled]:not([disabled='false']) .neo-content { - color: var(--neo-btn-text-color-disabled, var(--neo-text-color-disabled)); - scale: 1; - } - - &:focus-visible { - outline: none; - box-shadow: var(--neo-btn-box-shadow-focus, var(--neo-box-shadow-raised-2)); - transition: - opacity 0.3s ease, - color 0.3s ease, - background-color 0.3s ease, - border-color 0.3s ease, - backdrop-filter 0.3s ease, - border-radius 0.3s ease, - box-shadow 0.15s ease-out; - - &:not(:hover) .neo-content { - transition: - color 0s ease, - scale 0.3s ease; - } - - &:hover .neo-content { - color: var(--neo-btn-text-color-focused-hover, var(--neo-text-color-focused)); - } - - &.neo-pressed, - &:active:not(.neo-loading) { - box-shadow: var(--neo-btn-box-shadow-focus-active, var(--neo-box-shadow-pressed-2)); - - .neo-content { - color: var(--neo-btn-text-color-focused-active, var(--neo-text-color-focused-active)); - } - } - } - &.neo-borderless { border-color: transparent !important; } - &.neo-flat { - --neo-coalesce-box-shadow: var(--neo-box-shadow-raised-2); - --neo-pulse-box-shadow: var(--neo-box-shadow-raised-2); - - margin: var(--neo-btn-margin, 0); + &.neo-hover.neo-flat-hover.neo-hovered, + &.neo-hover.neo-flat-hover:focus-visible:not(:active, .neo-pressed), + &.neo-hover.neo-flat-hover:hover:not(:active, .neo-pressed), + &.neo-flat-active.neo-pressed, + &.neo-flat-active:active, + &.neo-flat:not(.neo-borderless, .neo-hover-flat:hover, .neo-hover-flat:focus-visible, .neo-hover-flat.neo-hovered, .neo-pressed, :active) { border-color: var(--neo-btn-border-color, var(--neo-border-color)); - box-shadow: var(--neo-box-shadow-flat); - - &:focus-visible, - &:hover { - border-color: transparent; - box-shadow: var(--neo-btn-box-shadow-hover-flat, var(--neo-box-shadow-inset-1)); - } - - &.neo-pressed, - &:active:not(.neo-loading) { - border-color: transparent; - box-shadow: var(--neo-btn-box-shadow-active-flat, var(--neo-box-shadow-inset-3)); - - &.neo-toggle { - box-shadow: var(--neo-btn-box-shadow-active-flat-toggle, var(--neo-box-shadow-inset-3)); - } - } } &.neo-glass { - --neo-box-shadow-inset-1: var(--neo-glass-box-shadow-inset-1); - --neo-box-shadow-inset-2: var(--neo-glass-box-shadow-inset-2); - --neo-box-shadow-inset-3: var(--neo-glass-box-shadow-inset-3); - --neo-box-shadow-flat: var(--neo-glass-box-shadow-flat); - --neo-box-shadow-pressed-1: var(--neo-glass-box-shadow-active-1); - --neo-box-shadow-pressed-2: var(--neo-glass-box-shadow-active-2); - --neo-box-shadow-raised-2: var(--neo-glass-box-shadow-raised-2); - --neo-box-shadow-raised-3: var(--neo-glass-box-shadow-raised-3); --neo-background-color-tinted: var(--neo-glass-background-color-tinted); + --neo-skeleton-color: var(--neo-glass-skeleton-color); + --neo-border-color: var(--neo-glass-border-color); background-color: var(--neo-btn-bg-color, var(--neo-glass-background-color)); - border-color: var( - --neo-btn-border-color, - var(--neo-glass-top-border-color) var(--neo-glass-right-border-color) var(--neo-glass-bottom-border-color) var(--neo-glass-left-border-color) - ); backdrop-filter: var(--neo-btn-backdrop-filter, var(--neo-blur-2) var(--neo-saturate-3)); - &.neo-pressed, - &:active:not(.neo-loading) { - &.neo-inset { - border-color: transparent; - } + &:not( + .neo-inset, + .neo-inset-hover:hover, + .neo-inset-hover:focus-visible, + .neo-borderless, + .neo-hover-flat:hover, + .neo-hover-flat:focus-visible, + .neo-hover-flat.neo-hovered, + .neo-pressed, + :active + ) { + border-color: var( + --neo-btn-border-color, + var(--neo-glass-top-border-color) var(--neo-glass-right-border-color) var(--neo-glass-bottom-border-color) + var(--neo-glass-left-border-color) + ); } - &.neo-flat:not(.neo-pressed, :active:not(.neo-loading)) { + &.neo-hover.neo-flat-hover.neo-hovered, + &.neo-hover.neo-flat-hover:focus-visible:not(:active, .neo-pressed), + &.neo-hover.neo-flat-hover:hover:not(:active, .neo-pressed), + &.neo-flat-active.neo-pressed, + &.neo-flat-active:active, + &.neo-flat:not(.neo-borderless, .neo-hover-flat:hover, .neo-hover-flat:focus-visible, .neo-hover-flat.neo-hovered, .neo-pressed, :active) { border-color: var(--neo-btn-border-color, var(--neo-glass-border-color-flat)); - - &:hover { - border-color: transparent; - } } } diff --git a/src/lib/buttons/NeoCheckboxButton.svelte b/src/lib/buttons/NeoCheckboxButton.svelte index e25043d..2396f46 100644 --- a/src/lib/buttons/NeoCheckboxButton.svelte +++ b/src/lib/buttons/NeoCheckboxButton.svelte @@ -111,8 +111,8 @@ box-shadow: var(--neo-checkbox-checked-shadow, var(--neo-box-shadow-pressed-2)); } - &.neo-inset:focus-visible { - border-color: var(--neo-checkbox-border-color-focused, var(--neo-border-color-focused)); + &:focus-visible { + outline: var(--neo-border-width, 1px) solid var(--neo-border-color-focused); } &.neo-disabled { diff --git a/src/lib/buttons/NeoRadioButton.svelte b/src/lib/buttons/NeoRadioButton.svelte index 6f80f77..9da8105 100644 --- a/src/lib/buttons/NeoRadioButton.svelte +++ b/src/lib/buttons/NeoRadioButton.svelte @@ -109,8 +109,8 @@ box-shadow: var(--neo-radio-checked-shadow, var(--neo-box-shadow-pressed-2)); } - &.neo-inset:focus-visible { - border-color: var(--neo-checkbox-border-color-focused, var(--neo-border-color-focused)); + &:focus-visible { + outline: var(--neo-border-width, 1px) solid var(--neo-border-color-focused); } &.neo-disabled { diff --git a/src/lib/buttons/NeoSwitchButton.svelte b/src/lib/buttons/NeoSwitchButton.svelte index fa35a89..de2aa7b 100644 --- a/src/lib/buttons/NeoSwitchButton.svelte +++ b/src/lib/buttons/NeoSwitchButton.svelte @@ -265,7 +265,7 @@ } &:focus-visible { - border-color: var(--neo-radio-border-color-focused, var(--neo-border-color-focused)); + outline: var(--neo-border-width, 1px) solid var(--neo-border-color-focused); } &:active { diff --git a/src/lib/buttons/neo-button.model.ts b/src/lib/buttons/neo-button.model.ts index db38b64..941bd2d 100644 --- a/src/lib/buttons/neo-button.model.ts +++ b/src/lib/buttons/neo-button.model.ts @@ -4,6 +4,17 @@ import type { HTMLActionProps } from '~/utils/action.utils.js'; import type { Color } from '~/utils/colors.utils.js'; import type { HTMLFlexProps, HTMLNeoBaseElement, HTMLRefProps, SvelteEvent } from '~/utils/html-element.utils.js'; +import { + type ShadowElevation, + type ShadowElevationString, + type ShadowHoverElevation, + type ShadowHoverElevationsString, +} from '~/utils/shadow.utils.js'; + +export type NeoButtonElevation = ShadowElevation | ShadowElevationString; +export type NeoButtonHoverElevation = ShadowHoverElevation | ShadowHoverElevationsString; +export type NeoButtonActiveElevation = ShadowHoverElevation | ShadowHoverElevationsString; + export type NeoButtonProps = { // Snippets @@ -75,17 +86,50 @@ export type NeoButtonProps = */ color?: Color; /** - If true, only the button content will be displayed. + * If true, button specific styles will be removed (padding, text align & justification). + * + * @defaults`{ elevation: 0, hover: -1, active: -2, pressed: false, scale: false, borderless: true }` */ ghost?: boolean; /** - * If true, the button will be displayed with no elevation or border. + * Shorthand for a flat borderless inset button. + * + * @defaults`{ elevation: 0, hover: -1, active: -3, pressed: false, borderless: true }` */ text?: boolean; + /** + * Input elevation. + * @default 3 + */ + elevation?: NeoButtonElevation; + /** + * Weather to increase/decrease the elevation when hovered/focused. + * + * @default -1 (relative to base elevation) + */ + hover?: NeoButtonHoverElevation; + /** + * Weather to increase/decrease the elevation when active. + * + * @default -2 (relative to base elevation) + */ + active?: NeoButtonActiveElevation; + /** + * Weather the pressed state should be displayed as recessed or pressed. + * + * @default true if `elevation` + `hover` > 0 && `active` < 0 + */ + pressed?: boolean; + /** + * Weather to scale the button content on active state. + * + * @default true + */ + scale?: boolean; /** * If true, the button will be displayed with no elevation. */ - flat?: boolean; + borderless?: boolean; /** * If true, the button will be displayed with a glass effect. */ @@ -98,14 +142,6 @@ export type NeoButtonProps = * If true, the button will have a rounded border. */ rounded?: boolean; - /** - * If true, the button will be inset instead of pressed when active. - */ - inset?: boolean; - /** - * If true, the button will be displayed with a shallower elevation when pressed. - */ - shallow?: boolean; /** * If true, the flex direction of the button will be reversed. */ @@ -153,3 +189,28 @@ export type NeoButtonProps = 'onclick' | 'onkeydown' | 'onkeyup' > >; + +export type NeoButtonTemplate = Pick< + NeoButtonProps, + 'elevation' | 'hover' | 'active' | 'pressed' | 'borderless' | 'glass' | 'tinted' | 'rounded' | 'reverse' | 'coalesce' | 'pulse' +>; + +export const NeoRaisedButton: NeoButtonTemplate = { + elevation: 0, + hover: 1, + active: -1, + pressed: true, + borderless: true, +}; + +export const NeoFlatButton: NeoButtonTemplate = { + elevation: 0, + hover: -1, + active: -2, + pressed: false, +}; + +export const NeoTextButton: NeoButtonTemplate = { + ...NeoFlatButton, + borderless: true, +}; diff --git a/src/lib/cards/NeoCard.svelte b/src/lib/cards/NeoCard.svelte index b129b5c..bfa4fac 100644 --- a/src/lib/cards/NeoCard.svelte +++ b/src/lib/cards/NeoCard.svelte @@ -166,7 +166,7 @@
{#snippet icon()} - + {/snippet}
@@ -201,7 +201,8 @@ class:neo-hovered={hovered} class:neo-start={start} class:neo-raised={elevation > 3 || hoverElevation > 3} - class:neo-inset={elevation < 0 || hoverElevation < 0} + class:neo-inset={elevation < 0} + class:neo-inset-hover={hoverElevation < 0} class:neo-deep={elevation < -3 || hoverElevation < -3} class:neo-flat={!elevation} class:neo-hover-flat={hoverFlat} @@ -391,14 +392,12 @@ } .neo-card-close { + flex: 0 0 auto; align-self: flex-start; margin-left: auto; opacity: 0; transition: opacity 0.3s ease; - --neo-btn-text-color-focused: var(--neo-close-color-focused, rgb(255 0 0 / 50%)); - --neo-btn-text-color-focused-hover: var(--neo-close-color-hover, rgb(255 0 0 / 75%)); - --neo-text-color-focused-active: var(--neo-close-color, rgb(255 0 0)); --neo-btn-text-color-hover: var(--neo-close-color-hover, rgb(255 0 0 / 75%)); --neo-btn-text-color-active: var(--neo-close-color, rgb(255 0 0)); } @@ -442,11 +441,20 @@ backdrop-filter: var(--neo-card-glass-blur, var(--neo-blur-3) var(--neo-saturate-2)); &.neo-convex, - &.neo-inset { + &.neo-inset, + &.neo-inset-hover:hover { border-color: var(--neo-card-border-color, transparent); } - &:not(.neo-inset, .neo-convex, .neo-borderless, .neo-hover-flat:hover, .neo-hover-flat.neo-hovered, .neo-hover-flat:focus-within) { + &:not( + .neo-inset, + .neo-inset-hover:hover, + .neo-convex, + .neo-borderless, + .neo-hover-flat:hover, + .neo-hover-flat.neo-hovered, + .neo-hover-flat:focus-within + ) { border-color: var( --neo-card-border-color, var(--neo-glass-top-border-color) var(--neo-glass-right-border-color) var(--neo-glass-bottom-border-color) diff --git a/src/lib/inputs/NeoColorPicker.svelte b/src/lib/inputs/NeoColorPicker.svelte index f354e7e..cdcd302 100644 --- a/src/lib/inputs/NeoColorPicker.svelte +++ b/src/lib/inputs/NeoColorPicker.svelte @@ -2,16 +2,16 @@ import { getUUID } from '@dvcol/common-utils/common/string'; import type { FormEventHandler } from 'svelte/elements'; - import type { NeoButtonProps } from '~/buttons/neo-button.model.js'; import type { NeoColorPickerProps } from '~/inputs/neo-color-picker.model.js'; import NeoButton from '~/buttons/NeoButton.svelte'; + import { type NeoButtonProps } from '~/buttons/neo-button.model.js'; import IconPaint from '~/icons/IconPaint.svelte'; import NeoColorPickerSelector from '~/inputs/NeoColorPickerSelector.svelte'; import NeoInput from '~/inputs/common/NeoInput.svelte'; import { HexColorRegexString } from '~/utils/regex.utils.js'; - import { coerce, computeButtonShadows, getDefaultElevation } from '~/utils/shadow.utils.js'; + import { coerce, computeButtonShadows, computeButtonStyle, getDefaultElevation } from '~/utils/shadow.utils.js'; /* eslint-disable prefer-const -- necessary for binding checked */ let { @@ -61,12 +61,14 @@ rounded: rest.rounded, glass: rest.glass, start: rest.start, - text, style, + ...template, ...buttonProps, onclick, }); + $inspect(afterProps); + const oninput: FormEventHandler = e => { ref?.dispatchEvent(new InputEvent(e.type, e)); pickerProps?.oninput?.(e); diff --git a/src/lib/inputs/NeoDateTime.svelte b/src/lib/inputs/NeoDateTime.svelte index 93a1763..f537423 100644 --- a/src/lib/inputs/NeoDateTime.svelte +++ b/src/lib/inputs/NeoDateTime.svelte @@ -1,12 +1,12 @@ {#snippet after()} diff --git a/src/lib/inputs/NeoTextarea.svelte b/src/lib/inputs/NeoTextarea.svelte index 70ee3d5..0fb87c3 100644 --- a/src/lib/inputs/NeoTextarea.svelte +++ b/src/lib/inputs/NeoTextarea.svelte @@ -458,7 +458,8 @@ class:neo-validation={showInputValidation} class:neo-disabled={disabled} class:neo-raised={elevation > 3 || elevation + hover > 3} - class:neo-inset={elevation < 0 || elevation + hover < 0} + class:neo-inset={elevation < 0} + class:neo-inset-hover={elevation + hover < 0} class:neo-deep={elevation < -3 || elevation + hover < -3} class:neo-flat={!elevation} class:neo-hover-flat={hoverFlat} @@ -589,7 +590,7 @@ cursor: pointer; &:focus-visible { - color: var(--neo-textarea-focus-color, var(--neo-text-color-focused)); + outline: var(--neo-border-width, 1px) solid var(--neo-border-color-focused); } &:hover { @@ -615,7 +616,6 @@ :global(.neo-button) { --neo-btn-padding-empty: 0.4375rem; --neo-btn-margin: auto; - --neo-btn-box-shadow-active-flat-toggle: var(--neo-box-shadow-inset-2); --neo-btn-bg-color: transparent; --neo-btn-backdrop-filter: none; } @@ -648,6 +648,10 @@ box-shadow: var(--neo-textarea-box-shadow, var(--neo-box-shadow-flat)); cursor: text; + &:focus-visible { + outline: var(--neo-border-width, 1px) solid var(--neo-border-color-focused); + } + &.neo-readonly { cursor: initial; } @@ -866,11 +870,12 @@ background-color: var(--neo-textarea-bg-color, var(--neo-glass-background-color)); backdrop-filter: var(--neo-textarea-glass-blur, var(--neo-blur-3) var(--neo-saturate-2)); + &.neo-inset-hover:hover, &.neo-inset { border-color: var(--neo-textarea-border-color, transparent); } - &:not(.neo-inset, .neo-borderless, .neo-hover-flat:hover, .neo-hover-flat.neo-hovered, .neo-hover-flat:focus-within) { + &:not(.neo-inset, .neo-inset-hover:hover, .neo-borderless, .neo-hover-flat:hover, .neo-hover-flat.neo-hovered, .neo-hover-flat:focus-within) { border-color: var( --neo-textarea-border-color, var(--neo-glass-top-border-color) var(--neo-glass-right-border-color) var(--neo-glass-bottom-border-color) diff --git a/src/lib/inputs/common/NeoAffix.svelte b/src/lib/inputs/common/NeoAffix.svelte index 2aaa884..4f822a4 100644 --- a/src/lib/inputs/common/NeoAffix.svelte +++ b/src/lib/inputs/common/NeoAffix.svelte @@ -156,8 +156,7 @@ background-color 0.3s ease; &:focus-visible { - color: var(--neo-close-color-focused, rgb(255 0 0 / 50%)); - background-color: var(--neo-close-bg-color-focused, rgb(255 0 0 / 5%)); + outline: var(--neo-border-width, 1px) solid var(--neo-border-color-focused); } &:hover { diff --git a/src/lib/inputs/common/NeoBaseInput.svelte b/src/lib/inputs/common/NeoBaseInput.svelte index 286a584..661c30d 100644 --- a/src/lib/inputs/common/NeoBaseInput.svelte +++ b/src/lib/inputs/common/NeoBaseInput.svelte @@ -520,6 +520,10 @@ border-bottom-right-radius: 0; } + &[hidden] { + pointer-events: none; + } + &[type='password']:not(:placeholder-shown) { letter-spacing: 0.2em; -webkit-text-stroke-width: 0.15em; diff --git a/src/lib/inputs/common/NeoInput.svelte b/src/lib/inputs/common/NeoInput.svelte index 4241791..7238c16 100644 --- a/src/lib/inputs/common/NeoInput.svelte +++ b/src/lib/inputs/common/NeoInput.svelte @@ -365,7 +365,8 @@ class:neo-validation={showInputValidation} class:neo-disabled={disabled} class:neo-raised={elevation > 3 || elevation + hover > 3} - class:neo-inset={elevation < 0 || elevation + hover < 0} + class:neo-inset={elevation < 0} + class:neo-inset-hover={elevation + hover < 0} class:neo-deep={elevation < -3 || elevation + hover < -3} class:neo-flat={!elevation} class:neo-hover-flat={hoverFlat} @@ -498,7 +499,7 @@ cursor: pointer; &:focus-visible { - color: var(--neo-input-focus-color, var(--neo-text-color-focused)); + outline: var(--neo-border-width, 1px) solid var(--neo-border-color-focused); } &:hover { @@ -525,7 +526,6 @@ --neo-btn-padding: 0.5rem 0.75rem; --neo-btn-margin: auto; --neo-btn-min-width: 2.375rem; - --neo-btn-box-shadow-active-flat-toggle: var(--neo-box-shadow-inset-2); --neo-btn-bg-color: transparent; --neo-btn-backdrop-filter: none; } @@ -555,6 +555,10 @@ box-shadow: var(--neo-input-box-shadow, var(--neo-box-shadow-flat)); cursor: var(--neo-input-cursor, text); + &:focus-visible { + outline: var(--neo-border-width, 1px) solid var(--neo-border-color-focused); + } + &.neo-readonly { cursor: inherit; } @@ -836,11 +840,7 @@ background-color: var(--neo-input-bg-color, var(--neo-glass-background-color)); backdrop-filter: var(--neo-input-glass-blur, var(--neo-blur-3) var(--neo-saturate-2)); - &.neo-inset { - border-color: var(--neo-input-border-color, transparent); - } - - &:not(.neo-inset, .neo-borderless, .neo-hover-flat:hover, .neo-hover-flat.neo-hovered, .neo-hover-flat:focus-within) { + &:not(.neo-inset, .neo-inset-hover:hover, .neo-borderless, .neo-hover-flat:hover, .neo-hover-flat.neo-hovered, .neo-hover-flat:focus-within) { border-color: var( --neo-input-border-color, var(--neo-glass-top-border-color) var(--neo-glass-right-border-color) var(--neo-glass-bottom-border-color) diff --git a/src/lib/inputs/common/neo-input.model.ts b/src/lib/inputs/common/neo-input.model.ts index 136b42c..cfb4149 100644 --- a/src/lib/inputs/common/neo-input.model.ts +++ b/src/lib/inputs/common/neo-input.model.ts @@ -1,8 +1,3 @@ -// TODO: movie this to dedicate input like Password, FilePicker, DatePicker & Number -// &::-webkit-calendar-picker-indicator { -// display: none; -// } - import type { Snippet } from 'svelte'; import type { HTMLInputAttributes, HTMLSelectAttributes, HTMLTextareaAttributes } from 'svelte/elements'; import type { NeoAffixProps } from '~/inputs/common/neo-affix.model.js'; diff --git a/src/lib/list/NeoListBaseItem.svelte b/src/lib/list/NeoListBaseItem.svelte index f9fbe13..22c4917 100644 --- a/src/lib/list/NeoListBaseItem.svelte +++ b/src/lib/list/NeoListBaseItem.svelte @@ -111,7 +111,9 @@ data-select={checked} aria-labelledby={labelId} ghost - shallow + elevation="0" + hover="-1" + active="-2" {readonly} {disabled} href={item?.href} diff --git a/src/lib/list/NeoListSearch.svelte b/src/lib/list/NeoListSearch.svelte index 728c15b..a991427 100644 --- a/src/lib/list/NeoListSearch.svelte +++ b/src/lib/list/NeoListSearch.svelte @@ -95,7 +95,7 @@ {/snippet} {#snippet after()} - + {#snippet icon()} {#if invert === undefined} diff --git a/src/lib/nav/NeoTab.svelte b/src/lib/nav/NeoTab.svelte index 94c712e..28e6d8f 100644 --- a/src/lib/nav/NeoTab.svelte +++ b/src/lib/nav/NeoTab.svelte @@ -187,6 +187,7 @@ &:focus-visible :global(> .neo-icon-close) { color: var(--neo-close-color-focused, rgb(255 0 0 / 75%)); background-color: var(--neo-close-bg-color-focused, rgb(255 0 0 / 5%)); + outline: var(--neo-border-width, 1px) solid var(--neo-border-color-focused); opacity: 1; transition: none; } diff --git a/src/lib/pill/NeoPill.svelte b/src/lib/pill/NeoPill.svelte index 5e377f9..fb884f0 100644 --- a/src/lib/pill/NeoPill.svelte +++ b/src/lib/pill/NeoPill.svelte @@ -91,8 +91,6 @@ const useFn = $derived(toAction(use)); const useProps = $derived(toActionProps(use)); - - // TODO - sizing: small, medium, large (font, line-height, padding) { - if (text) return; +export const computeButtonShadows = (elevation: number | ShadowElevation, template?: NeoButtonTemplate) => { + if (!template?.elevation) return; return ` --neo-btn-box-shadow: var(--neo-box-shadow-raised-${Math.min(Math.abs(elevation), 3)}); --neo-btn-box-shadow-hover: var(--neo-box-shadow-raised-${clamp(Math.abs(elevation) - 1, 1, 2)}); @@ -103,3 +106,8 @@ export const computeButtonShadows = (elevation: number | ShadowElevation, text?: --neo-btn-box-shadow-focus-active: var(--neo-box-shadow-pressed-${clamp(Math.abs(elevation) - 1, 1, 2)}); `; }; + +export const computeButtonStyle = (elevation: number | ShadowElevation, pressed?: boolean, text?: boolean): NeoButtonTemplate => { + if (text || !pressed || (pressed && elevation >= 0)) return NeoTextButton; + return { elevation: Math.max(1, Math.abs(elevation)) as ShadowElevation, hover: 0, active: -2, borderless: true }; +};