Applies to both user-app and user-app-v2. Logic and structure are shared; each app keeps its own look. Appearance settings - New "تنظیمات ظاهری" page (/settings/appearance), opened from the dashboard and available without login. It offers a light/dark/system theme and a "fast & light" motion switch. - appearanceStore saves the choice to localStorage. A small inline boot script in index.html applies it before first paint so there is no white flash. The "system" option follows OS changes live. - New ToggleSwitch component. Dark mode - Theme LESS tokens now compile to CSS custom properties. Light/dark values come from a generated virtual:palette.css (themeConfig/ appearance.ts, with colors.json + new colors.dark.json). - New LESS alpha() plugin replaces fade() on theme tokens. - New semantic tokens: page, onInk, heroSurface, hairlineStrong, photoMat, photoStage. - The dark palette is layered (page < card < panel < border) so surfaces separate without shadows. - Hard-coded colors were replaced by tokens according to their role. - antd inputs/text get dark overrides; vant uses its own .van-theme-dark. - Light mode looks the same as before. Reduced motion - With html.motion-reduced, transitions are capped at 120ms, keyframe loops stop, and backdrop blurs and permanent will-change are dropped. - Spinners and the toast progress bar (which closes the toast) are exempt via .keep-motion. - Carousel autoplay, the v1 Lottie and the product morph honor the setting. Product detail - Opening a product is now a container-transform morph from the tapped card (ProductMorph). It replaces the bottom Drawer and uses composited transform/opacity only. - The detail view is seeded with the card's data, so it no longer opens blank and jumps. Filters and sheets - Filters are now a full-width bottom sheet. Selections stay a draft until applied, so the result list no longer re-renders while dragging the slider. - DropDown collapse animates grid-template-rows instead of a capped max-height. - v2 IslandSheet is restyled to the v2 design language (API unchanged). Fixes - filtersStore.reset() shallow-copied shared arrays, so clearing the filters never cleared the selected brands.
162 lines
4.7 KiB
Vue
162 lines
4.7 KiB
Vue
<!--
|
|
Ported from legacy henza_pwa/src/components/dropDown.vue, restyled into the
|
|
redesigned design language (see components/home/* and HomeView) — previously
|
|
raw Tailwind utility classes with legacy tokens (text-primary,
|
|
border-lightGray/20), the one accordion in the app that didn't match the
|
|
LESS-token look every other surface uses.
|
|
|
|
FIXED — the collapse itself. It used to v-if the body in and out and animate
|
|
`max-height` from 0 to a hard-coded 240px, which broke three ways: content
|
|
taller than 240px was cut off for the whole animation and then jumped to full
|
|
height at the end; content shorter than 240px opened fast and then closed
|
|
with a dead pause (the first stretch of the shrink happened above the real
|
|
height, so nothing moved); and the close used `animation: … reverse` on a
|
|
`leave-to` class, which Vue does not time leave transitions by, so it
|
|
usually just snapped shut. The body's own padding also popped in and out.
|
|
|
|
Now the body stays mounted and the panel animates `grid-template-rows` from
|
|
0fr to 1fr — the browser interpolates to the content's real height, whatever
|
|
it is, in both directions, with no measuring and no cap. All the padding
|
|
below the header lives inside the clipped row, so it is part of what
|
|
animates. Browsers too old to interpolate grid rows (pre-2022) simply open
|
|
and close without the slide.
|
|
|
|
STRUCTURE IS SHARED, LOOK IS NOT: the template, script and the collapse
|
|
mechanics are identical in user-app and user-app-v2; only the "Look" block
|
|
at the bottom differs, each in its own app's design language.
|
|
-->
|
|
<template>
|
|
<div class="dd" :class="{ 'is-open': show }">
|
|
<button type="button" class="dd__head" :aria-expanded="show" @click="show = !show">
|
|
<h3 class="dd__title">
|
|
<slot name="text">{{ text }}</slot>
|
|
</h3>
|
|
<span class="dd__chevron" aria-hidden="true">
|
|
<Icon icon="vuesax-linear:arrow-down" size="16" />
|
|
</span>
|
|
</button>
|
|
<div class="dd__panel" :inert="!show">
|
|
<div class="dd__clip">
|
|
<div class="dd__body">
|
|
<slot />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import Icon from '../icon/Icon.vue'
|
|
|
|
const props = withDefaults(defineProps<{ text?: string; defaultOpen?: boolean }>(), { defaultOpen: false })
|
|
const show = ref(props.defaultOpen)
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
/* ── Mechanics (same in both apps) ──────────────────────────── */
|
|
// One curve for the whole collapse: the iOS sheet curve the app's sheets use —
|
|
// quick off the mark, soft landing.
|
|
@dd-ease: cubic-bezier(0.32, 0.72, 0, 1);
|
|
@dd-time: 0.34s;
|
|
|
|
.dd__head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
width: 100%;
|
|
border: 0;
|
|
cursor: pointer;
|
|
background: none;
|
|
font-family: inherit;
|
|
text-align: start;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
|
|
.dd__title {
|
|
margin: 0;
|
|
}
|
|
|
|
.dd__chevron {
|
|
flex: 0 0 auto;
|
|
display: grid;
|
|
place-items: center;
|
|
transition: transform @dd-time @dd-ease, color 0.2s ease, background-color 0.2s ease;
|
|
}
|
|
.dd.is-open .dd__chevron {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.dd__panel {
|
|
display: grid;
|
|
grid-template-rows: 0fr;
|
|
transition: grid-template-rows @dd-time @dd-ease;
|
|
}
|
|
.dd.is-open .dd__panel {
|
|
grid-template-rows: 1fr;
|
|
}
|
|
|
|
// The row's content box: `min-height: 0` is what lets the 0fr row actually
|
|
// shrink below the content's height, and the clip hides what is outside it.
|
|
.dd__clip {
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
// Fades and settles in a touch behind the height, and out a touch ahead of it,
|
|
// so the content never shows half-cut at the moving edge.
|
|
.dd__body {
|
|
opacity: 0;
|
|
transform: translateY(-6px);
|
|
transition: opacity 0.18s ease, transform @dd-time @dd-ease;
|
|
}
|
|
.dd.is-open .dd__body {
|
|
opacity: 1;
|
|
transform: none;
|
|
transition: opacity 0.24s ease 0.06s, transform @dd-time @dd-ease;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.dd__panel,
|
|
.dd__body,
|
|
.dd__chevron {
|
|
transition: none !important;
|
|
}
|
|
}
|
|
|
|
/* ── Look: user-app ─────────────────────────────────────────── */
|
|
// A white card with a hairline border and the 16px radius the detail page's
|
|
// spec table uses; the border and chevron take the brand green when open.
|
|
.dd {
|
|
border: 1px solid @hairline;
|
|
border-radius: 16px;
|
|
background: @canvas;
|
|
transition: border-color 0.2s ease;
|
|
}
|
|
.dd.is-open {
|
|
border-color: alpha(@primary, 28%);
|
|
}
|
|
|
|
.dd__head {
|
|
padding: 15px;
|
|
}
|
|
|
|
.dd__title {
|
|
font-size: 13.5px;
|
|
font-weight: 800;
|
|
color: @ink;
|
|
}
|
|
|
|
.dd__chevron {
|
|
color: @inkFaint;
|
|
}
|
|
.dd.is-open .dd__chevron {
|
|
color: @primary;
|
|
}
|
|
|
|
.dd__body {
|
|
padding: 0 15px 15px;
|
|
}
|
|
</style>
|