lerobot/robot-learning-tutorial
508
1---2const { title, open = false, class: className, ...props } = Astro.props;3const wrapperClass = ["accordion", className].filter(Boolean).join(" ");4---5<details class={wrapperClass} open={open} {...props}>6 <summary class="accordion__summary">7 <span class="accordion__title">{title}</span>8 <svg class="accordion__chevron" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">9 <path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />10 </svg>11 </summary>12 <div class="accordion__content-wrapper">13 <div class="accordion__content">14 <slot />15 </div>16 </div>17</details>18 19<script>20 // Animate open/close by transitioning explicit height21 document.addEventListener('DOMContentLoaded', () => {22 const accordions = document.querySelectorAll('.accordion') as NodeListOf<HTMLDetailsElement>;23 accordions.forEach((acc) => {24 const summary = acc.querySelector('summary.accordion__summary') as HTMLElement | null;25 const wrapper = acc.querySelector('.accordion__content-wrapper') as HTMLElement | null;26 const content = acc.querySelector('.accordion__content') as HTMLElement | null;27 if (!summary || !wrapper || !content) return;28 29 const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;30 const duration = reduceMotion ? 0 : 240;31 32 // Initial state33 wrapper.style.overflow = 'hidden';34 wrapper.style.height = acc.open ? 'auto' : '0px';35 36 const open = () => {37 wrapper.style.height = '0px';38 void wrapper.offsetHeight; // reflow39 const target = content.scrollHeight;40 wrapper.style.transition = `height ${duration}ms ease`;41 wrapper.style.height = `${target}px`;42 const onEnd = () => {43 wrapper.style.transition = '';44 wrapper.style.height = 'auto';45 wrapper.removeEventListener('transitionend', onEnd);46 };47 wrapper.addEventListener('transitionend', onEnd);48 };49 50 const close = () => {51 const start = wrapper.offsetHeight || content.scrollHeight;52 wrapper.style.height = `${start}px`;53 void wrapper.offsetHeight; // reflow54 wrapper.style.transition = `height ${duration}ms ease`;55 wrapper.style.height = '0px';56 const onEnd = () => {57 wrapper.style.transition = '';58 wrapper.removeEventListener('transitionend', onEnd);59 acc.removeAttribute('open');60 };61 wrapper.addEventListener('transitionend', onEnd);62 };63 64 summary.addEventListener('click', (e) => {65 e.preventDefault();66 if (acc.open) {67 close();68 } else {69 acc.setAttribute('open', '');70 open();71 }72 });73 });74 });75 </script>76 77<style>78 .accordion {79 margin: 0 0 var(--spacing-4);80 padding: 0;81 border: 1px solid var(--border-color);82 border-radius: var(--table-border-radius);83 background: var(--surface-bg);84 transition: box-shadow 180ms ease, border-color 180ms ease;85 }86 87 .accordion[open] {88 border-color: color-mix(in oklab, var(--border-color), var(--primary-color) 20%);89 }90 91 .accordion[open] .accordion__summary {92 border-bottom: 1px solid var(--border-color);93 }94 95 .accordion__summary {96 margin: 0;97 list-style: none;98 display: flex;99 align-items: center;100 justify-content: space-between;101 gap: 4px;102 padding: var(--spacing-2) var(--spacing-3);103 cursor: pointer;104 color: var(--text-color);105 user-select: none;106 }107 108 /* Remove conditional padding to avoid jump on close */109 110 /* Remove native marker */111 .accordion__summary::-webkit-details-marker {112 display: none;113 }114 .accordion__summary::marker {115 content: "";116 }117 118 .accordion[size="big"] .accordion__summary {119 padding: 16px;120 }121 122 .accordion__title {123 font-weight: 600;124 }125 126 .accordion__chevron {127 flex: 0 0 auto;128 transition: transform 220ms ease;129 opacity: .85;130 }131 132 .accordion[open] .accordion__chevron {133 transform: rotate(180deg);134 }135 136 /* Animated expand/collapse using height (controlled in JS) */137 .accordion__content-wrapper {138 overflow: hidden;139 height: 0px;140 will-change: height;141 position: relative;142 }143 144 /* Subtle top shadow overlay when open (visible above children) */145 .accordion[open] .accordion__content-wrapper::before {146 content: "";147 position: absolute;148 left: 0;149 right: 0;150 top: 0;151 height: 6px;152 background: linear-gradient(to bottom, rgba(0,0,0,0.025), rgba(0,0,0,0));153 pointer-events: none;154 z-index: 1;155 }156 157 .accordion__content {158 margin: 0;159 padding: 0;160 }161 162 /* Ensure the very last slotted element has no bottom margin */163 .accordion .accordion__content > :global(*:last-child) {164 margin-bottom: 0 !important;165 }166 /* Remove bottom padding on last child, except for code-output blocks */167 .accordion .accordion__content > :global(:not(.code-output):last-child) {168 padding-bottom: 0 !important;169 }170 171 /* Ensure the very first slotted element has no top spacing */172 .accordion .accordion__content > :global(*:first-child) {173 margin-top: 0 !important;174 }175 176 /* Content padding: default for direct children, opt-out for code/tables */177 .accordion .accordion__content > :global(*) {178 padding: 8px;179 }180 .accordion .accordion__content > :global(.table-scroll),181 .accordion .accordion__content > :global(pre),182 .accordion .accordion__content > :global(.code-card) {183 padding: 0;184 }185 186 /* Focus styles for accessibility */187 .accordion__summary:focus-visible {188 outline: 2px solid var(--primary-color);189 outline-offset: 3px;190 border-radius: var(--table-border-radius);191 }192 193 194</style>195 196 197 