Files
sigpro-ui/docs/components/indicator.md
2026-04-02 19:31:39 +02:00

21 KiB

Indicator

Indicator component for adding badges, status markers, or notifications to elements. Perfect for showing counts, online status, or alerts.

Tag

Indicator

Props

Prop Type Default Description
badge string | VNode | Signal - Content to display as indicator
badgeClass string '' Additional CSS classes for the badge
class string '' Additional CSS classes for the container
children VNode - Element to attach the indicator to

Live Examples

Basic Indicator

Live Demo

const BasicDemo = () => {
  return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
    Indicator({ value: '3', ui: 'badge-primary' }, [
      Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '📦')
    ]),
    Indicator({ value: '99+', ui: 'badge-secondary' }, [
      Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔')
    ]),
    Indicator({ value: 'New', ui: 'badge-accent' }, [
      Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '✨')
    ])
  ]);
};
$mount(BasicDemo, '#demo-basic');

Online Status Indicator

Live Demo

const StatusDemo = () => {
  return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
    Indicator({ value: '●', ui: 'badge-success badge-xs' }, [
      Div({ class: 'avatar placeholder' }, [
        Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JD')
      ])
    ]),
    Indicator({ value: '●', ui: 'badge-warning badge-xs' }, [
      Div({ class: 'avatar placeholder' }, [
        Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JS')
      ])
    ]),
    Indicator({ value: '●', ui: 'badge-error badge-xs' }, [
      Div({ class: 'avatar placeholder' }, [
        Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'BC')
      ])
    ])
  ]);
};
$mount(StatusDemo, '#demo-status');

Reactive Counter

Live Demo

const ReactiveDemo = () => {
  const count = $(0);
  
  return Div({ class: 'flex flex-col gap-4 items-center' }, [
    Indicator({ 
      badge: () => count() > 0 ? count() : null,
      badgeClass: 'badge-primary'
    }, [
      Button({ 
        class: 'btn btn-lg btn-primary',
        onclick: () => count(count() + 1)
      }, 'Notifications')
    ]),
    Div({ class: 'flex gap-2' }, [
      Button({ 
        class: 'btn btn-sm',
        onclick: () => count(Math.max(0, count() - 1))
      }, 'Decrease'),
      Button({ 
        class: 'btn btn-sm btn-ghost',
        onclick: () => count(0)
      }, 'Clear')
    ])
  ]);
};
$mount(ReactiveDemo, '#demo-reactive');

Shopping Cart

Live Demo

const CartDemo = () => {
  const cart = $([
    { id: 1, name: 'Product 1', price: 29 },
    { id: 2, name: 'Product 2', price: 49 }
  ]);
  
  const addItem = () => {
    const newId = Math.max(...cart().map(i => i.id), 0) + 1;
    cart([...cart(), { id: newId, name: `Product ${newId}`, price: Math.floor(Math.random() * 100) + 10 }]);
    Toast('Item added to cart', 'alert-success', 1500);
  };
  
  const removeItem = (id) => {
    cart(cart().filter(item => item.id !== id));
    Toast('Item removed', 'alert-info', 1500);
  };
  
  const total = () => cart().reduce((sum, item) => sum + item.price, 0);
  
  return Div({ class: 'flex flex-col gap-4' }, [
    Div({ class: 'flex justify-between items-center' }, [
      Indicator({ 
        badge: () => cart().length,
        badgeClass: 'badge-primary'
      }, [
        Button({ class: 'btn', onclick: addItem }, '🛒 Add to Cart')
      ]),
      Span({ class: 'text-lg font-bold' }, () => `Total: $${total()}`)
    ]),
    cart().length === 0 
      ? Div({ class: 'alert alert-soft text-center' }, 'Cart is empty')
      : Div({ class: 'flex flex-col gap-2' }, cart().map(item =>
        Div({ class: 'flex justify-between items-center p-2 bg-base-200 rounded-lg' }, [
          Span({}, item.name),
          Div({ class: 'flex gap-2 items-center' }, [
            Span({ class: 'text-sm font-bold' }, `$${item.price}`),
            Button({ 
              class: 'btn btn-xs btn-ghost btn-circle',
              onclick: () => removeItem(item.id)
            }, '✕')
          ])
        ])
      ))
  ]);
};
$mount(CartDemo, '#demo-cart');

Email Inbox

Live Demo

