الإعدادات

يوضح هذا النمط كيفية إنشاء مكون إعدادات سريع الاستجابة ويدعم إدخالات متعددة للأجهزة ويعمل على المتصفحات.

المقالة الكاملة · الفيديو على YouTube · المصدر على جيت هب

HTML

<main>   <h1>Settings</h1>      <form>      <section>       <header>         <h2>Sound & vibration</h2>         <small>Adjust system volume channels</small>       </header>              <fieldset>                  <div class="fieldset-item">           <picture aria-hidden="true">             <svg viewBox="0 0 24 24">               <title>A note icon</title>               <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>             </svg>           </picture>           <div class="input-stack">             <label                for="media-volume"                id="media-volume"                aria-hidden="true">                 Media volume             </label>             <input                name="media-volume"                aria-labelledby="media-volume"                type="range"                value="3"                max="10"                style="--track-fill: 30%"             >           </div>         </div>          <div class="fieldset-item">           <picture aria-hidden="true">             <title>A phone icon</title>             <svg viewBox="0 0 24 24">               <path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z"/>             </svg>           </picture>           <div class="input-stack">             <label for="call-volume" id="call-volume" aria-hidden="true">Call volume</label>             <input                name="call-volume"                aria-labelledby="call-volume"                type="range"                value="7"                max="10"                style="--track-fill: 70%"             >           </div>         </div>          <div class="fieldset-item">           <picture aria-hidden="true">             <svg viewBox="0 0 24 24">               <title>A bell icon</title>               <path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/>             </svg>           </picture>           <div class="input-stack">             <label for="ring-volume" id="ring-volume" aria-hidden="true">Ring volume</label>             <input                name="ring-volume"                aria-labelledby="ring-volume"                type="range"                value="5"                max="10"                style="--track-fill: 50%"             >           </div>         </div>          <div class="fieldset-item">           <picture aria-hidden="true">             <svg viewBox="0 0 24 24">               <title>An alarm clock icon</title>               <path d="M22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM12.5 8H11v6l4.75 2.85.75-1.23-4-2.37V8zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"/>             </svg>           </picture>           <div class="input-stack">             <label for="alarm-volume" id="alarm-volume" aria-hidden="true">Alarm volume</label>             <input                name="alarm-volume"                aria-labelledby="alarm-volume"                type="range"                value="8"                max="10"                style="--track-fill: 80%"             >           </div>         </div>                </fieldset>     </section>      <section>       <header>         <h2>Notifications</h2>         <small>Turn specific channels on/off</small>       </header>              <fieldset>          <div class="fieldset-item">           <input              type="checkbox"             checked             id="text-notifications"             name="text-notifications"           >           <div class="input-stack">             <label for="text-notifications">               <h3>Text Messages</h3>               <small>Get notified about all text messages sent to your device</small>             </label>           </div>         </div>          <div class="fieldset-item">           <input              type="checkbox"             id="voice-notifications"             name="voice-notifications"           >           <div class="input-stack">             <label for="voice-notifications">               <h3>Voice Mail</h3>               <small>Get notified about all voice messages sent to your device</small>             </label>           </div>         </div>          <div class="fieldset-item">           <input              type="checkbox"             id="email-notifications"             name="email-notifications"           >           <div class="input-stack">             <label for="email-notifications">               <h3>Emails</h3>               <small>Get notified about all text messages to your device</small>             </label>           </div>         </div>        </fieldset>     </section>    </form> </main>

