Menu

Change language

GitHub repo

Quick Actions

blocklayout

Vertical list of clickable shortcuts in a SectionCard. Spell rows out inline with QuickActionItem — swap the anchor for your router Link when you need SPA routing.

Installation

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

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
headingstringQuick Actionsoptional
descriptionstring—optional

Schema

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

HTMLElementTagNameMap
interface HTMLElementTagNameMap {
    'uip-quick-actions': UipQuickActions
    'uip-quick-action-item': UipQuickActionItem
  }
}

Files installed (1)

  • src/uip/blocks/quick-actions/quick-actions.ts4.1 kB
    import { LitElement, css, html, nothing } from 'lit'
    import { ArrowUpRight, type IconNode } from 'lucide'
    import * as icons from 'lucide'
    import { tailwind } from '../../lib/styles'
    import { ThemeController } from '../../lib/theme'
    import { icon } from '../../lib/icon'
    // Side-effect imports: register the composed elements for standalone use
    // (consumers who install only this block still get working primitives).
    import '../../components/icon-box/icon-box'
    import '../../components/section-card/section-card'
    
    function toPascal(kebab: string) {
      return kebab
        .split('-')
        .map((p) => (p ? `${p[0] ?? ''}`.toUpperCase() + p.slice(1) : ''))
        .join('')
    }
    
    /**
     * <uip-quick-actions> — the registry QuickActions block as a web component.
     *
     * Class strings are React's QuickActions / QuickActionItem verbatim.
     * `title` is `heading` (`title` is taken by HTMLElement); rows are
     * <uip-quick-action-item> light-DOM children (React's `children`), spaced by
     * the block's own `flex flex-col gap-2` wrapper (React's `contentClassName`).
     * Swap the anchor for your router link when you need SPA routing — you own
     * this file.
     */
    export class UipQuickActions 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 = 'Quick Actions'
      description?: string
    
      constructor() {
        super()
        new ThemeController(this)
      }
    
      connectedCallback() {
        super.connectedCallback()
        this.setAttribute('data-uipkge', '')
        this.setAttribute('data-slot', 'quick-actions')
      }
    
      render() {
        return html`
          <uip-section-card heading=${this.heading} description=${this.description ?? nothing}>
            <div class="flex flex-col gap-2"><slot></slot></div>
          </uip-section-card>
        `
      }
    }
    
    /**
     * <uip-quick-action-item> — one shortcut row (React's QuickActionItem).
     * `label` + `href` render the anchor; the icon is `icon-name` (kebab-case
     * lucide name, e.g. `user-plus`) or the `icon` IconNode property.
     */
    export class UipQuickActionItem 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 = {
        label: {},
        href: {},
        icon: { attribute: false },
        iconName: { attribute: 'icon-name' },
      }
    
      label = ''
      href?: string
      icon?: IconNode
      iconName?: string
    
      constructor() {
        super()
        new ThemeController(this)
      }
    
      connectedCallback() {
        super.connectedCallback()
        this.setAttribute('data-uipkge', '')
        this.setAttribute('data-slot', 'quick-action-item')
      }
    
      private get resolvedIcon(): IconNode | undefined {
        if (this.icon) return this.icon
        if (!this.iconName) return undefined
        const node = (icons as unknown as Record<string, IconNode>)[toPascal(this.iconName)]
        return node
      }
    
      render() {
        const node = this.resolvedIcon
        return html`
          <a
            href=${this.href ?? nothing}
            class="hover:border-border hover:bg-muted/50 group flex cursor-pointer items-center gap-3 rounded-lg border border-transparent p-3 transition-colors duration-200"
          >
            ${node ? html`<uip-icon-box .icon=${node} icon-name=${this.iconName ?? 'icon'}></uip-icon-box>` : nothing}
            <span class="flex-1 text-sm font-medium">${this.label}</span>
            ${icon(ArrowUpRight, 'arrow-up-right', 'text-muted-foreground size-4 opacity-0 transition-colors duration-200 group-hover:opacity-100')}
          </a>
        `
      }
    }
    
    customElements.get('uip-quick-actions') || customElements.define('uip-quick-actions', UipQuickActions)
    customElements.get('uip-quick-action-item') || customElements.define('uip-quick-action-item', UipQuickActionItem)
    
    declare global {
      interface HTMLElementTagNameMap {
        'uip-quick-actions': UipQuickActions
        'uip-quick-action-item': UipQuickActionItem
      }
    }
    

Raw manifest:https://uipkge.dev/r/lit/quick-actions.json