const InboxDemo = () => {
  const unread = $(3);
  const messages = $([
    { id: 1, from: 'john@example.com', subject: 'Meeting tomorrow', read: false },
    { id: 2, from: 'jane@example.com', subject: 'Project update', read: false },
    { id: 3, from: 'bob@example.com', subject: 'Question about design', read: false },
    { id: 4, from: 'alice@example.com', subject: 'Weekly report', read: true }
  ]);
  
  const markAsRead = (id) => {
    const msg = messages().find(m => m.id === id);
    if (!msg.read) {
      msg.read = true;
      messages([...messages()]);
      unread(unread() - 1);
    }
  };
  
  return Div({ class: 'flex flex-col gap-4' }, [
    Div({ class: 'flex justify-between items-center' }, [
      Indicator({ 
        badge: () => unread(),
        badgeClass: 'badge-primary'
      }, [
        Span({ class: 'text-lg font-bold' }, 'Inbox')
      ]),
      Button({ 
        class: 'btn btn-sm btn-ghost',
        onclick: () => {
          messages().forEach(m => m.read = true);
          messages([...messages()]);
          unread(0);
        }
      }, 'Mark all read')
    ]),
    Div({ class: 'flex flex-col gap-2' }, messages().map(msg =>
      Div({ 
        class: `p-3 rounded-lg cursor-pointer transition-all ${msg.read ? 'bg-base-200 opacity-60' : 'bg-primary/10 border-l-4 border-primary'}`,
        onclick: () => markAsRead(msg.id)
      }, [
        Div({ class: 'font-medium' }, msg.from),
        Div({ class: 'text-sm' }, msg.subject),
        !msg.read && Span({ class: 'badge badge-xs badge-primary mt-1' }, 'New')
      ])
    ))
  ]);
};
$mount(InboxDemo, '#demo-inbox');

Custom Position

Live Demo

const PositionsDemo = () => {
  return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
    Div({ class: 'text-center' }, [
      Div({ class: 'text-xs mb-2' }, 'Top-Left'),
      Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
        Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
      ])
    ]),
    Div({ class: 'text-center' }, [
      Div({ class: 'text-xs mb-2' }, 'Top-Right'),
      Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
        Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
      ])
    ]),
    Div({ class: 'text-center' }, [
      Div({ class: 'text-xs mb-2' }, 'Bottom-Left'),
      Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
        Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
      ])
    ]),
    Div({ class: 'text-center' }, [
      Div({ class: 'text-xs mb-2' }, 'Bottom-Right'),
      Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
        Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
      ])
    ])
  ]);
};
$mount(PositionsDemo, '#demo-positions');

All Variants

Live Demo

