@zomako/elearning-components 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/elearning-components.es.js +448 -320
- package/dist/elearning-components.umd.js +10 -10
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Accordion/README.md +181 -0
- package/src/components/Accordion/index.tsx +191 -0
- package/src/components/Accordion/style.module.css +112 -0
- package/src/index.ts +1 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(x,j){typeof exports=="object"&&typeof module<"u"?j(exports,require("react"),require("framer-motion")):typeof define=="function"&&define.amd?define(["exports","react","framer-motion"],j):(x=typeof globalThis<"u"?globalThis:x||self,j(x.ElearningComponents={},x.React,x.FramerMotion))})(this,function(x,j,B){"use strict";var H={exports:{}},U={};/**
|
|
2
2
|
* @license React
|
|
3
3
|
* react-jsx-runtime.production.min.js
|
|
4
4
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* This source code is licensed under the MIT license found in the
|
|
8
8
|
* LICENSE file in the root directory of this source tree.
|
|
9
|
-
*/var
|
|
9
|
+
*/var ie;function De(){if(ie)return U;ie=1;var d=j,v=Symbol.for("react.element"),k=Symbol.for("react.fragment"),_=Object.prototype.hasOwnProperty,E=d.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,w={key:!0,ref:!0,__self:!0,__source:!0};function C(T,p,P){var o,u={},f=null,b=null;P!==void 0&&(f=""+P),p.key!==void 0&&(f=""+p.key),p.ref!==void 0&&(b=p.ref);for(o in p)_.call(p,o)&&!w.hasOwnProperty(o)&&(u[o]=p[o]);if(T&&T.defaultProps)for(o in p=T.defaultProps,p)u[o]===void 0&&(u[o]=p[o]);return{$$typeof:v,type:T,key:f,ref:b,props:u,_owner:E.current}}return U.Fragment=k,U.jsx=C,U.jsxs=C,U}var V={};/**
|
|
10
10
|
* @license React
|
|
11
11
|
* react-jsx-runtime.development.js
|
|
12
12
|
*
|
|
@@ -14,17 +14,17 @@
|
|
|
14
14
|
*
|
|
15
15
|
* This source code is licensed under the MIT license found in the
|
|
16
16
|
* LICENSE file in the root directory of this source tree.
|
|
17
|
-
*/var
|
|
18
|
-
`+
|
|
19
|
-
`),
|
|
20
|
-
`),
|
|
21
|
-
`+a[
|
|
17
|
+
*/var oe;function Ae(){return oe||(oe=1,process.env.NODE_ENV!=="production"&&function(){var d=j,v=Symbol.for("react.element"),k=Symbol.for("react.portal"),_=Symbol.for("react.fragment"),E=Symbol.for("react.strict_mode"),w=Symbol.for("react.profiler"),C=Symbol.for("react.provider"),T=Symbol.for("react.context"),p=Symbol.for("react.forward_ref"),P=Symbol.for("react.suspense"),o=Symbol.for("react.suspense_list"),u=Symbol.for("react.memo"),f=Symbol.for("react.lazy"),b=Symbol.for("react.offscreen"),A=Symbol.iterator,W="@@iterator";function K(e){if(e===null||typeof e!="object")return null;var r=A&&e[A]||e[W];return typeof r=="function"?r:null}var D=d.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;function y(e){{for(var r=arguments.length,t=new Array(r>1?r-1:0),n=1;n<r;n++)t[n-1]=arguments[n];We("error",e,t)}}function We(e,r,t){{var n=D.ReactDebugCurrentFrame,s=n.getStackAddendum();s!==""&&(r+="%s",t=t.concat([s]));var c=t.map(function(i){return String(i)});c.unshift("Warning: "+r),Function.prototype.apply.call(console[e],console,c)}}var Ye=!1,Le=!1,Ue=!1,Ve=!1,$e=!1,se;se=Symbol.for("react.module.reference");function Me(e){return!!(typeof e=="string"||typeof e=="function"||e===_||e===w||$e||e===E||e===P||e===o||Ve||e===b||Ye||Le||Ue||typeof e=="object"&&e!==null&&(e.$$typeof===f||e.$$typeof===u||e.$$typeof===C||e.$$typeof===T||e.$$typeof===p||e.$$typeof===se||e.getModuleId!==void 0))}function Be(e,r,t){var n=e.displayName;if(n)return n;var s=r.displayName||r.name||"";return s!==""?t+"("+s+")":t}function ce(e){return e.displayName||"Context"}function N(e){if(e==null)return null;if(typeof e.tag=="number"&&y("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case _:return"Fragment";case k:return"Portal";case w:return"Profiler";case E:return"StrictMode";case P:return"Suspense";case o:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case T:var r=e;return ce(r)+".Consumer";case C:var t=e;return ce(t._context)+".Provider";case p:return Be(e,e.render,"ForwardRef");case u:var n=e.displayName||null;return n!==null?n:N(e.type)||"Memo";case f:{var s=e,c=s._payload,i=s._init;try{return N(i(c))}catch{return null}}}return null}var F=Object.assign,$=0,le,ue,fe,de,ve,pe,he;function ge(){}ge.__reactDisabledLog=!0;function Ke(){{if($===0){le=console.log,ue=console.info,fe=console.warn,de=console.error,ve=console.group,pe=console.groupCollapsed,he=console.groupEnd;var e={configurable:!0,enumerable:!0,value:ge,writable:!0};Object.defineProperties(console,{info:e,log:e,warn:e,error:e,group:e,groupCollapsed:e,groupEnd:e})}$++}}function Je(){{if($--,$===0){var e={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:F({},e,{value:le}),info:F({},e,{value:ue}),warn:F({},e,{value:fe}),error:F({},e,{value:de}),group:F({},e,{value:ve}),groupCollapsed:F({},e,{value:pe}),groupEnd:F({},e,{value:he})})}$<0&&y("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var q=D.ReactCurrentDispatcher,Z;function J(e,r,t){{if(Z===void 0)try{throw Error()}catch(s){var n=s.stack.trim().match(/\n( *(at )?)/);Z=n&&n[1]||""}return`
|
|
18
|
+
`+Z+e}}var Q=!1,G;{var Ge=typeof WeakMap=="function"?WeakMap:Map;G=new Ge}function _e(e,r){if(!e||Q)return"";{var t=G.get(e);if(t!==void 0)return t}var n;Q=!0;var s=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var c;c=q.current,q.current=null,Ke();try{if(r){var i=function(){throw Error()};if(Object.defineProperty(i.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(i,[])}catch(R){n=R}Reflect.construct(e,[],i)}else{try{i.call()}catch(R){n=R}e.call(i.prototype)}}else{try{throw Error()}catch(R){n=R}e()}}catch(R){if(R&&n&&typeof R.stack=="string"){for(var a=R.stack.split(`
|
|
19
|
+
`),m=n.stack.split(`
|
|
20
|
+
`),h=a.length-1,g=m.length-1;h>=1&&g>=0&&a[h]!==m[g];)g--;for(;h>=1&&g>=0;h--,g--)if(a[h]!==m[g]){if(h!==1||g!==1)do if(h--,g--,g<0||a[h]!==m[g]){var S=`
|
|
21
|
+
`+a[h].replace(" at new "," at ");return e.displayName&&S.includes("<anonymous>")&&(S=S.replace("<anonymous>",e.displayName)),typeof e=="function"&&G.set(e,S),S}while(h>=1&&g>=0);break}}}finally{Q=!1,q.current=c,Je(),Error.prepareStackTrace=s}var L=e?e.displayName||e.name:"",I=L?J(L):"";return typeof e=="function"&&G.set(e,I),I}function ze(e,r,t){return _e(e,!1)}function Xe(e){var r=e.prototype;return!!(r&&r.isReactComponent)}function z(e,r,t){if(e==null)return"";if(typeof e=="function")return _e(e,Xe(e));if(typeof e=="string")return J(e);switch(e){case P:return J("Suspense");case o:return J("SuspenseList")}if(typeof e=="object")switch(e.$$typeof){case p:return ze(e.render);case u:return z(e.type,r,t);case f:{var n=e,s=n._payload,c=n._init;try{return z(c(s),r,t)}catch{}}}return""}var M=Object.prototype.hasOwnProperty,be={},ye=D.ReactDebugCurrentFrame;function X(e){if(e){var r=e._owner,t=z(e.type,e._source,r?r.type:null);ye.setExtraStackFrame(t)}else ye.setExtraStackFrame(null)}function He(e,r,t,n,s){{var c=Function.call.bind(M);for(var i in e)if(c(e,i)){var a=void 0;try{if(typeof e[i]!="function"){var m=Error((n||"React class")+": "+t+" type `"+i+"` is invalid; it must be a function, usually from the `prop-types` package, but received `"+typeof e[i]+"`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");throw m.name="Invariant Violation",m}a=e[i](r,i,n,t,null,"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED")}catch(h){a=h}a&&!(a instanceof Error)&&(X(s),y("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",n||"React class",t,i,typeof a),X(null)),a instanceof Error&&!(a.message in be)&&(be[a.message]=!0,X(s),y("Failed %s type: %s",t,a.message),X(null))}}}var qe=Array.isArray;function ee(e){return qe(e)}function Ze(e){{var r=typeof Symbol=="function"&&Symbol.toStringTag,t=r&&e[Symbol.toStringTag]||e.constructor.name||"Object";return t}}function Qe(e){try{return me(e),!1}catch{return!0}}function me(e){return""+e}function Ee(e){if(Qe(e))return y("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.",Ze(e)),me(e)}var Re=D.ReactCurrentOwner,er={key:!0,ref:!0,__self:!0,__source:!0},je,ke;function rr(e){if(M.call(e,"ref")){var r=Object.getOwnPropertyDescriptor(e,"ref").get;if(r&&r.isReactWarning)return!1}return e.ref!==void 0}function tr(e){if(M.call(e,"key")){var r=Object.getOwnPropertyDescriptor(e,"key").get;if(r&&r.isReactWarning)return!1}return e.key!==void 0}function nr(e,r){typeof e.ref=="string"&&Re.current}function ar(e,r){{var t=function(){je||(je=!0,y("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)",r))};t.isReactWarning=!0,Object.defineProperty(e,"key",{get:t,configurable:!0})}}function ir(e,r){{var t=function(){ke||(ke=!0,y("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)",r))};t.isReactWarning=!0,Object.defineProperty(e,"ref",{get:t,configurable:!0})}}var or=function(e,r,t,n,s,c,i){var a={$$typeof:v,type:e,key:r,ref:t,props:i,_owner:c};return a._store={},Object.defineProperty(a._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:!1}),Object.defineProperty(a,"_self",{configurable:!1,enumerable:!1,writable:!1,value:n}),Object.defineProperty(a,"_source",{configurable:!1,enumerable:!1,writable:!1,value:s}),Object.freeze&&(Object.freeze(a.props),Object.freeze(a)),a};function sr(e,r,t,n,s){{var c,i={},a=null,m=null;t!==void 0&&(Ee(t),a=""+t),tr(r)&&(Ee(r.key),a=""+r.key),rr(r)&&(m=r.ref,nr(r,s));for(c in r)M.call(r,c)&&!er.hasOwnProperty(c)&&(i[c]=r[c]);if(e&&e.defaultProps){var h=e.defaultProps;for(c in h)i[c]===void 0&&(i[c]=h[c])}if(a||m){var g=typeof e=="function"?e.displayName||e.name||"Unknown":e;a&&ar(i,g),m&&ir(i,g)}return or(e,a,m,s,n,Re.current,i)}}var re=D.ReactCurrentOwner,Te=D.ReactDebugCurrentFrame;function Y(e){if(e){var r=e._owner,t=z(e.type,e._source,r?r.type:null);Te.setExtraStackFrame(t)}else Te.setExtraStackFrame(null)}var te;te=!1;function ne(e){return typeof e=="object"&&e!==null&&e.$$typeof===v}function Se(){{if(re.current){var e=N(re.current.type);if(e)return`
|
|
22
22
|
|
|
23
|
-
Check the render method of \``+e+"`."}return""}}function
|
|
23
|
+
Check the render method of \``+e+"`."}return""}}function cr(e){return""}var Oe={};function lr(e){{var r=Se();if(!r){var t=typeof e=="string"?e:e.displayName||e.name;t&&(r=`
|
|
24
24
|
|
|
25
|
-
Check the top-level render call using <`+t+">.")}return r}}function
|
|
25
|
+
Check the top-level render call using <`+t+">.")}return r}}function we(e,r){{if(!e._store||e._store.validated||e.key!=null)return;e._store.validated=!0;var t=lr(r);if(Oe[t])return;Oe[t]=!0;var n="";e&&e._owner&&e._owner!==re.current&&(n=" It was passed a child from "+N(e._owner.type)+"."),Y(e),y('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.',t,n),Y(null)}}function Ce(e,r){{if(typeof e!="object")return;if(ee(e))for(var t=0;t<e.length;t++){var n=e[t];ne(n)&&we(n,r)}else if(ne(e))e._store&&(e._store.validated=!0);else if(e){var s=K(e);if(typeof s=="function"&&s!==e.entries)for(var c=s.call(e),i;!(i=c.next()).done;)ne(i.value)&&we(i.value,r)}}}function ur(e){{var r=e.type;if(r==null||typeof r=="string")return;var t;if(typeof r=="function")t=r.propTypes;else if(typeof r=="object"&&(r.$$typeof===p||r.$$typeof===u))t=r.propTypes;else return;if(t){var n=N(r);He(t,e.props,"prop",n,e)}else if(r.PropTypes!==void 0&&!te){te=!0;var s=N(r);y("Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?",s||"Unknown")}typeof r.getDefaultProps=="function"&&!r.getDefaultProps.isReactClassApproved&&y("getDefaultProps is only used on classic React.createClass definitions. Use a static property named `defaultProps` instead.")}}function fr(e){{for(var r=Object.keys(e.props),t=0;t<r.length;t++){var n=r[t];if(n!=="children"&&n!=="key"){Y(e),y("Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.",n),Y(null);break}}e.ref!==null&&(Y(e),y("Invalid attribute `ref` supplied to `React.Fragment`."),Y(null))}}var Pe={};function xe(e,r,t,n,s,c){{var i=Me(e);if(!i){var a="";(e===void 0||typeof e=="object"&&e!==null&&Object.keys(e).length===0)&&(a+=" You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.");var m=cr();m?a+=m:a+=Se();var h;e===null?h="null":ee(e)?h="array":e!==void 0&&e.$$typeof===v?(h="<"+(N(e.type)||"Unknown")+" />",a=" Did you accidentally export a JSX literal instead of a component?"):h=typeof e,y("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",h,a)}var g=sr(e,r,t,s,c);if(g==null)return g;if(i){var S=r.children;if(S!==void 0)if(n)if(ee(S)){for(var L=0;L<S.length;L++)Ce(S[L],e);Object.freeze&&Object.freeze(S)}else y("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else Ce(S,e)}if(M.call(r,"key")){var I=N(e),R=Object.keys(r).filter(function(_r){return _r!=="key"}),ae=R.length>0?"{key: someKey, "+R.join(": ..., ")+": ...}":"{key: someKey}";if(!Pe[I+ae]){var gr=R.length>0?"{"+R.join(": ..., ")+": ...}":"{}";y(`A props object containing a "key" prop is being spread into JSX:
|
|
26
26
|
let props = %s;
|
|
27
27
|
<%s {...props} />
|
|
28
28
|
React keys must be passed directly to JSX without using spread:
|
|
29
29
|
let props = %s;
|
|
30
|
-
<%s key={someKey} {...props} />`,
|
|
30
|
+
<%s key={someKey} {...props} />`,ae,I,gr,I),Pe[I+ae]=!0}}return e===_?fr(g):ur(g),g}}function dr(e,r,t){return xe(e,r,t,!0)}function vr(e,r,t){return xe(e,r,t,!1)}var pr=vr,hr=dr;V.Fragment=_,V.jsx=pr,V.jsxs=hr}()),V}process.env.NODE_ENV==="production"?H.exports=De():H.exports=Ae();var l=H.exports;const Ne=({cards:d=[]})=>{const[v,k]=j.useState(0),[_,E]=j.useState(!1);if(!d||d.length===0)return l.jsx("div",{className:"flashcard-deck-container",children:l.jsx("p",{className:"flashcard-empty-message",children:"No cards available"})});const w=d[v],C=v>0,T=v<d.length-1,p=()=>{C&&(k(v-1),E(!1))},P=()=>{T&&(k(v+1),E(!1))},o=()=>{E(!_)};return l.jsxs("div",{className:"flashcard-deck-container",children:[l.jsx("div",{className:"flashcard-deck-header",children:l.jsxs("span",{className:"flashcard-counter",children:["Card ",v+1," of ",d.length]})}),l.jsx("div",{className:"flashcard-wrapper",children:l.jsxs(B.motion.div,{className:"flashcard",onClick:o,animate:{rotateY:_?180:0},transition:{duration:.6,type:"spring",stiffness:100},style:{transformStyle:"preserve-3d"},children:[l.jsx("div",{className:"flashcard-face flashcard-front",children:l.jsx("div",{className:"flashcard-content",children:w.front})}),l.jsx("div",{className:"flashcard-face flashcard-back",children:l.jsx("div",{className:"flashcard-content",children:w.back})})]},v)}),l.jsxs("div",{className:"flashcard-controls",children:[l.jsx("button",{className:"flashcard-button flashcard-button-previous",onClick:p,disabled:!C,"aria-label":"Previous card",children:"Previous"}),l.jsx("button",{className:"flashcard-button flashcard-button-next",onClick:P,disabled:!T,"aria-label":"Next card",children:"Next"})]})]})},O={accordion:"_accordion_1gkiv_1",empty:"_empty_1gkiv_17",item:"_item_1gkiv_31",heading:"_heading_1gkiv_73",trigger:"_trigger_1gkiv_85",title:"_title_1gkiv_145",icon:"_icon_1gkiv_155",panelWrapper:"_panelWrapper_1gkiv_179",panel:"_panel_1gkiv_179",content:"_content_1gkiv_195"},Fe=(d,v,k,_)=>d&&k&&k.length>0?new Set(k.filter(E=>_!=null&&E>=0&&E<_)):!d&&v!=null&&v>=0&&_!=null&&v<_?new Set([v]):new Set,Ie=({items:d,allowMultiple:v=!1,defaultExpandedIndex:k,defaultExpandedIndexes:_,className:E})=>{const w=j.useId(),[C,T]=j.useState(()=>Fe(v,k,_,d==null?void 0:d.length)),p=j.useCallback(o=>{T(u=>{const f=new Set(u);return f.has(o)?f.delete(o):(v||f.clear(),f.add(o)),f})},[v]),P=j.useCallback((o,u)=>{var A,W,K,D;const f=o.currentTarget.closest("[data-accordion]"),b=f==null?void 0:f.querySelectorAll("[data-accordion-trigger]");if(b!=null&&b.length)switch(o.key){case"Enter":case" ":o.preventDefault(),p(u);break;case"ArrowDown":case"ArrowRight":o.preventDefault(),u<d.length-1&&((A=b[u+1])==null||A.focus());break;case"ArrowUp":case"ArrowLeft":o.preventDefault(),u>0&&((W=b[u-1])==null||W.focus());break;case"Home":o.preventDefault(),(K=b[0])==null||K.focus();break;case"End":o.preventDefault(),(D=b[b.length-1])==null||D.focus();break}},[d.length,p]);return d!=null&&d.length?l.jsx("div",{className:[O.accordion,E].filter(Boolean).join(" "),role:"region","aria-label":"Accordion","data-accordion":!0,children:d.map((o,u)=>{const f=C.has(u),b=`${w}-header-${u}`,A=`${w}-panel-${u}`;return l.jsxs("div",{className:O.item,"data-open":f,children:[l.jsx("h3",{className:O.heading,children:l.jsxs("button",{type:"button",id:b,className:O.trigger,"aria-expanded":f,"aria-controls":A,"data-accordion-trigger":!0,onClick:()=>p(u),onKeyDown:W=>P(W,u),children:[l.jsx("span",{className:O.title,children:o.title}),l.jsx(B.motion.span,{className:O.icon,animate:{rotate:f?180:0},transition:{duration:.25},"aria-hidden":!0,children:"▼"})]})}),l.jsx("div",{id:A,role:"region","aria-labelledby":b,"aria-hidden":!f,className:O.panelWrapper,children:l.jsx(B.AnimatePresence,{initial:!1,children:f&&l.jsx(B.motion.div,{initial:{height:0,opacity:0},animate:{height:"auto",opacity:1},exit:{height:0,opacity:0},transition:{duration:.25,ease:"easeInOut"},className:O.panel,children:l.jsx("div",{className:O.content,children:o.content})})})})]},o.id??u)})}):l.jsx("div",{className:[O.accordion,E].filter(Boolean).join(" "),role:"region","aria-label":"Accordion",children:l.jsx("p",{className:O.empty,children:"No items to display."})})};x.Accordion=Ie,x.FlashcardDeck=Ne,Object.defineProperty(x,Symbol.toStringTag,{value:"Module"})});
|
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.flashcard-deck-container{display:flex;flex-direction:column;align-items:center;gap:2rem;padding:2rem;max-width:600px;margin:0 auto}.flashcard-deck-header{width:100%;text-align:center}.flashcard-counter{font-size:1rem;color:#666;font-weight:500}.flashcard-wrapper{perspective:1000px;width:100%;min-height:300px}.flashcard{position:relative;width:100%;height:300px;cursor:pointer;transform-style:preserve-3d}.flashcard-face{position:absolute;width:100%;height:100%;backface-visibility:hidden;border-radius:12px;box-shadow:0 4px 6px #0000001a,0 2px 4px #0000000f;display:flex;align-items:center;justify-content:center;padding:2rem;box-sizing:border-box}.flashcard-front{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;transform:rotateY(0)}.flashcard-back{background:linear-gradient(135deg,#f093fb,#f5576c);color:#fff;transform:rotateY(180deg)}.flashcard-content{font-size:1.25rem;text-align:center;word-wrap:break-word;overflow-wrap:break-word;line-height:1.6;font-weight:500}.flashcard-controls{display:flex;gap:1rem;width:100%;justify-content:center}.flashcard-button{padding:.75rem 2rem;font-size:1rem;font-weight:600;border:none;border-radius:8px;cursor:pointer;transition:all .3s ease;background-color:#667eea;color:#fff;min-width:120px}.flashcard-button:hover:not(:disabled){background-color:#5568d3;transform:translateY(-2px);box-shadow:0 4px 8px #667eea4d}.flashcard-button:active:not(:disabled){transform:translateY(0)}.flashcard-button:disabled{background-color:#e0e0e0;color:#9e9e9e;cursor:not-allowed;opacity:.6}.flashcard-empty-message{text-align:center;color:#666;font-size:1.1rem;padding:2rem}@media (max-width: 768px){.flashcard-deck-container{padding:1rem}.flashcard{height:250px}.flashcard-content{font-size:1.1rem}.flashcard-button{padding:.625rem 1.5rem;font-size:.9rem;min-width:100px}}
|
|
1
|
+
.flashcard-deck-container{display:flex;flex-direction:column;align-items:center;gap:2rem;padding:2rem;max-width:600px;margin:0 auto}.flashcard-deck-header{width:100%;text-align:center}.flashcard-counter{font-size:1rem;color:#666;font-weight:500}.flashcard-wrapper{perspective:1000px;width:100%;min-height:300px}.flashcard{position:relative;width:100%;height:300px;cursor:pointer;transform-style:preserve-3d}.flashcard-face{position:absolute;width:100%;height:100%;backface-visibility:hidden;border-radius:12px;box-shadow:0 4px 6px #0000001a,0 2px 4px #0000000f;display:flex;align-items:center;justify-content:center;padding:2rem;box-sizing:border-box}.flashcard-front{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;transform:rotateY(0)}.flashcard-back{background:linear-gradient(135deg,#f093fb,#f5576c);color:#fff;transform:rotateY(180deg)}.flashcard-content{font-size:1.25rem;text-align:center;word-wrap:break-word;overflow-wrap:break-word;line-height:1.6;font-weight:500}.flashcard-controls{display:flex;gap:1rem;width:100%;justify-content:center}.flashcard-button{padding:.75rem 2rem;font-size:1rem;font-weight:600;border:none;border-radius:8px;cursor:pointer;transition:all .3s ease;background-color:#667eea;color:#fff;min-width:120px}.flashcard-button:hover:not(:disabled){background-color:#5568d3;transform:translateY(-2px);box-shadow:0 4px 8px #667eea4d}.flashcard-button:active:not(:disabled){transform:translateY(0)}.flashcard-button:disabled{background-color:#e0e0e0;color:#9e9e9e;cursor:not-allowed;opacity:.6}.flashcard-empty-message{text-align:center;color:#666;font-size:1.1rem;padding:2rem}@media (max-width: 768px){.flashcard-deck-container{padding:1rem}.flashcard{height:250px}.flashcard-content{font-size:1.1rem}.flashcard-button{padding:.625rem 1.5rem;font-size:.9rem;min-width:100px}}._accordion_1gkiv_1{width:100%;max-width:640px;margin:0 auto;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;color:#1a1a1a}._empty_1gkiv_17{padding:1rem 0;margin:0;color:#64748b;font-size:.9375rem}._item_1gkiv_31{border:1px solid #e2e8f0;border-bottom:none}._item_1gkiv_31:last-of-type{border-bottom:1px solid #e2e8f0}._item_1gkiv_31:first-of-type{border-radius:8px 8px 0 0}._item_1gkiv_31:last-of-type{border-radius:0 0 8px 8px}._item_1gkiv_31:only-of-type{border-radius:8px}._heading_1gkiv_73{margin:0;font-size:1rem;font-weight:600}._trigger_1gkiv_85{display:flex;align-items:center;justify-content:space-between;width:100%;padding:1rem 1.25rem;background:#f8fafc;border:none;cursor:pointer;text-align:left;font-size:inherit;font-weight:inherit;color:inherit;transition:background-color .2s ease}._trigger_1gkiv_85:hover{background:#f1f5f9}._trigger_1gkiv_85:focus-visible{outline:2px solid #3b82f6;outline-offset:2px}._item_1gkiv_31[data-open=true] ._trigger_1gkiv_85{background:#fff;border-bottom:1px solid #e2e8f0}._title_1gkiv_145{flex:1;padding-right:.75rem}._icon_1gkiv_155{flex-shrink:0;font-size:.65rem;color:#64748b;transition:color .2s ease}._trigger_1gkiv_85:hover ._icon_1gkiv_155,._item_1gkiv_31[data-open=true] ._icon_1gkiv_155{color:#3b82f6}._panelWrapper_1gkiv_179,._panel_1gkiv_179{overflow:hidden}._content_1gkiv_195{padding:1rem 1.25rem 1.25rem;background:#fff;font-size:.9375rem;line-height:1.6;color:#334155}._content_1gkiv_195 :first-child{margin-top:0}._content_1gkiv_195 :last-child{margin-bottom:0}
|
package/package.json
CHANGED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Accordion Component
|
|
2
|
+
|
|
3
|
+
A fully accessible, animated React accordion built with Framer Motion and CSS Modules. It renders a list of collapsible items with smooth expand/collapse animations and supports either single or multiple open panels.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The `Accordion` component is designed for e-learning and content-heavy UIs where you need to reveal content on demand without leaving the page. Use it for FAQs, course modules, step-by-step instructions, or any list of titled sections with collapsible bodies.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Animated expand/collapse** using Framer Motion and `AnimatePresence`
|
|
12
|
+
- **Single or multiple open panels** controlled by the `allowMultiple` prop
|
|
13
|
+
- **Keyboard accessible**: Enter/Space to toggle, Arrow keys to move focus, Home/End for first/last
|
|
14
|
+
- **ARIA-compliant**: correct `aria-expanded`, `aria-controls`, `aria-labelledby`, and `aria-hidden` for screen readers
|
|
15
|
+
- **Clean, modern styling** via CSS Modules with a clear header/content distinction
|
|
16
|
+
- **TypeScript** with exported `AccordionProps` and `AccordionItem` interfaces
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
### Prerequisites
|
|
21
|
+
|
|
22
|
+
- React 16.8+ (hooks)
|
|
23
|
+
- Node.js and npm (or yarn/pnpm)
|
|
24
|
+
|
|
25
|
+
### Dependencies
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install react react-dom framer-motion
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or with yarn:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
yarn add react react-dom framer-motion
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or with pnpm:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm add react react-dom framer-motion
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Props
|
|
44
|
+
|
|
45
|
+
| Prop | Type | Required | Default | Description |
|
|
46
|
+
|------|------|----------|---------|-------------|
|
|
47
|
+
| `items` | `AccordionItem[]` | Yes | — | List of accordion items. Each has `title` and `content`. |
|
|
48
|
+
| `allowMultiple` | `boolean` | No | `false` | If `true`, multiple panels can be open at once. If `false`, opening one closes the others. |
|
|
49
|
+
| `defaultExpandedIndex` | `number` | No | — | When `allowMultiple` is `false`, the index of the initially open item. Omit or use `-1` for none. |
|
|
50
|
+
| `defaultExpandedIndexes` | `number[]` | No | — | When `allowMultiple` is `true`, the indices of initially open items. |
|
|
51
|
+
| `className` | `string` | No | — | Optional class name applied to the root accordion element. |
|
|
52
|
+
|
|
53
|
+
### AccordionItem
|
|
54
|
+
|
|
55
|
+
| Property | Type | Required | Description |
|
|
56
|
+
|----------|------|----------|-------------|
|
|
57
|
+
| `id` | `string` | No | Optional unique id for the item (used as React `key` when provided). |
|
|
58
|
+
| `title` | `string` | Yes | Label for the accordion header. |
|
|
59
|
+
| `content` | `React.ReactNode` | Yes | Content shown when the panel is expanded. |
|
|
60
|
+
|
|
61
|
+
### TypeScript interfaces
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import Accordion, { AccordionProps, AccordionItem } from './Accordion';
|
|
65
|
+
|
|
66
|
+
// AccordionItem: each item in the list
|
|
67
|
+
interface AccordionItem {
|
|
68
|
+
id?: string;
|
|
69
|
+
title: string;
|
|
70
|
+
content: React.ReactNode;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// AccordionProps: component props
|
|
74
|
+
interface AccordionProps {
|
|
75
|
+
items: AccordionItem[];
|
|
76
|
+
allowMultiple?: boolean;
|
|
77
|
+
defaultExpandedIndex?: number;
|
|
78
|
+
defaultExpandedIndexes?: number[];
|
|
79
|
+
className?: string;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
### Basic example (single open panel)
|
|
86
|
+
|
|
87
|
+
Only one panel is open at a time. No panel is open initially.
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import Accordion, { AccordionItem } from './components/Accordion';
|
|
91
|
+
|
|
92
|
+
const items: AccordionItem[] = [
|
|
93
|
+
{
|
|
94
|
+
title: 'What is included?',
|
|
95
|
+
content: 'Access to all lessons, quizzes, and downloadable resources for 12 months.',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
title: 'Can I get a refund?',
|
|
99
|
+
content: 'Yes. You can request a full refund within 14 days of purchase if you have not completed more than 2 lessons.',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
title: 'How do I get support?',
|
|
103
|
+
content: 'Use the Help button in the course dashboard or email support@example.com.',
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
function FAQ() {
|
|
108
|
+
return <Accordion items={items} />;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Multiple panels open
|
|
113
|
+
|
|
114
|
+
Allow several panels to be open at once, with the first and third open by default:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
<Accordion
|
|
118
|
+
items={items}
|
|
119
|
+
allowMultiple
|
|
120
|
+
defaultExpandedIndexes={[0, 2]}
|
|
121
|
+
/>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### One panel open by default (single mode)
|
|
125
|
+
|
|
126
|
+
Open only the second item initially:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
<Accordion
|
|
130
|
+
items={items}
|
|
131
|
+
defaultExpandedIndex={1}
|
|
132
|
+
/>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Custom root class
|
|
136
|
+
|
|
137
|
+
Pass a `className` to style or position the accordion:
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
<Accordion items={items} className="my-accordion" />
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Rich content
|
|
144
|
+
|
|
145
|
+
`content` can be any `React.ReactNode` (e.g. lists, links, components):
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
const items: AccordionItem[] = [
|
|
149
|
+
{
|
|
150
|
+
title: 'Module 1: Introduction',
|
|
151
|
+
content: (
|
|
152
|
+
<>
|
|
153
|
+
<p>Welcome to the course. In this module you will:</p>
|
|
154
|
+
<ul>
|
|
155
|
+
<li>Learn the key concepts</li>
|
|
156
|
+
<li>Complete a short quiz</li>
|
|
157
|
+
</ul>
|
|
158
|
+
</>
|
|
159
|
+
),
|
|
160
|
+
},
|
|
161
|
+
];
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Accessibility
|
|
165
|
+
|
|
166
|
+
- **Keyboard**: Each header is focusable. Enter or Space toggles the panel. Arrow Down/Right moves to the next header, Arrow Up/Left to the previous. Home focuses the first header, End the last.
|
|
167
|
+
- **ARIA**: The root has `role="region"` and `aria-label="Accordion"`. Each trigger has `aria-expanded` and `aria-controls` pointing to its panel. Each panel has `role="region"`, `aria-labelledby` pointing to its header, and `aria-hidden` when collapsed.
|
|
168
|
+
- **Focus**: Focus is not trapped; keyboard users can tab in and out of the accordion and use the arrow keys to move between headers.
|
|
169
|
+
|
|
170
|
+
## Styling
|
|
171
|
+
|
|
172
|
+
Styles are in `style.module.css`. You can override by passing `className` or by targeting the accordion’s container in your own CSS. The design uses a light gray header background and white content area with a clear border between header and content.
|
|
173
|
+
|
|
174
|
+
## File structure
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
Accordion/
|
|
178
|
+
index.tsx # Component and TypeScript interfaces
|
|
179
|
+
style.module.css # Component styles
|
|
180
|
+
README.md # This file
|
|
181
|
+
```
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import React, { useState, useCallback, useId } from 'react';
|
|
2
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
3
|
+
import styles from './style.module.css';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A single accordion item with a title and collapsible content.
|
|
7
|
+
*/
|
|
8
|
+
export interface AccordionItem {
|
|
9
|
+
/** Optional unique id. If omitted, index is used. */
|
|
10
|
+
id?: string;
|
|
11
|
+
/** Header label (always visible). */
|
|
12
|
+
title: string;
|
|
13
|
+
/** Content shown when the item is expanded. */
|
|
14
|
+
content: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Props for the Accordion component.
|
|
19
|
+
*/
|
|
20
|
+
export interface AccordionProps {
|
|
21
|
+
/** List of accordion items (title + content). */
|
|
22
|
+
items: AccordionItem[];
|
|
23
|
+
/** If true, multiple items can be open at once. Default: false. */
|
|
24
|
+
allowMultiple?: boolean;
|
|
25
|
+
/** When allowMultiple is false, the index of the item to be open initially. Omit or -1 for none. */
|
|
26
|
+
defaultExpandedIndex?: number;
|
|
27
|
+
/** When allowMultiple is true, the indices of items to be open initially. */
|
|
28
|
+
defaultExpandedIndexes?: number[];
|
|
29
|
+
/** Optional className for the root element. */
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const getInitialOpen = (
|
|
34
|
+
allowMultiple: boolean,
|
|
35
|
+
defaultExpandedIndex?: number,
|
|
36
|
+
defaultExpandedIndexes?: number[],
|
|
37
|
+
itemCount?: number
|
|
38
|
+
): Set<number> => {
|
|
39
|
+
if (allowMultiple && defaultExpandedIndexes && defaultExpandedIndexes.length > 0) {
|
|
40
|
+
return new Set(
|
|
41
|
+
defaultExpandedIndexes.filter((i) => itemCount != null && i >= 0 && i < itemCount)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (!allowMultiple && defaultExpandedIndex != null && defaultExpandedIndex >= 0 && itemCount != null && defaultExpandedIndex < itemCount) {
|
|
45
|
+
return new Set([defaultExpandedIndex]);
|
|
46
|
+
}
|
|
47
|
+
return new Set<number>();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const Accordion: React.FC<AccordionProps> = ({
|
|
51
|
+
items,
|
|
52
|
+
allowMultiple = false,
|
|
53
|
+
defaultExpandedIndex,
|
|
54
|
+
defaultExpandedIndexes,
|
|
55
|
+
className,
|
|
56
|
+
}) => {
|
|
57
|
+
const baseId = useId();
|
|
58
|
+
const [openSet, setOpenSet] = useState<Set<number>>(() =>
|
|
59
|
+
getInitialOpen(allowMultiple, defaultExpandedIndex, defaultExpandedIndexes, items?.length)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const toggle = useCallback(
|
|
63
|
+
(index: number) => {
|
|
64
|
+
setOpenSet((prev) => {
|
|
65
|
+
const next = new Set(prev);
|
|
66
|
+
if (next.has(index)) {
|
|
67
|
+
next.delete(index);
|
|
68
|
+
} else {
|
|
69
|
+
if (!allowMultiple) next.clear();
|
|
70
|
+
next.add(index);
|
|
71
|
+
}
|
|
72
|
+
return next;
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
[allowMultiple]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const handleKeyDown = useCallback(
|
|
79
|
+
(e: React.KeyboardEvent, index: number) => {
|
|
80
|
+
const accordion = (e.currentTarget as HTMLElement).closest('[data-accordion]');
|
|
81
|
+
const triggers = accordion?.querySelectorAll<HTMLElement>('[data-accordion-trigger]');
|
|
82
|
+
if (!triggers?.length) return;
|
|
83
|
+
|
|
84
|
+
switch (e.key) {
|
|
85
|
+
case 'Enter':
|
|
86
|
+
case ' ':
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
toggle(index);
|
|
89
|
+
break;
|
|
90
|
+
case 'ArrowDown':
|
|
91
|
+
case 'ArrowRight':
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
if (index < items.length - 1) triggers[index + 1]?.focus();
|
|
94
|
+
break;
|
|
95
|
+
case 'ArrowUp':
|
|
96
|
+
case 'ArrowLeft':
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
if (index > 0) triggers[index - 1]?.focus();
|
|
99
|
+
break;
|
|
100
|
+
case 'Home':
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
triggers[0]?.focus();
|
|
103
|
+
break;
|
|
104
|
+
case 'End':
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
triggers[triggers.length - 1]?.focus();
|
|
107
|
+
break;
|
|
108
|
+
default:
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
[items.length, toggle]
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (!items?.length) {
|
|
116
|
+
return (
|
|
117
|
+
<div className={[styles.accordion, className].filter(Boolean).join(' ')} role="region" aria-label="Accordion">
|
|
118
|
+
<p className={styles.empty}>No items to display.</p>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
className={[styles.accordion, className].filter(Boolean).join(' ')}
|
|
126
|
+
role="region"
|
|
127
|
+
aria-label="Accordion"
|
|
128
|
+
data-accordion
|
|
129
|
+
>
|
|
130
|
+
{items.map((item, index) => {
|
|
131
|
+
const isOpen = openSet.has(index);
|
|
132
|
+
const headerId = `${baseId}-header-${index}`;
|
|
133
|
+
const panelId = `${baseId}-panel-${index}`;
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div
|
|
137
|
+
key={item.id ?? index}
|
|
138
|
+
className={styles.item}
|
|
139
|
+
data-open={isOpen}
|
|
140
|
+
>
|
|
141
|
+
<h3 className={styles.heading}>
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
id={headerId}
|
|
145
|
+
className={styles.trigger}
|
|
146
|
+
aria-expanded={isOpen}
|
|
147
|
+
aria-controls={panelId}
|
|
148
|
+
data-accordion-trigger
|
|
149
|
+
onClick={() => toggle(index)}
|
|
150
|
+
onKeyDown={(e) => handleKeyDown(e, index)}
|
|
151
|
+
>
|
|
152
|
+
<span className={styles.title}>{item.title}</span>
|
|
153
|
+
<motion.span
|
|
154
|
+
className={styles.icon}
|
|
155
|
+
animate={{ rotate: isOpen ? 180 : 0 }}
|
|
156
|
+
transition={{ duration: 0.25 }}
|
|
157
|
+
aria-hidden
|
|
158
|
+
>
|
|
159
|
+
▼
|
|
160
|
+
</motion.span>
|
|
161
|
+
</button>
|
|
162
|
+
</h3>
|
|
163
|
+
<div
|
|
164
|
+
id={panelId}
|
|
165
|
+
role="region"
|
|
166
|
+
aria-labelledby={headerId}
|
|
167
|
+
aria-hidden={!isOpen}
|
|
168
|
+
className={styles.panelWrapper}
|
|
169
|
+
>
|
|
170
|
+
<AnimatePresence initial={false}>
|
|
171
|
+
{isOpen && (
|
|
172
|
+
<motion.div
|
|
173
|
+
initial={{ height: 0, opacity: 0 }}
|
|
174
|
+
animate={{ height: 'auto', opacity: 1 }}
|
|
175
|
+
exit={{ height: 0, opacity: 0 }}
|
|
176
|
+
transition={{ duration: 0.25, ease: 'easeInOut' }}
|
|
177
|
+
className={styles.panel}
|
|
178
|
+
>
|
|
179
|
+
<div className={styles.content}>{item.content}</div>
|
|
180
|
+
</motion.div>
|
|
181
|
+
)}
|
|
182
|
+
</AnimatePresence>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
})}
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export default Accordion;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
.accordion {
|
|
2
|
+
width: 100%;
|
|
3
|
+
max-width: 640px;
|
|
4
|
+
margin: 0 auto;
|
|
5
|
+
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
6
|
+
color: #1a1a1a;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.empty {
|
|
10
|
+
padding: 1rem 0;
|
|
11
|
+
margin: 0;
|
|
12
|
+
color: #64748b;
|
|
13
|
+
font-size: 0.9375rem;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.item {
|
|
17
|
+
border: 1px solid #e2e8f0;
|
|
18
|
+
border-bottom: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.item:last-of-type {
|
|
22
|
+
border-bottom: 1px solid #e2e8f0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.item:first-of-type {
|
|
26
|
+
border-radius: 8px 8px 0 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.item:last-of-type {
|
|
30
|
+
border-radius: 0 0 8px 8px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.item:only-of-type {
|
|
34
|
+
border-radius: 8px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.heading {
|
|
38
|
+
margin: 0;
|
|
39
|
+
font-size: 1rem;
|
|
40
|
+
font-weight: 600;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.trigger {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: space-between;
|
|
47
|
+
width: 100%;
|
|
48
|
+
padding: 1rem 1.25rem;
|
|
49
|
+
background: #f8fafc;
|
|
50
|
+
border: none;
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
text-align: left;
|
|
53
|
+
font-size: inherit;
|
|
54
|
+
font-weight: inherit;
|
|
55
|
+
color: inherit;
|
|
56
|
+
transition: background-color 0.2s ease;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.trigger:hover {
|
|
60
|
+
background: #f1f5f9;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.trigger:focus-visible {
|
|
64
|
+
outline: 2px solid #3b82f6;
|
|
65
|
+
outline-offset: 2px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.item[data-open="true"] .trigger {
|
|
69
|
+
background: #fff;
|
|
70
|
+
border-bottom: 1px solid #e2e8f0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.title {
|
|
74
|
+
flex: 1;
|
|
75
|
+
padding-right: 0.75rem;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.icon {
|
|
79
|
+
flex-shrink: 0;
|
|
80
|
+
font-size: 0.65rem;
|
|
81
|
+
color: #64748b;
|
|
82
|
+
transition: color 0.2s ease;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.trigger:hover .icon,
|
|
86
|
+
.item[data-open="true"] .icon {
|
|
87
|
+
color: #3b82f6;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.panelWrapper {
|
|
91
|
+
overflow: hidden;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.panel {
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.content {
|
|
99
|
+
padding: 1rem 1.25rem 1.25rem;
|
|
100
|
+
background: #fff;
|
|
101
|
+
font-size: 0.9375rem;
|
|
102
|
+
line-height: 1.6;
|
|
103
|
+
color: #334155;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.content :first-child {
|
|
107
|
+
margin-top: 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.content :last-child {
|
|
111
|
+
margin-bottom: 0;
|
|
112
|
+
}
|
package/src/index.ts
CHANGED