Menu

Change language

GitHub repo

Toggle Setting List

blocklayout

List of toggle-able settings in a SectionCard. Spell rows out inline with ToggleSettingRow — each row is a controlled switch taking `checked` + `onCheckedChange`.

Installation

$npx shadcn@latest add https://uipkge.dev/r/lit/toggle-setting-list.json
Manifest:https://uipkge.dev/r/lit/toggle-setting-list.jsonInstalls to:src/uip/blocks/toggle-setting-list/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
headingstringNotificationsoptional
descriptionstring—optional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

HTMLElementTagNameMap
interface HTMLElementTagNameMap {
    'uip-toggle-setting-list': UipToggleSettingList
    'uip-toggle-setting-row': UipToggleSettingRow
  }
}

npm dependencies

Files installed (1)

  • src/uip/blocks/toggle-setting-list/toggle-setting-list.ts4.3 kB
    import { LitElement, css, html, nothing } from 'lit'
    import { cn } from '../../lib/utils'
    import { tailwind } from '../../lib/styles'
    import { ThemeController } from '../../lib/theme'
    // Side-effect import: registers <uip-section-card> for standalone use
    // (consumers who install only this block still get a working card).
    import '../../components/section-card/section-card'
    
    /**
     * <uip-toggle-setting-list> — the registry ToggleSettingList block as a web component.
     *
     * Class strings are React's ToggleSettingList verbatim. React's `title`
     * becomes `heading` (`title` is taken by HTMLElement); `headerIcon`
     * becomes the `header-icon` slot (forwarded to SectionCard's
     * `header-action` slot); `children` becomes the default slot (the rows,
     * rendered inside React's `-my-4 divide-y` list). Rows are
     * <uip-toggle-setting-row> elements (same file, same install unit).
     */
    export class UipToggleSettingList extends LitElement {
      static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }
      // :host display can't be a utility class (the host has no template of its own).
      static styles = [tailwind, css`:host { display: block; }`]
    
      static properties = {
        heading: {},
        description: {},
      }
    
      heading = 'Notifications'
      description?: string
    
      constructor() {
        super()
        new ThemeController(this)
      }
    
      connectedCallback() {
        super.connectedCallback()
        this.setAttribute('data-uipkge', '')
        this.setAttribute('data-slot', 'toggle-setting-list')
      }
    
      render() {
        return html`
          <uip-section-card data-slot="toggle-setting-list" heading=${this.heading} description=${this.description ?? nothing}>
            <span slot="header-action"><slot name="header-icon"></slot></span>
            <ul class="-my-4 divide-y"><slot></slot></ul>
          </uip-section-card>
        `
      }
    }
    
    /**
     * <uip-toggle-setting-row> — React's ToggleSettingRow: one controlled switch
     * row. Class strings are React's verbatim (the hand-rolled
     * `role="switch"` button, not the Switch primitive). `checked` is the live
     * state; `onCheckedChange` becomes the `checked-change` CustomEvent (detail:
     * boolean, bubbles, composed).
     */
    export class UipToggleSettingRow extends LitElement {
      static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }
      static styles = [tailwind, css`:host { display: block; }`]
    
      static properties = {
        label: {},
        desc: {},
        checked: { type: Boolean },
      }
    
      label = ''
      desc?: string
      checked = false
    
      constructor() {
        super()
        new ThemeController(this)
      }
    
      connectedCallback() {
        super.connectedCallback()
        this.setAttribute('data-uipkge', '')
        this.setAttribute('data-slot', 'toggle-setting-row')
      }
    
      private toggle() {
        this.checked = !this.checked
        this.dispatchEvent(new CustomEvent<boolean>('checked-change', { detail: this.checked, bubbles: true, composed: true }))
      }
    
      render() {
        return html`
          <li class="flex items-center justify-between gap-4 py-4 first:pt-0">
            <div>
              <p class="text-sm font-medium">${this.label}</p>
              ${this.desc ? html`<p class="text-muted-foreground text-xs">${this.desc}</p>` : nothing}
            </div>
            <button
              type="button"
              role="switch"
              aria-label=${this.label}
              aria-checked=${this.checked ? 'true' : 'false'}
              class=${cn(
                'focus-visible:ring-ring relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full transition-colors focus:outline-none focus-visible:ring-2',
                this.checked ? 'bg-primary' : 'bg-muted-foreground/30',
              )}
              @click=${this.toggle}
            >
              <span
                class=${cn(
                  'bg-background inline-block size-4 translate-y-0.5 rounded-full shadow transition-transform',
                  this.checked ? 'translate-x-[18px]' : 'translate-x-0.5',
                )}
              ></span>
            </button>
          </li>
        `
      }
    }
    
    customElements.get('uip-toggle-setting-list') ||
      customElements.define('uip-toggle-setting-list', UipToggleSettingList)
    customElements.get('uip-toggle-setting-row') || customElements.define('uip-toggle-setting-row', UipToggleSettingRow)
    
    declare global {
      interface HTMLElementTagNameMap {
        'uip-toggle-setting-list': UipToggleSettingList
        'uip-toggle-setting-row': UipToggleSettingRow
      }
    }
    

Raw manifest:https://uipkge.dev/r/lit/toggle-setting-list.json