const VariantsDemo = () => {
  return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
    Indicator({ badge: '3', badgeClass: 'badge-primary badge-sm' }, [
      Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '📧')
    ]),
    Indicator({ badge: '99+', badgeClass: 'badge-secondary badge-md' }, [
      Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔')
    ]),
    Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
      Div({ class: 'avatar' }, [
        Div({ class: 'w-10 h-10 rounded-full bg-primary' })
      ])
    ]),
    Indicator({ badge: '!', badgeClass: 'badge-error badge-sm' }, [
      Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '⚠️')
    ])
  ]);
};
$mount(VariantsDemo, '#demo-variants');
<script> (function() { const initIndicatorExamples = () => { // 1. Basic Indicator const basicTarget = document.querySelector('#demo-basic'); if (basicTarget && !basicTarget.hasChildNodes()) { const BasicDemo = () => { return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [ Indicator({ badge: '3', badgeClass: 'badge-primary' }, [ Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '📦') ]), Indicator({ badge: '99+', badgeClass: 'badge-secondary' }, [ Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔') ]), Indicator({ badge: 'New', badgeClass: 'badge-accent' }, [ Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '') ]) ]); }; $mount(BasicDemo, basicTarget); } // 2. Online Status Indicator const statusTarget = document.querySelector('#demo-status'); if (statusTarget && !statusTarget.hasChildNodes()) { const StatusDemo = () => { return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [ Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [ Div({ class: 'avatar placeholder' }, [ Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JD') ]) ]), Indicator({ badge: '●', badgeClass: 'badge-warning badge-xs' }, [ Div({ class: 'avatar placeholder' }, [ Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JS') ]) ]), Indicator({ badge: '●', badgeClass: 'badge-error badge-xs' }, [ Div({ class: 'avatar placeholder' }, [ Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'BC') ]) ]) ]); }; $mount(StatusDemo, statusTarget); } // 3. Reactive Counter const reactiveTarget = document.querySelector('#demo-reactive'); if (reactiveTarget && !reactiveTarget.hasChildNodes()) { const ReactiveDemo = () => { const count = $(0); return Div({ class: 'flex flex-col gap-4 items-center' }, [ Indicator({ badge: () => count() > 0 ? count() : null, badgeClass: 'badge-primary' }, [ Button({ class: 'btn btn-lg btn-primary', onclick: () => count(count() + 1) }, 'Notifications') ]), Div({ class: 'flex gap-2' }, [ Button({ class: 'btn btn-sm', onclick: () => count(Math.max(0, count() - 1)) }, 'Decrease'), Button({ class: 'btn btn-sm btn-ghost', onclick: () => count(0) }, 'Clear') ]) ]); }; $mount(ReactiveDemo, reactiveTarget); } // 4. Shopping Cart const cartTarget = document.querySelector('#demo-cart'); if (cartTarget && !cartTarget.hasChildNodes()) { const CartDemo = () => { const cart = $([ { id: 1, name: 'Product 1', price: 29 }, { id: 2, name: 'Product 2', price: 49 } ]); const addItem = () => { const newId = Math.max(...cart().map(i => i.id), 0) + 1; cart([...cart(), { id: newId, name: `Product ${newId}`, price: Math.floor(Math.random() * 100) + 10 }]); Toast('Item added to cart', 'alert-success', 1500); }; const removeItem = (id) => { cart(cart().filter(item => item.id !== id)); Toast('Item removed', 'alert-info', 1500); }; const total = () => cart().reduce((sum, item) => sum + item.price, 0); return Div({ class: 'flex flex-col gap-4' }, [ Div({ class: 'flex justify-between items-center' }, [ Indicator({ badge: () => cart().length, badgeClass: 'badge-primary' }, [ Button({ class: 'btn', onclick: addItem }, '🛒 Add to Cart') ]), Span({ class: 'text-lg font-bold' }, () => `Total: $${total()}`) ]), cart().length === 0 ? Div({ class: 'alert alert-soft text-center' }, 'Cart is empty') : Div({ class: 'flex flex-col gap-2' }, cart().map(item => Div({ class: 'flex justify-between items-center p-2 bg-base-200 rounded-lg' }, [ Span({}, item.name), Div({ class: 'flex gap-2 items-center' }, [ Span({ class: 'text-sm font-bold' }, `$${item.price}`), Button({ class: 'btn btn-xs btn-ghost btn-circle', onclick: () => removeItem(item.id) }, '✕') ]) ]) )) ]); }; $mount(CartDemo, cartTarget); } // 5. Email Inbox const inboxTarget = document.querySelector('#demo-inbox'); if (inboxTarget && !inboxTarget.hasChildNodes()) { const InboxDemo = () => { const unread = $(3); const messages = $([ { id: 1, from: 'john@example.com', subject: 'Meeting tomorrow', read: false }, { id: 2, from: 'jane@example.com', subject: 'Project update', read: false }, { id: 3, from: 'bob@example.com', subject: 'Question about design', read: false }, { id: 4, from: 'alice@example.com', subject: 'Weekly report', read: true } ]); const markAsRead = (id) => { const msg = messages().find(m => m.id === id); if (!msg.read) { msg.read = true; messages([...messages()]); unread(unread() - 1); } }; return Div({ class: 'flex flex-col gap-4' }, [ Div({ class: 'flex justify-between items-center' }, [ Indicator({ badge: () => unread(), badgeClass: 'badge-primary' }, [ Span({ class: 'text-lg font-bold' }, 'Inbox') ]), Button({ class: 'btn btn-sm btn-ghost', onclick: () => { messages().forEach(m => m.read = true); messages([...messages()]); unread(0); } }, 'Mark all read') ]), Div({ class: 'flex flex-col gap-2' }, messages().map(msg => Div({ class: `p-3 rounded-lg cursor-pointer transition-all ${msg.read ? 'bg-base-200 opacity-60' : 'bg-primary/10 border-l-4 border-primary'}`, onclick: () => markAsRead(msg.id) }, [ Div({ class: 'font-medium' }, msg.from), Div({ class: 'text-sm' }, msg.subject), !msg.read && Span({ class: 'badge badge-xs badge-primary mt-1' }, 'New') ]) )) ]); }; $mount(InboxDemo, inboxTarget); } // 6. Custom Position const positionsTarget = document.querySelector('#demo-positions'); if (positionsTarget && !positionsTarget.hasChildNodes()) { const PositionsDemo = () => { return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [ Div({ class: 'text-center' }, [ Div({ class: 'text-xs mb-2' }, 'Top-Left'), Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [ Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' }) ]) ]), Div({ class: 'text-center' }, [ Div({ class: 'text-xs mb-2' }, 'Top-Right'), Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [ Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' }) ]) ]), Div({ class: 'text-center' }, [ Div({ class: 'text-xs mb-2' }, 'Bottom-Left'), Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [ Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' }) ]) ]), Div({ class: 'text-center' }, [ Div({ class: 'text-xs mb-2' }, 'Bottom-Right'), Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [ Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' }) ]) ]) ]); }; $mount(PositionsDemo, positionsTarget); } // 7. All Variants const variantsTarget = document.querySelector('#demo-variants'); if (variantsTarget && !variantsTarget.hasChildNodes()) { const VariantsDemo = () => { return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [ Indicator({ badge: '3', badgeClass: 'badge-primary badge-sm' }, [ Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '📧') ]), Indicator({ badge: '99+', badgeClass: 'badge-secondary badge-md' }, [ Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔') ]), Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [ Div({ class: 'avatar' }, [ Div({ class: 'w-10 h-10 rounded-full bg-primary' }) ]) ]), Indicator({ badge: '!', badgeClass: 'badge-error badge-sm' }, [ Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '⚠️') ]) ]); }; $mount(VariantsDemo, variantsTarget); } }; initIndicatorExamples(); if (window.$docsify) { window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => { hook.doneEach(initIndicatorExamples); }); } })(); </script>