كل المكوّنات
MotionSheet لوحة سفلية أو علوية أو درج جانبي مع نقاط توقف ونقرات سريعة وتسليم التمرير.
الويب React Native الطبقات العلوية
جرّبه JavaScript معطّل. الشيفرة أدناه هي المثال الكامل.
App.jsx import { useState } from 'react' ;
import { MotionHighlight , MotionSheet } from '@kkwaenggwari/animated-chorus' ;
import './styles.css' ;
const sorts = ['Newest' , 'Popular' , 'Price' ];
const sizes = ['XS' , 'S' , 'M' , 'L' , 'XL' ];
const colors = ['Any' , 'Green' , 'Sand' , 'Ink' ];
export default function App ({ side = 'bottom' , detents = 'two' , modal = true } ) {
const [open, setOpen] = useState (false );
const [sort, setSort] = useState ('Popular' );
const [size, setSize] = useState ('M' );
const [color, setColor] = useState ('Any' );
return (
<div className ="page" >
<header >
<strong > Shop</strong >
<button className ="open" onClick ={() => setOpen(true)}>
Filters · {sort} · {size}
</button >
</header >
<div className ="grid" >
{Array.from({ length: 6 }, (_, index) => (
<span key ={index} className ="product" />
))}
</div >
<MotionSheet
open ={open}
onOpenChange ={setOpen}
side ={side}
modal ={modal}
detents ={detents === 'two' ? [0.55 , 0.92 ] : ['content ']}
className ={ `sheet ${side }`}
>
<span className ="handle" />
{/* The action sits beside the title, so it is in reach at every detent. */}
<div className ="sheet-head" >
<h2 > Filters</h2 >
<button className ="apply" onClick ={() => setOpen(false)}>
Show results
</button >
</div >
<p className ="label" > Sort by</p >
<MotionHighlight
items ={sorts}
value ={sort}
onValueChange ={setSort}
className ="choices"
itemClassName ="choice"
highlightClassName ="choice-on"
/>
<p className ="label" > Size</p >
<MotionHighlight
items ={sizes}
value ={size}
onValueChange ={setSize}
className ="choices"
itemClassName ="choice"
highlightClassName ="choice-on"
/>
<p className ="label" > Color</p >
<MotionHighlight
items ={colors}
value ={color}
onValueChange ={setColor}
className ="choices"
itemClassName ="choice"
highlightClassName ="choice-on"
/>
</MotionSheet >
</div >
);
}styles.css .page {
display : grid;
gap : 16px ;
min-height : 440px ;
padding : 20px ;
align-content : start;
}
header {
display : flex;
flex-wrap : wrap;
align-items : center;
justify-content : space-between;
gap : 10px ;
}
header strong {
font-size : 20px ;
color : #0e2e24 ;
}
.open {
min-height : 44px ;
padding : 0 18px ;
border : 0 ;
border-radius : 999px ;
background : #176b52 ;
color : #fff ;
font :
650 15px system-ui,
sans-serif;
cursor : pointer;
}
.grid {
display : grid;
grid-template-columns : repeat (3 , 1 fr);
gap : 10px ;
}
.product {
aspect-ratio : 1 ;
border-radius : 14px ;
background : #eaf6f0 ;
}
.product :nth-child (3 n + 2 ) {
background : #cfe8dc ;
}
.sheet {
gap : 10px ;
padding : 12px 22px 22px ;
background : #fff ;
box-shadow : 0 -8px 30px rgba (14 , 46 , 36 , 0.18 );
}
.sheet .bottom {
border-radius : 22px 22px 0 0 ;
}
.sheet .top {
border-radius : 0 0 22px 22px ;
}
.sheet .top .handle {
order : 1 ;
margin-top : 6px ;
}
.sheet .end {
width : min (340px , 86vw );
border-radius : 22px 0 0 22px ;
}
.handle {
justify-self : center;
align-self : center;
width : 44px ;
height : 5px ;
border-radius : 3px ;
background : #cfe8dc ;
}
.sheet-head {
display : flex;
flex-wrap : wrap;
align-items : center;
justify-content : space-between;
gap : 8px 12px ;
}
.sheet h2 {
margin : 4px 0 0 ;
font :
750 22px system-ui,
sans-serif;
color : #0e2e24 ;
}
.label {
margin : 8px 0 0 ;
color : #5b616b ;
font-size : 14px ;
}
.choices {
display : flex;
flex-wrap : wrap;
gap : 4px ;
padding : 4px ;
border-radius : 14px ;
background : #eaf6f0 ;
}
.choice {
min-width : 52px ;
min-height : 40px ;
padding : 0 14px ;
border : 0 ;
background : none;
font :
600 14px system-ui,
sans-serif;
color : #0e2e24 ;
cursor : pointer;
}
.choice-on {
border-radius : 10px ;
background : #fff ;
box-shadow : 0 0 0 1px #cfe8dc ;
}
.apply {
min-height : 44px ;
padding : 0 18px ;
border : 0 ;
border-radius : 999px ;
background : #176b52 ;
color : #fff ;
font :
650 15px system-ui,
sans-serif;
cursor : pointer;
}أقصر استخدام Filters.jsx نسخ الشيفرة <MotionSheet open={open} onOpenChange={setOpen} detents={[0.45 , 0.92 ]}>
<FilterList />
</MotionSheet >;ما يتولّاه عنك تتبع السحب، وتقاوم بعد أكبر نقطة توقف، وتستقر وفق سرعة إفلاتك؛ والنقرة السريعة تنقل نقطة توقف واحدة أو تغلقها. يُسلَّم السحب باللمس إلى المحتوى القابل للتمرير: السحب إلى الأسفل يعيد المحتوى إلى أعلاه قبل أن تتحرك اللوحة. نقاط توقف ككسور من الشاشة، أو بالبكسل، أو 'content' للحجم الطبيعي للمحتوى. اللوحة الشرطية تُبقي التركيز داخلها، وتجعل الصفحة خاملة، وتُغلق بـ Escape أو بالخلفية. استخدمه لـ لوحة مرشّحات. قائمة مشاركة. درج تنقّل مع side="start". Props Prop النوع الافتراضي الوصف openbooleanfalseهل اللوحة ظاهرة. onOpenChange(open) => voidيعمل مع false حين تُغلق اللوحة بالسحب أو النقر أو Escape. detents(number | 'content')[]['content']أحجام السكون: كسر من الشاشة، أو بالبكسل، أو 'content'. detentnumberفهرس نقطة التوقف المتحكَّم به. defaultDetentnumber0فهرس نقطة التوقف المستخدم عند الفتح من دون detent. onDetentChange(index) => voidيعمل مع فهرس نقطة التوقف التي تستقر عليها اللوحة. side'bottom' | 'top' | 'start' | 'end''bottom'الحافة التي تأتي منها اللوحة. modalbooleantrueيضيف خلفية، ويُبقي التركيز داخلها، ويجعل الصفحة خاملة. dismissiblebooleantrueالسحب بعد أصغر نقطة توقف والخلفية وEscape تغلقها. backdropClassNamestringصنف الخلفية؛ ويضبط backdropStyle نمطها المضمّن. transitionTransitionspringمنحنى توقيت { duration, easing } أو نابض { tempo: 'spring', spring }. تحتفظ النوابض بزخمها عند المقاطعة.
تذهب props الأخرى إلى العنصر المعروض، فتعمل className وstyle ومعالجات الأحداث كالمعتاد.