Nondeterministic Palindrome Machine

    @nrk/core-tabs

    2.1.0 • Public • Published

    Core Tabs

    @nrk/core-tabs converts <button> and <a> elements to keyboard accessible tabs, controlling following tabpanels. Tabs can be nested and easily extended with custom animations or behaviour through the tabs.toggle event.

    Example

    <!--demo-->
    <core-tabs>
      <button>Tab 1</button>
      <button>Tab 2</button>
      <a href="#link">Tab 3</a>
    </core-tabs>
    <div>Tabpanel 1</div>
    <div hidden>
      <core-tabs>
        <button>Subtab 1</button>
        <button>Subtab 2</button>
        <button>Subtab 3</button>
      </core-tabs>
      <div>Subtabpanel 1</div>
      <div>Subtabpanel 2</div>
      <div>Subtabpanel 3</div>
    </div>
    <div hidden>Tabpanel 3</div>
    <!--demo-->
    <div id="jsx-tabs" class="my-vertical-tabs"></div>
    <script type="text/jsx">
      ReactDOM.render(<div>
        <CoreTabs>
          <button>Vertical tab 1 JSX</button>
          <button>Vertical tab 2 JSX</button>
        </CoreTabs>
        <div>Tabpanel 1 JSX</div>
        <div>
          <CoreTabs>
            <button>Subtab 1 JSX</button>
            <button hidden>Subtab 2 JSX</button>
          </CoreTabs>
          <div>Subtabpanel 1</div>
          <div hidden>Subtabpanel 2</div>
        </div>
      </div>, document.getElementById('jsx-tabs'))
    </script>
    <!--demo-->
    <div id="jsx-dynamic-tabs" class="my-vertical-tabs"></div>
    <script type="text/jsx">
      const Dynamic = () => {
          const [elements, setElements] = React.useState([])
          const menu = elements.map(item => <button type="button">Dynamic Tab {item}</button>);
          const pages = elements.map(item => <div>Tabpanel {item}</div>);
    
          return (
            <>
              <button type="button" onClick={() => setElements([...elements, elements.length + 1])}>
                Add extra tab
              </button>
              <button type="button" onClick={() => setElements([1,2])}>
                Set to two tabs
              </button>
              <button type="button" onClick={() => setElements([])}>
                Remove all
              </button>
              <CoreTabs>
                {menu}
              </CoreTabs>
              {pages}
            </>
          )
        }
      ReactDOM.render(<Dynamic />, document.getElementById('jsx-dynamic-tabs'))
    </script>

    Installation

    Using NPM provides own element namespace and extensibility. Recommended:

    npm install @nrk/core-tabs  # Using NPM

    Using static registers the custom element with default name automatically:

    <script src="https://static.nrk.no/core-components/major/7/core-tabs/core-tabs.min.js"></script>  <!-- Using static -->

    Remember to polyfill custom elements if needed.

    Usage

    HTML / JavaScript

    <core-tabs>
      <button>Tab 1</button>                  <!-- Tab elements must be <a> or <button>. Do not use <li> -->
      <a href="#">Tab 2</a>
      <button>Tab 3</button>
      <button data-for="panel-2">Tab 4</button>    <!-- Point to a specific tabpanel -->
    </core-tabs>
    <div>Tabpanel 1 content</div>             <!-- First tabpanel is the next element sibling of core-tabs -->
    <div hidden>Tabpanel 2 content</div>      <!-- Second tabpanel. Use hidden attribute to prevent FOUC -->
    <div hidden id="panel-2">Tabpanel 3 content</div>      <!-- Third tabpanel. ID used to connect to tab 4 -->
    import CoreTabs from '@nrk/core-tabs'                 // Using NPM
    window.customElements.define('core-tabs', CoreTabs)   // Using NPM. Replace 'core-tabs' with 'my-tabs' to namespace
    
    const myTabs = document.querySelector('core-tabs')
    
    // Getters
    myTabs.tab        // Get active tab
    myTabs.tabs       // Get all tabs
    myTabs.panel      // Get active tabpanel
    myTabs.panels     // Get all tabpanels
    
    // Setters
    myTabs.tab = 0        // Set active tab from index
    myTabs.tab = 'my-tab' // Set active tab from id
    myTabs.tab = myTab    // Set active tab from element

    React / Preact

    import CoreTabs from '@nrk/core-tabs/jsx'
    
    <CoreTabs data-for={Number|String}      // Optional. Sets active tab by index or id
              ref={(comp) => {}}            // Optional. Get reference to React component
              forwardRef={(el) => {}}       // Optional. Get reference to underlying DOM custom element
              onTabsToggle={Function}>      // Optional. Listen to toggle event
      <button>Tab 1</button>                // Tab elements must be <a> or <button>. Do not use <li>
      <a href="#">Tab 2</a>
    </CoreTabs>
    <div>Tabpanel 1 content</div>           // First tabpanel is the next element sibling of CoreTabs
    <div hidden>Tabpanel 1 content</div>    // Second tabpanel. Use hidden attribute to prevent FOUC
    <div hidden>Tabpanel 1 content</div>    // Third tabpanel.  Use hidden attribute to prevent FOUC

    Events

    tabs.toggle

    Fired when toggling a tab:

    document.addEventListener('tabs.toggle', (event) =>
      event.target     // The tabs element
    })

    Styling

    All styling in documentation is example only. Both the tabs and tabpanels receive attributes reflecting the current toggle state:

    .my-tab {}                          /* Target tab in any state */
    .my-tab[aria-selected="true"] {}    /* Target only open tab */
    .my-tab[aria-selected="false"] {}   /* Target only closed tab */
    
    .my-tabpanel {}                     /* Target panel element in any state */
    .my-tabpanel:not([hidden]) {}       /* Target only open panel */
    .my-tabpanel[hidden] {}             /* Target only closed panel */

    FAQ

    Why aren't tabs wrapped in <ul><li>...</li></ul>?

    A <ul>/<li> structure would seem logical for tabs, but this causes some screen readers to incorrectly announce tabs as single (tab 1 of 1).

    Does panels always need be a next element sibling?

    The aria specification does not allow any elements that are focusable by a screen reader to be placed between tabs and panels. Therefore, core-tabs defaults to use the next element siblings as panels. This behaviour can be overridden, by setting up id on panel elements and the data-for attribute on tab element (for is deprecated). Use with caution and only do this if your project must use another DOM structure. Example:

    const myTabs = document.querySelector('core-tabs')
    myTabs.tabs.forEach((tabs, index) => tab.setAttribute('data-for', myTabs.panels[index].id = 'my-panel-' + index))

    Keywords

    none

    Install

    npm i @nrk/core-tabs

    DownloadsWeekly Downloads

    150

    Version

    2.1.0

    License

    MIT

    Unpacked Size

    142 kB

    Total Files

    12

    Last publish

    Collaborators

    • yngvar-nrk
    • lorecaster
    • wwalmnes
    • nrk-ps-teamcity
    • vincent.andersson
    • kristofferdyrkorn
    • swla
    • nrk-midas-jenkins
    • andorpandor
    • jarlelin
    • aevare
    • nrkrichard
    • gesi
    • gundelsby-nrk
    • jonstalecarlsen
    • machineboy
    • vagifabilov
    • nrk-sofie-ci
    • nytamin
    • jesperstarkar
    • loftum
    • emte123
    • skjalgepalg
    • eirikhalvard
    • snorreeb
    • astokke
    • n640071
    • n07073
    • kristian.rosenvold
    • henrik-mattsson
    • eirikbacker
    • haavardm
    • popeindustries
    • yr
    • nrk-kurator-jenkins
    • toshb
    • jstnhlj
    • torgeilo
    • nrk-user-sync
    • dhdeploy
    • rognstadragnar
    • janerikbr
    • espenwa
    • rogerhmar
    • ovstetun
    • eivind
    • stianlj
    • haraldkj
    • mariusu
    • spesialsnorre
    • geirt
    • cristobal
    • knuthaug
    • thohalv
    • johnarne
    • nrk-sup-jenkins
    • eshaswini
    • morrow
    • macbruker
    • oyvindeh
    • laat
    • toggu
    • nrk-jenkins
    • plomma
    • evjand
    • moltubakk
    • erlord
    • ingridguren
    • lu-lux
    • andersli
    • silje
    • stiandg
    • sjurlur
    • anderscan
    • terjemeisingset
    • andipodnrk
    • pkej
    • yosrimti
    • zenangst
    • morten.nyhaug
    • ingvildcath
    • erlend.jones
    • brneirik
    • mollerse
    • eriksalhus
    • frdrks
    • tbnrk
    • nordanke
    • balte
    • mikaelrss
    • simonmitternacht
    • christiankarlsson9
    • martintorgersen
    • rebchr
    • steipal
    • discobus
    • ingvesund
    • martingundersen
    • tinkajts
    • hallvardlid
    • tomivar
    • ajaco
    • tobinus
    • mortenok
    • nrk-ark-deploy
    • jeangilbertlouis
    • heidimork
    • ingriddraagen
    • fridajalborg
    • bruusi
    • rosvoll
    • christianeide
    • olefjaerestad
    • andreeide
    • enordby
    • artzag
    • glen_imrie
    • mia.aasbakken
    • danielo200
    • elathamna
    • evjjan17
    • olatoft
    • kongsrud
    • chrpeter
    • ingvildforseth
    • esseb
    • talepre
    • haraldk76
    • stigok
    • dagfinno
    • johannesodland
    • anders993
    • vildefj
    • vildepk
    • malinaandahl
    • filonenkodmitry
    • andreakn
    • rolerboler
    • meloygutt
    • anders.baggethun