CSS

         @custom-media --motionOK (prefers-reduced-motion: no-preference);  :root {   --surface1: lch(10 0 0);   --surface2: lch(15 0 0);   --surface3: lch(20 0 0);   --surface4: lch(25 0 0);    --text1: lch(95 0 0);   --text2: lch(75 0 0);    --brand: lch(64 20 237);   --brand-bg1: lch(70 64 349);   --brand-bg2: lch(60 84 300);    --brand-bg-gradient: linear-gradient(     to bottom,      var(--brand-bg1),      var(--brand-bg2)   );    --thumb-highlight-color: lch(100 0 0 / 20%);    --space-xxs: .25rem;   --space-xs:  .5rem;   --space-sm:  1rem;   --space-md:  1.5rem;   --space-lg:  2rem;   --space-xl:  3rem;   --space-xxl: 6rem;      --isLTR: 1;   --isRTL: -1;      &:dir(rtl) {     --isLTR: -1;     --isRTL: 1;   }    @media (prefers-color-scheme: light) {     & {       --surface1: lch(90 0 0);       --surface2: lch(100 0 0);       --surface3: lch(98 0 0);       --surface4: lch(85 0 0);        --text1: lch(20 0 0);       --text2: lch(40 0 0);        --brand: lch(64 40 237);       --brand-bg1: lch(50 64 349);       --brand-bg2: lch(40 84 300);        --thumb-highlight-color: lch(0 0 0 / 20%);     }   } }  html {   block-size: 100%;   inline-size: 100%; }  body {   min-block-size: 100%;   min-inline-size: 100%;    box-sizing: border-box;   margin: 0;   padding-block: var(--space-xs);    background: var(--surface1);   color: var(--text1);   font-family: system-ui, sans-serif; }  h1,h2,h3 {    margin: 0;    font-weight: 500; }  main {   display: grid;   gap: var(--space-xl);   place-content: center;   padding: var(--space-sm);    @media (width >= 540px) {     padding: var(--space-lg);   }    @media (width >= 800px) {     padding: var(--space-xl);   } }  form {   max-width: 89vw;   display: grid;   gap: var(--space-xl) var(--space-xxl);   --repeat: auto-fit;   grid-template-columns:      repeat(var(--repeat), minmax(min(10ch, 100%), 35ch));   align-items: flex-start;    @media (orientation: landscape) and (width >= 640px) {     --repeat: 2;   } }  section {   display: grid;   gap: var(--space-md); }  header {   display: grid;   gap: var(--space-xxs); }  fieldset {   border: 1px solid var(--surface4);   background: var(--surface4);   padding: 0;   margin: 0;   display: grid;   gap: 1px;   border-radius: var(--space-sm);   overflow: hidden;   transition: box-shadow .3s ease;    &:focus-within {     box-shadow: 0 5px 20px -10px hsl(0 0% 0% / 50%);   } }  input[type="range"] {   --track-height: .5ex;   --track-fill: 0%;   --thumb-size: 3ex;   --thumb-offset: -1.25ex;   --thumb-highlight-size: 0px;    display: block;   inline-size: 100%;   margin: 1ex 0;   appearance: none;   background: transparent;   outline-offset: 5px;    @media (hover: none) {     --thumb-size: 30px;     --thumb-offset: -14px;   }    &::-webkit-slider-runnable-track {     appearance: none;     block-size: var(--track-height);     border-radius: 5ex;     background:        linear-gradient(         to right,          transparent var(--track-fill),          var(--surface1) 0%       ),       var(--brand-bg-gradient) fixed;   }    &::-moz-range-track {     appearance: none;     block-size: var(--track-height);     border-radius: 5ex;     background:        linear-gradient(         to right,          transparent var(--track-fill),          var(--surface1) 0%       ),       var(--brand-bg-gradient) fixed;   }    &::-webkit-slider-thumb {     appearance: none;     cursor: ew-resize;     border: 3px solid var(--surface3);     block-size: var(--thumb-size);     inline-size: var(--thumb-size);     margin-block-start: var(--thumb-offset);     border-radius: 50%;     background: var(--brand-bg-gradient) fixed;     box-shadow: 0 0 0 var(--thumb-highlight-size) var(--thumb-highlight-color);          @media (--motionOK) {       transition: box-shadow .1s ease;     }      @nest .fieldset-item:focus-within & {       border-color: var(--surface2);     }   }    &::-moz-range-thumb {     appearance: none;     cursor: ew-resize;     border: 3px solid var(--surface3);     block-size: var(--thumb-size);     inline-size: var(--thumb-size);     margin-block-start: var(--thumb-offset);     border-radius: 50%;     background: var(--brand-bg-gradient) fixed;     box-shadow: 0 0 0 var(--thumb-highlight-size) var(--thumb-highlight-color);      @media (--motionOK) {       transition: box-shadow .1s ease;     }      @nest .fieldset-item:focus-within & {       border-color: var(--surface2);     }   }    &:is(:hover,:active) {     --thumb-highlight-size: 10px;   } }  input[type="checkbox"] {   inline-size: var(--space-sm);   block-size: var(--space-sm);   margin: 0;   outline-offset: 5px;   accent-color: var(--brand);   position: relative;   transform-style: preserve-3d;   cursor: pointer;    &:hover::before {     --thumb-scale: 1;   }    @media (hover: none) {     inline-size: var(--space-md);     block-size: var(--space-md);   }    &::before {     --thumb-scale: .01;     --thumb-highlight-size: var(--space-xl);      content: "";     inline-size: var(--thumb-highlight-size);     block-size: var(--thumb-highlight-size);     clip-path: circle(50%);     position: absolute;     inset-block-start: 50%;     inset-inline-start: 50%;     background: var(--thumb-highlight-color);     transform-origin: center center;     transform:        translateX(calc(var(--isRTL) * 50%))        translateY(-50%)        translateZ(-1px)        scale(var(--thumb-scale))     ;     will-change: transform;      @media (--motionOK) {       transition: transform .2s ease;     }   } }  .fieldset-item {   background: var(--surface3);   transition: background .2s ease;    display: grid;   grid-template-columns: var(--space-lg) 1fr;   gap: var(--space-md);    padding-block: var(--space-sm);   padding-inline: var(--space-md);    @media (width >= 540px) {     grid-template-columns: var(--space-xxl) 1fr;     gap: var(--space-xs);     padding-block: var(--space-md);     padding-inline: 0 var(--space-xl);   }    &:focus-within {     background: var(--surface2);      & svg {       fill: white;     }      & picture {       clip-path: circle(50%);       background: var(--brand-bg-gradient) fixed;     }   }    & > :is(.input-stack, label) {     display: grid;     gap: var(--space-xs);   }    & > .input-stack > label {     display: contents;   }    & > picture {     block-size: var(--space-xl);     inline-size: var(--space-xl);     clip-path: circle(40%);     display: inline-grid;     place-content: center;     background: var(--surface3) fixed;      @media (--motionOK) {       transition: clip-path .3s ease;     }   }    & svg {     fill: var(--text2);     block-size: var(--space-md);   }    & > :is(picture, input[type="checkbox"]) {     place-self: center;   } }  small {   color: var(--text2);   line-height: 1.5; }         

JS

         const form = document.querySelector('form') const sliders = document.querySelectorAll('input[type="range"]')  const rangeToPercent = slider => {   const max = slider.getAttribute('max') || 10   const percent = slider.value / max * 100    return `${parseInt(percent)}%` }  sliders.forEach(slider => {   slider.style.setProperty('--track-fill', rangeToPercent(slider))    slider.addEventListener('input', e => {     e.target.style.setProperty('--track-fill', rangeToPercent(e.target))   }) })  form.addEventListener('input', e => {   const formData = Object.fromEntries(new FormData(form))   console.table(formData) })