UNPKG

@ryanhelsing/ry-ui

Version:

Framework-agnostic, Light DOM web components. CSS is the source of truth.

1,440 lines (1,317 loc) 62.2 kB
<ry-page> <ry-header sticky> <ry-cluster> <strong>ry-ui</strong> <span>Kitchen Sink</span> </ry-cluster> </ry-header> <ry-theme-panel></ry-theme-panel> <ry-main> <!-- Sticky --> <ry-section> <h2>Sticky</h2> <ry-example title="sticky" stacked> <template> <div style="height: 150px; overflow-y: auto; border: 1px solid var(--ry-color-border); border-radius: var(--ry-radius-lg);"> <header sticky style="background: var(--ry-color-bg); padding: var(--ry-space-3); border-bottom: 1px solid var(--ry-color-border);"> <strong>Sticky header</strong> </header> <stack style="padding: var(--ry-space-4);"> <p>Scroll down...</p> <p>Keep scrolling...</p> <p>The header sticks!</p> <p>More content here...</p> <p>Almost there...</p> <p>Bottom reached.</p> </stack> </div> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>sticky</code></td><td>boolean</td><td>Sticks to top of scroll container</td></tr> </tbody> </table> </ry-section> <!-- Grid --> <ry-section> <h2>Grid</h2> <ry-example title="grid"> <template> <grid cols="3"> <card>One</card> <card>Two</card> <card>Three</card> </grid> </template> </ry-example> <ry-example title="grid auto-fit"> <template> <grid cols="auto-fit" style="--ry-grid-min: 200px"> <card>Auto</card> <card>Fit</card> <card>Cards</card> <card>Wrap</card> <card>Fluidly</card> </grid> </template> </ry-example> <ry-example title="grid responsive breakpoints"> <template> <grid cols="5" cols-md="3" cols-sm="1"> <card>1</card> <card>2</card> <card>3</card> <card>4</card> <card>5</card> </grid> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>cols</code></td><td>1-6 | auto-fit | auto-fill</td><td>Number of columns or fluid mode</td></tr> <tr><td><code>cols-sm</code></td><td>1-3</td><td>Columns at ≤640px</td></tr> <tr><td><code>cols-md</code></td><td>1-4</td><td>Columns at 641-1024px</td></tr> <tr><td><code>cols-lg</code></td><td>2-6</td><td>Columns at ≥1025px</td></tr> <tr><td><code>--ry-grid-min</code></td><td>CSS length</td><td>Min column width for auto-fit/auto-fill (default 280px)</td></tr> </tbody> </table> </ry-section> <!-- Stack --> <ry-section> <h2>Stack</h2> <ry-example title="stack"> <template> <stack gap="sm"> <card>Stacked item 1</card> <card>Stacked item 2</card> <card>Stacked item 3</card> </stack> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>gap</code></td><td>sm | md | lg</td><td>Vertical spacing</td></tr> </tbody> </table> </ry-section> <!-- Cluster --> <ry-section> <h2>Cluster</h2> <ry-example title="cluster"> <template> <cluster> <badge>Tag 1</badge> <badge>Tag 2</badge> <badge>Tag 3</badge> <badge>Tag 4</badge> </cluster> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>gap</code></td><td>sm | md | lg</td><td>Horizontal spacing</td></tr> </tbody> </table> </ry-section> <!-- Card --> <ry-section> <h2>Card</h2> <ry-example title="card"> <template> <card> <h3>Card Title</h3> <p>Card content goes here.</p> <actions> <button>Action</button> <button variant="ghost">Cancel</button> </actions> </card> </template> </ry-example> <ry-example title="interactive cards" stacked> <template> <grid cols="3"> <card interactive> <h3>Hover Me</h3> <p>All cards lift on hover. Interactive cards get a stronger effect with primary border.</p> </card> <card interactive href="#card-demo"> <h3>With Link</h3> <p>Interactive cards with <code>href</code> navigate on click or Enter key.</p> </card> <card interactive> <h3>Keyboard Accessible</h3> <p>Tab to focus, Enter or Space to activate. Emits <code>ry:click</code>.</p> </card> </grid> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>interactive</code></td><td>boolean</td><td>Clickable card with hover lift, focus ring, keyboard support</td></tr> <tr><td><code>href</code></td><td>URL</td><td>Navigate on click (requires <code>interactive</code>)</td></tr> </tbody> </table> </ry-section> <!-- Button --> <ry-section> <h2>Button</h2> <ry-example title="button"> <template> <cluster> <button>Primary</button> <button variant="secondary">Secondary</button> <button variant="outline">Outline</button> <button variant="ghost">Ghost</button> <button variant="danger">Danger</button> <button variant="accent">Accent</button> <button pressed>Pressed</button> </cluster> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>variant</code></td><td>primary | secondary | outline | ghost | danger | accent</td><td>Button style</td></tr> <tr><td><code>size</code></td><td>sm | md | lg</td><td>Button size</td></tr> <tr><td><code>pressed</code></td><td>boolean</td><td>Pressed/active toggle state</td></tr> <tr><td><code>modal</code></td><td>modal-id</td><td>Opens modal on click</td></tr> <tr><td><code>drawer</code></td><td>drawer-id</td><td>Opens drawer on click</td></tr> </tbody> </table> </ry-section> <!-- Split --> <ry-section> <h2>Split</h2> <ry-example title="split" stacked> <template> <split style="--ry-split-width: 200px; height: 120px;"> <card>Main content (flex: 1)</card> <card>Sidebar (200px)</card> </split> </template> </ry-example> <ry-example title="split resizable" stacked> <template> <split resizable style="height: 150px;"> <card>Drag the handle to resize →</card> <card>Resizable sidebar (default 300px)</card> </split> </template> </ry-example> <ry-example title="split resizable + persist" stacked> <template> <split resizable persist="demo-panel" style="height: 150px; --ry-split-width: 250px;"> <card>Width persists across page reloads. Double-click handle to reset.</card> <card>Persisted sidebar</card> </split> </template> <script slot="js" type="text/plain"> const split = document.querySelector('ry-split[resizable]'); // Listen for resize split.addEventListener('ry:resize', (e) => { console.log(e.detail.width); // new width in px }); // Keyboard: Arrow keys (±10px), Shift+Arrow (±50px) // Home = min width, End = max width // Double-click handle = reset to default </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>resizable</code></td><td>boolean</td><td>Enable drag-to-resize handle</td></tr> <tr><td><code>persist</code></td><td>string</td><td>localStorage key — saves width across reloads</td></tr> <tr><td><code>--ry-split-width</code></td><td>CSS length</td><td>Sidebar width (default 300px)</td></tr> <tr><td><code>--ry-split-min-width</code></td><td>CSS length</td><td>Min width during resize (default 100px)</td></tr> <tr><td><code>--ry-split-max-width</code></td><td>CSS length</td><td>Max width during resize (default 80% of container)</td></tr> </tbody> </table> </ry-section> <!-- Check List --> <ry-section> <h2>Check List</h2> <ry-example title="check-list"> <template> <ul class="ry-check-list"> <li>Unlimited projects</li> <li>Priority support</li> <li>Advanced analytics</li> <li>Custom domains</li> </ul> </template> </ry-example> <table> <thead><tr><th>Class</th><th>Description</th></tr></thead> <tbody> <tr><td><code>.ry-check-list</code></td><td>Green checkmark list (CSS-only, no JS)</td></tr> </tbody> </table> </ry-section> <!-- Accordion --> <ry-section> <h2>Accordion</h2> <ry-example title="accordion"> <template> <accordion> <accordion-item title="What is ry-ui?" open> A framework-agnostic, Light DOM component library. </accordion-item> <accordion-item title="Do I need a build step?"> Nope! Just link the CSS and JS files. </accordion-item> </accordion> </template> <script slot="js" type="text/plain"> const accordion = document.querySelector('ry-accordion'); const item = document.querySelector('ry-accordion-item'); // Listen for open/close item.addEventListener('ry:toggle', (e) => { console.log(e.detail.open); // true or false }); // Open/close programmatically item.open = true; // Expand item.open = false; // Collapse // Toggle item.toggle(); </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>title</code></td><td>string</td><td>Header text (on accordion-item)</td></tr> <tr><td><code>open</code></td><td>boolean</td><td>Initially expanded (on accordion-item)</td></tr> </tbody> </table> </ry-section> <!-- Tabs --> <ry-section> <h2>Tabs</h2> <ry-example title="tabs"> <template> <tabs> <tab title="Overview" active> <p>This is the overview tab content.</p> </tab> <tab title="Features"> <p>This is the features tab content.</p> </tab> </tabs> </template> <script slot="js" type="text/plain"> const tabs = document.querySelector('ry-tabs'); // Listen for tab changes tabs.addEventListener('ry:change', (e) => { console.log(e.detail.index); // 0, 1, 2... console.log(e.detail.title); // "Overview" }); // Switch tabs programmatically tabs.activeIndex = 1; // Switch to second tab </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>title</code></td><td>string</td><td>Tab label (on tab)</td></tr> <tr><td><code>active</code></td><td>boolean</td><td>Initially selected (on tab)</td></tr> </tbody> </table> </ry-section> <!-- Modal --> <ry-section> <h2>Modal</h2> <ry-example title="modal"> <template> <button modal="example-modal">Open Modal</button> <modal id="example-modal" title="Modal Title"> <p>Modal content with focus trapping.</p> <footer> <button variant="ghost" close>Cancel</button> <button>Confirm</button> </footer> </modal> </template> <script slot="js" type="text/plain"> const modal = document.querySelector('ry-modal'); // Listen for open/close modal.addEventListener('ry:open', () => { console.log('Modal opened'); }); modal.addEventListener('ry:close', () => { console.log('Modal closed'); }); // Open/close programmatically modal.open(); modal.close(); // Check state if (modal.state === 'open') { ... } </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>id</code></td><td>string</td><td>Modal identifier</td></tr> <tr><td><code>title</code></td><td>string</td><td>Header title</td></tr> <tr><td><code>close</code></td><td>boolean</td><td>Closes modal (on button)</td></tr> </tbody> </table> </ry-section> <!-- Dropdown --> <ry-section> <h2>Dropdown</h2> <ry-example title="dropdown"> <template> <dropdown> <button>Options ▾</button> <menu> <menu-item>Edit</menu-item> <menu-item>Duplicate</menu-item> <divider></divider> <menu-item>Delete</menu-item> </menu> </dropdown> </template> <script slot="js" type="text/plain"> const dropdown = document.querySelector('ry-dropdown'); // Listen for menu item selection dropdown.addEventListener('ry:select', (e) => { console.log(e.detail.value); // "Edit", "Delete", etc. }); // Open/close programmatically dropdown.open(); dropdown.close(); </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td>first child</td><td>—</td><td>First child element is the trigger</td></tr> </tbody> </table> </ry-section> <!-- Field --> <ry-section> <h2>Field</h2> <ry-example title="field"> <template> <stack> <field label="Email" hint="We'll never share your email"> <input type="email" placeholder="you@example.com"> </field> <field label="Password" error="Must be at least 8 characters"> <input type="password" placeholder="Enter password"> </field> <field label="Message"> <textarea rows="3" placeholder="Your message..."></textarea> </field> </stack> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>label</code></td><td>string</td><td>Field label text</td></tr> <tr><td><code>error</code></td><td>string</td><td>Error message (hides hint when set)</td></tr> <tr><td><code>hint</code></td><td>string</td><td>Helper text below input</td></tr> </tbody> </table> </ry-section> <!-- Checkbox --> <ry-section> <h2>Checkbox</h2> <ry-example title="checkbox"> <template> <stack gap="sm"> <label> <input type="checkbox" checked> Accept terms and conditions </label> <label> <input type="checkbox"> Subscribe to newsletter </label> <label> <input type="checkbox" disabled> Disabled option </label> </stack> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>checked</code></td><td>boolean</td><td>Initially checked</td></tr> <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr> </tbody> </table> </ry-section> <!-- Radio --> <ry-section> <h2>Radio</h2> <ry-example title="radio"> <template> <stack gap="sm"> <label> <input type="radio" name="plan" value="free" checked> Free plan </label> <label> <input type="radio" name="plan" value="pro"> Pro plan </label> <label> <input type="radio" name="plan" value="enterprise"> Enterprise </label> <label> <input type="radio" name="plan" value="disabled" disabled> Disabled </label> </stack> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>name</code></td><td>string</td><td>Groups radios together</td></tr> <tr><td><code>value</code></td><td>string</td><td>Value when selected</td></tr> <tr><td><code>checked</code></td><td>boolean</td><td>Initially selected</td></tr> <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr> </tbody> </table> </ry-section> <!-- Badge --> <ry-section> <h2>Badge</h2> <ry-example title="badge"> <template> <cluster> <badge>Default</badge> <badge variant="primary">Primary</badge> <badge variant="success">Success</badge> <badge variant="warning">Warning</badge> <badge variant="danger">Danger</badge> <badge variant="accent">Accent</badge> <badge style="--ry-badge-color: oklch(0.6 0.2 150)">Custom Color</badge> </cluster> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>variant</code></td><td>default | primary | success | warning | danger | accent</td><td>Badge style</td></tr> <tr><td><code>--ry-badge-color</code></td><td>CSS color</td><td>Arbitrary background color via style</td></tr> <tr><td><code>--ry-badge-text</code></td><td>CSS color</td><td>Arbitrary text color via style</td></tr> </tbody> </table> </ry-section> <!-- Alert --> <ry-section> <h2>Alert</h2> <ry-example title="alert"> <template> <stack> <alert type="info" title="Info"> This is an informational message. </alert> <alert type="success" title="Success"> Operation completed successfully. </alert> <alert type="warning" title="Warning"> Please review before continuing. </alert> <alert type="danger" title="Error"> Something went wrong. </alert> </stack> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>type</code></td><td>info | success | warning | danger</td><td>Alert style</td></tr> <tr><td><code>title</code></td><td>string</td><td>Alert heading</td></tr> </tbody> </table> </ry-section> <!-- Switch --> <ry-section> <h2>Switch</h2> <ry-example title="switch"> <template> <stack gap="sm"> <switch name="notifications" checked>Enable notifications</switch> <switch name="darkMode">Dark mode</switch> <switch disabled>Disabled switch</switch> </stack> </template> <script slot="js" type="text/plain"> const toggle = document.querySelector('ry-switch'); // Listen for changes toggle.addEventListener('ry:change', (e) => { console.log(e.detail.checked); // true or false console.log(e.detail.name); // "notifications" }); // Get/set programmatically toggle.checked; // true toggle.checked = false; // Turn off // Form usage with hidden input const hidden = document.querySelector('input[name="notifications"]'); toggle.addEventListener('ry:change', (e) => { hidden.value = e.detail.checked ? '1' : '0'; }); </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>name</code></td><td>string</td><td>Form field name</td></tr> <tr><td><code>checked</code></td><td>boolean</td><td>Initially checked</td></tr> <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr> </tbody> </table> </ry-section> <!-- Slider --> <ry-section> <h2>Slider</h2> <ry-example title="slider" stacked> <template> <stack gap="lg"> <slider min="0" max="100" value="50"></slider> <slider min="0" max="100" start="25" end="75" range labeled></slider> <slider min="0" max="10" step="1" value="5" ticked labeled tooltip></slider> <stack gap="sm"> <slider min="0" max="100" value="60" color="success"></slider> <slider min="0" max="100" value="60" color="warning"></slider> <slider min="0" max="100" value="60" color="danger"></slider> <slider min="0" max="100" value="60" color="info"></slider> </stack> <slider min="0" max="100" value="50" disabled></slider> </stack> </template> <script slot="js" type="text/plain"> const slider = document.querySelector('ry-slider'); // Listen for changes (fires during drag) slider.addEventListener('ry:input', (e) => { console.log(e.detail.value); // 50 (single slider) // For range slider: // e.detail.start, e.detail.end }); // Listen for final value (fires on mouseup) slider.addEventListener('ry:change', (e) => { saveVolume(e.detail.value); }); // Get/set programmatically slider.value; // 50 slider.value = 75; // Update value // Range slider rangeSlider.start; // 25 rangeSlider.end; // 75 rangeSlider.start = 10; </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>min</code></td><td>number</td><td>Minimum value (default: 0)</td></tr> <tr><td><code>max</code></td><td>number</td><td>Maximum value (default: 100)</td></tr> <tr><td><code>step</code></td><td>number</td><td>Step increment, 0 for smooth (default: 1)</td></tr> <tr><td><code>value</code></td><td>number</td><td>Current value (single slider)</td></tr> <tr><td><code>start</code> / <code>end</code></td><td>number</td><td>Range values (with range attribute)</td></tr> <tr><td><code>range</code></td><td>boolean</td><td>Enable dual-handle range mode</td></tr> <tr><td><code>labeled</code></td><td>boolean</td><td>Show value labels</td></tr> <tr><td><code>ticked</code></td><td>boolean</td><td>Show tick marks</td></tr> <tr><td><code>tooltip</code></td><td>boolean</td><td>Show value on thumb hover</td></tr> <tr><td><code>vertical</code></td><td>boolean</td><td>Vertical orientation</td></tr> <tr><td><code>reversed</code></td><td>boolean</td><td>Reverse direction</td></tr> <tr><td><code>color</code></td><td>primary | secondary | success | warning | danger | info</td><td>Track color</td></tr> <tr><td><code>size</code></td><td>sm | lg</td><td>Size variant</td></tr> <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr> </tbody> </table> </ry-section> <!-- Knob --> <ry-section> <h2>Knob</h2> <ry-example title="knob" stacked> <template> <stack gap="lg"> <cluster> <knob min="0" max="100" value="50" label="Volume" description="Master output volume"></knob> <knob min="0" max="100" value="75" label="Pan" description="Left/right stereo position"></knob> <knob min="0" max="100" value="25" label="Reverb" description="Room reverb amount"></knob> </cluster> <cluster> <knob min="0" max="3" step="1" value="1" labels="Off,Low,Med,High" label="Mode"></knob> <knob min="0" max="4" step="1" value="2" labels="∿,△,▢,▲,◇" label="Wave"></knob> </cluster> <cluster> <knob value="60" color="success" label="Success"></knob> <knob value="60" color="warning" label="Warning"></knob> <knob value="60" color="danger" label="Danger"></knob> <knob value="60" color="secondary" label="Secondary"></knob> </cluster> <cluster> <knob size="sm" value="25" label="Small"></knob> <knob value="50" label="Default"></knob> <knob size="lg" value="75" label="Large"></knob> </cluster> <knob min="0" max="100" value="50" label="Disabled" disabled></knob> </stack> </template> <script slot="js" type="text/plain"> const knob = document.querySelector('ry-knob'); // Listen for changes (fires during drag) knob.addEventListener('ry:input', (e) => { console.log(e.detail.value); // 0-100 updateVolume(e.detail.value); }); // Listen for final value (fires on release) knob.addEventListener('ry:change', (e) => { saveSettings({ volume: e.detail.value }); }); // Get/set programmatically knob.value; // 50 knob.value = 75; // Update value // For discrete knobs with labels modeKnob.value; // 1 (index) // Labels: "Off,Low,Med,High" → value 1 = "Low" </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>min</code></td><td>number</td><td>Minimum value (default: 0)</td></tr> <tr><td><code>max</code></td><td>number</td><td>Maximum value (default: 100)</td></tr> <tr><td><code>step</code></td><td>number</td><td>Step increment, 0 for smooth (default: 0)</td></tr> <tr><td><code>value</code></td><td>number</td><td>Current value</td></tr> <tr><td><code>label</code></td><td>string</td><td>Label text below knob</td></tr> <tr><td><code>labels</code></td><td>string</td><td>Comma-separated display labels for discrete values</td></tr> <tr><td><code>description</code></td><td>string</td><td>Tooltip text shown on hover</td></tr> <tr><td><code>color</code></td><td>primary | secondary | success | warning | danger</td><td>Track color</td></tr> <tr><td><code>size</code></td><td>sm | lg</td><td>Size variant</td></tr> <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr> </tbody> </table> </ry-section> <!-- Number Select --> <ry-section> <h2>Number Select</h2> <ry-example title="number-select" stacked> <template> <ry-stack gap="xl"> <p><strong>Arrow placement</strong></p> <ry-cluster gap="lg"> <ry-number-select min="0" max="100" value="50"></ry-number-select> <ry-number-select min="0" max="100" value="50" arrows="start"></ry-number-select> <ry-number-select min="0" max="100" value="50" arrows="end"></ry-number-select> <ry-number-select min="0" max="100" value="50" arrows="none"></ry-number-select> <ry-number-select min="0" max="100" value="50" arrows="stacked"></ry-number-select> <ry-number-select min="0" max="100" value="50" arrows="stacked-end" icons="chevron"></ry-number-select> <ry-number-select min="0" max="100" value="50" arrows="stacked-start" icons="chevron"></ry-number-select> </ry-cluster> <p><strong>Icon styles</strong></p> <ry-cluster gap="lg"> <ry-number-select min="0" max="100" value="50"></ry-number-select> <ry-number-select min="0" max="100" value="50" icons="chevron"></ry-number-select> <ry-number-select min="0" max="100" value="50" icons="arrow"></ry-number-select> <ry-number-select min="0" max="100" value="50" icons="chevron" arrows="stacked"></ry-number-select> <ry-number-select min="0" max="100" value="50" icons="arrow" arrows="stacked"></ry-number-select> </ry-cluster> <p><strong>Drag direction</strong> — stacked defaults to vertical</p> <ry-cluster gap="lg"> <ry-number-select min="0" max="100" value="50"></ry-number-select> <ry-number-select min="0" max="100" value="50" drag="y"></ry-number-select> <ry-number-select min="0" max="100" value="50" drag="none"></ry-number-select> </ry-cluster> <p><strong>Prefix &amp; suffix</strong></p> <ry-cluster gap="lg"> <ry-number-select min="0" max="1000" value="50" prefix="$"></ry-number-select> <ry-number-select min="0" max="360" value="90" suffix="°" icons="chevron" arrows="stacked"></ry-number-select> <ry-number-select min="0" max="100" value="75" suffix="%"></ry-number-select> <ry-number-select min="0" max="100" value="50" prefix="$" suffix="USD" editable></ry-number-select> </ry-cluster> <p><strong>Step intervals</strong></p> <ry-cluster gap="lg"> <ry-number-select min="0" max="100" value="50" step="1"></ry-number-select> <ry-number-select min="0" max="100" value="50" step="5"></ry-number-select> <ry-number-select min="0" max="10" value="5" step="0.1"></ry-number-select> <ry-number-select min="0" max="1" value="0.5" step="0.01"></ry-number-select> </ry-cluster> <p><strong>Behaviors</strong></p> <ry-cluster gap="lg"> <ry-number-select min="0" max="100" value="50" editable></ry-number-select> <ry-number-select min="0" max="7" value="3" wrap></ry-number-select> <ry-number-select min="0" max="100" value="50" disabled></ry-number-select> </ry-cluster> <p><strong>Sizes</strong></p> <ry-cluster gap="lg" style="align-items: center"> <ry-number-select min="0" max="100" value="50" size="xs"></ry-number-select> <ry-number-select min="0" max="100" value="50" size="sm"></ry-number-select> <ry-number-select min="0" max="100" value="50"></ry-number-select> <ry-number-select min="0" max="100" value="50" size="lg"></ry-number-select> </ry-cluster> </ry-stack> </template> <script slot="js" type="text/plain"> const ns = document.querySelector('ry-number-select'); // Events — ry:input fires during drag, ry:change on commit ns.addEventListener('ry:input', (e) => { console.log('input:', e.detail.value); }); ns.addEventListener('ry:change', (e) => { console.log('change:', e.detail.value); }); // Programmatic API ns.value; // 50 ns.value = 75; ns.min = 10; ns.step = 5; ns.drag = 'y'; // switch drag direction ns.disabled = true; </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>min</code></td><td>number</td><td>Minimum value (default: 0)</td></tr> <tr><td><code>max</code></td><td>number</td><td>Maximum value (default: 100)</td></tr> <tr><td><code>step</code></td><td>number</td><td>Step increment (default: 1)</td></tr> <tr><td><code>value</code></td><td>number</td><td>Current value (default: 0)</td></tr> <tr><td><code>arrows</code></td><td>both | start | end | stacked | none</td><td>Button placement (default: both)</td></tr> <tr><td><code>icons</code></td><td>plus-minus | chevron | arrow</td><td>Button icon style (default: plus-minus)</td></tr> <tr><td><code>drag</code></td><td>x | y | none</td><td>Drag direction (default: x, stacked defaults to y)</td></tr> <tr><td><code>prefix</code></td><td>string</td><td>Text before value (e.g. "$")</td></tr> <tr><td><code>suffix</code></td><td>string</td><td>Text after value (e.g. "°", "%")</td></tr> <tr><td><code>editable</code></td><td>boolean</td><td>Allow direct typing on click</td></tr> <tr><td><code>wrap</code></td><td>boolean</td><td>Wrap around min/max boundaries</td></tr> <tr><td><code>size</code></td><td>xs | sm | lg</td><td>Size variant</td></tr> <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr> </tbody> </table> </ry-section> <!-- Color Picker --> <ry-section> <h2>Color Picker</h2> <ry-example title="color-picker" stacked> <template> <stack gap="lg"> <p><strong>Dropdown mode (default)</strong> - click to open:</p> <cluster> <color-picker value="#3b82f6"></color-picker> <color-picker value="#22c55e"></color-picker> <color-picker value="#8b5cf6"></color-picker> <color-picker value="#ef4444" opacity></color-picker> </cluster> <p><strong>With swatches:</strong></p> <color-picker value="#3b82f6" swatches="#ef4444;#f59e0b;#22c55e;#3b82f6;#8b5cf6;#ec4899;#1e293b;#ffffff" ></color-picker> <p><strong>Inline mode</strong> - always visible:</p> <color-picker value="#3b82f6" inline></color-picker> <p><strong>With opacity + swatches (inline):</strong></p> <color-picker value="rgba(59, 130, 246, 0.7)" format="rgb" opacity inline swatches="#ef4444;#f59e0b;#22c55e;#3b82f6;#8b5cf6;#ec4899" ></color-picker> <cluster> <color-picker value="#64748b" disabled></color-picker> <span style="color: var(--ry-color-text-muted)">Disabled</span> </cluster> </stack> </template> <script slot="js" type="text/plain"> // Get the element const picker = document.querySelector('ry-color-picker'); // Listen for changes (fires during drag) picker.addEventListener('ry:input', (e) => { console.log(e.detail.value); // "#3b82f6" console.log(e.detail.rgb); // { r: 59, g: 130, b: 246 } console.log(e.detail.hsv); // { h: 217, s: 76, v: 96 } }); // Listen for final value (fires on mouseup/blur) picker.addEventListener('ry:change', (e) => { saveColor(e.detail.value); }); // Get/set programmatically picker.value; // "#3b82f6" picker.value = '#ff0000'; // Set new color picker.setColor('hsl(200, 100%, 50%)'); // Accepts any format // Access color in different formats picker.rgb; // { r: 59, g: 130, b: 246 } picker.hsl; // { h: 217, s: 89, l: 60 } picker.hsv; // { h: 217, s: 76, v: 96 } // Form submission form.addEventListener('submit', (e) => { const color = picker.value; fetch('/api/save', { body: JSON.stringify({ color }) }); }); </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>value</code></td><td>color string</td><td>Initial color (hex, rgb, hsl)</td></tr> <tr><td><code>format</code></td><td>hex | rgb | hsl</td><td>Output format (default: hex)</td></tr> <tr><td><code>inline</code></td><td>boolean</td><td>Always show picker (no dropdown)</td></tr> <tr><td><code>opacity</code></td><td>boolean</td><td>Enable alpha channel slider</td></tr> <tr><td><code>swatches</code></td><td>string</td><td>Preset colors separated by semicolons</td></tr> <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr> </tbody> </table> </ry-section> <!-- Color Input --> <ry-section> <h2>Color Input</h2> <ry-example title="color-input" stacked> <template> <stack gap="lg"> <p>Input-style color picker with editable hex value:</p> <cluster> <color-input value="#3b82f6"></color-input> <color-input value="#22c55e"></color-input> <color-input value="#ef4444"></color-input> </cluster> <p>With opacity support:</p> <color-input value="rgba(139, 92, 246, 0.8)" opacity></color-input> <cluster> <color-input value="#64748b" disabled></color-input> <span style="color: var(--ry-color-text-muted)">Disabled</span> </cluster> </stack> </template> <script slot="js" type="text/plain"> const input = document.querySelector('ry-color-input'); // Listen for changes input.addEventListener('ry:change', (e) => { console.log(e.detail.value); // "#3b82f6" }); // Get/set programmatically input.value; // "#3b82f6" input.value = '#ff0000'; // Works great in forms const hidden = document.querySelector('input[name="brandColor"]'); input.addEventListener('ry:change', (e) => { hidden.value = e.detail.value; }); </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>value</code></td><td>color string</td><td>Initial color (hex, rgb, hsl)</td></tr> <tr><td><code>format</code></td><td>hex | rgb | hsl</td><td>Output format (default: hex)</td></tr> <tr><td><code>opacity</code></td><td>boolean</td><td>Enable alpha channel slider</td></tr> <tr><td><code>placeholder</code></td><td>string</td><td>Placeholder text for input</td></tr> <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr> </tbody> </table> </ry-section> <!-- Gradient Picker --> <ry-section> <h2>Gradient Picker</h2> <ry-example title="gradient-picker" stacked> <template> <stack gap="lg"> <p><strong>Linear gradient</strong> (default):</p> <gradient-picker value="linear-gradient(90deg, #3b82f6 0%, #8b5cf6 100%)"></gradient-picker> <p><strong>Multi-stop rainbow with CSS output:</strong></p> <gradient-picker value="linear-gradient(90deg, #ef4444 0%, #f59e0b 25%, #22c55e 50%, #3b82f6 75%, #8b5cf6 100%)" output></gradient-picker> <p><strong>Radial gradient:</strong></p> <gradient-picker value="radial-gradient(circle, #fbbf24 0%, #f97316 50%, #dc2626 100%)"></gradient-picker> </stack> </template> <script slot="js" type="text/plain"> const picker = document.querySelector('ry-gradient-picker'); // Listen for changes picker.addEventListener('ry:input', (e) => { console.log(e.detail.value); // CSS gradient string console.log(e.detail.stops); // [{ id, color, position }, ...] console.log(e.detail.type); // "linear" | "radial" console.log(e.detail.angle); // 90 (degrees, linear only) }); picker.addEventListener('ry:change', (e) => { applyGradient(e.detail.value); }); // Get/set programmatically picker.value; // "linear-gradient(90deg, ...)" picker.value = 'radial-gradient(circle, #ff0000 0%, #0000ff 100%)'; picker.type; // "linear" | "radial" picker.angle; // 0-360 picker.stops; // [{ id, color, position }, ...] // Add/remove stops picker.addStop('#ff00ff', 50); picker.removeStop('stop-2'); </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>value</code></td><td>CSS gradient string</td><td>Initial gradient value</td></tr> <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr> </tbody> </table> </ry-section> <!-- Toggle Button --> <ry-section> <h2>Toggle Button</h2> <ry-example title="toggle-button" stacked> <template> <stack gap="lg"> <grid cols="3"> <toggle-button name="plan" value="bronze" block> <strong>Bronze</strong><br> <small>Standard Network</small><br> <big><b>$85</b>/month</big><br> <small>Deductible: $6,000 · Coverage: 60%</small><br> <small>✓ Preventive care ✓ Generic Rx ✓ Telehealth</small> </toggle-button> <toggle-button name="plan" value="silver" block> <strong>Silver</strong><br> <small>Enhanced Network</small><br> <big><b>$145</b>/month</big><br> <small>Deductible: $3,000 · Coverage: 80%</small><br> <small>✓ Lower deductible ✓ Specialists ✓ Mental health</small> </toggle-button> <toggle-button name="plan" value="gold" block> <strong>Gold</strong><br> <small>Premium Network</small><br> <big><b>$220</b>/month</big><br> <small>Deductible: $1,000 · Coverage: 90%</small><br> <small>✓ Lowest out-of-pocket ✓ Full Rx ✓ No referrals</small> </toggle-button> </grid> <cluster> <toggle-button name="coverage" value="medical" pressed>🏥 Medical</toggle-button> <toggle-button name="coverage" value="dental">🦷 Dental</toggle-button> <toggle-button name="coverage" value="vision">👁 Vision</toggle-button> </cluster> <stack> <grid cols="2"> <toggle-button name="selection" value="ppo200" block> <strong>Blue PPO $200</strong><br> <small>Employee Only · $200 Deductible</small><br> <b>$171.28</b>/month </toggle-button> <toggle-button name="selection" value="ppo500" block> <strong>Blue PPO $500</strong><br> <small>Employee Only · $500 Deductible</small><br> <b>$142.53</b>/month </toggle-button> </grid> <toggle-button name="selection" value="waive" pressed>Waive $0/month</toggle-button> </stack> </stack> </template> <script slot="js" type="text/plain"> const buttons = document.querySelectorAll('ry-toggle-button[name="plan"]'); // Listen for changes on each button buttons.forEach(btn => { btn.addEventListener('ry:change', (e) => { console.log(e.detail.pressed); // true or false console.log(e.detail.value); // "bronze", "silver", etc. }); }); // Get selected value from a group function getSelectedPlan() { const pressed = document.querySelector( 'ry-toggle-button[name="plan"][pressed]' ); return pressed?.value; // "gold" } // Set programmatically button.pressed = true; // Form submission form.addEventListener('submit', (e) => { const plan = getSelectedPlan(); fetch('/api/enroll', { body: JSON.stringify({ plan }) }); }); </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>name</code></td><td>string</td><td>Groups buttons (same name = only one pressed)</td></tr> <tr><td><code>value</code></td><td>string</td><td>Value when selected</td></tr> <tr><td><code>pressed</code></td><td>boolean</td><td>Currently pressed</td></tr> <tr><td><code>disabled</code></td><td>boolean</td><td>Disable interaction</td></tr> <tr><td><code>size</code></td><td>sm | md | lg</td><td>Button size</td></tr> </tbody> </table> </ry-section> <!-- Tooltip --> <ry-section> <h2>Tooltip</h2> <ry-example title="tooltip"> <template> <cluster> <tooltip content="Top tooltip" position="top"> <button>Top</button> </tooltip> <tooltip content="Bottom tooltip" position="bottom"> <button>Bottom</button> </tooltip> <tooltip content="Left tooltip" position="left"> <button>Left</button> </tooltip> <tooltip content="Right tooltip" position="right"> <button>Right</button> </tooltip> </cluster> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>content</code></td><td>string</td><td>Tooltip text</td></tr> <tr><td><code>position</code></td><td>top | bottom | left | right</td><td>Tooltip position</td></tr> </tbody> </table> </ry-section> <!-- Drawer --> <ry-section> <h2>Drawer</h2> <ry-example title="drawer"> <template> <button drawer="example-drawer">Open Drawer</button> <drawer id="example-drawer" side="right"> <h3>Drawer Title</h3> <p>Drawer content here.</p> </drawer> </template> <script slot="js" type="text/plain"> const drawer = document.querySelector('ry-drawer'); // Listen for open/close drawer.addEventListener('ry:open', () => { console.log('Drawer opened'); }); drawer.addEventListener('ry:close', () => { console.log('Drawer closed'); }); // Open/close programmatically drawer.open(); drawer.close(); // Check state if (drawer.state === 'open') { ... } </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>id</code></td><td>string</td><td>Drawer identifier</td></tr> <tr><td><code>side</code></td><td>left | right | bottom</td><td>Slide direction</td></tr> </tbody> </table> </ry-section> <!-- Toast --> <ry-section> <h2>Toast</h2> <ry-example title="toast"> <template> <cluster> <button onclick="RyToast.success('Saved!')">Success</button> <button onclick="RyToast.error('Failed!')">Error</button> <button onclick="RyToast.warning('Warning!')">Warning</button> <button onclick="RyToast.info('Info!')">Info</button> </cluster> </template> <script slot="js" type="text/plain"> // Show toasts (global API) RyToast.success('Changes saved!'); RyToast.error('Something went wrong'); RyToast.warning('Please review'); RyToast.info('New update available'); // With options RyToast.success('Saved!', { duration: 5000, // ms (default: 3000) }); // Common patterns async function saveData() { try { await api.save(data); RyToast.success('Saved!'); } catch (err) { RyToast.error(err.message); } } </script> </ry-example> <table> <thead><tr><th>Method</th><th>Description</th></tr></thead> <tbody> <tr><td><code>RyToast.success(msg)</code></td><td>Show success toast</td></tr> <tr><td><code>RyToast.error(msg)</code></td><td>Show error toast</td></tr> <tr><td><code>RyToast.warning(msg)</code></td><td>Show warning toast</td></tr> <tr><td><code>RyToast.info(msg)</code></td><td>Show info toast</td></tr> </tbody> </table> </ry-section> <!-- Select --> <ry-section> <h2>Select</h2> <ry-example title="select"> <template> <select placeholder="Choose a country" name="country"> <option value="us">United States</option> <option value="uk">United Kingdom</option> <option value="ca">Canada</option> <option value="au">Australia</option> </select> </template> <script slot="js" type="text/plain"> const select = document.querySelector('ry-select'); // Listen for selection changes select.addEventListener('ry:change', (e) => { console.log(e.detail.value); // "us" console.log(e.detail.label); // "United States" }); // Get/set programmatically select.value; // "us" select.value = 'uk'; // Select United Kingdom // Open/close the dropdown select.open(); select.close(); // Form integration const formData = new FormData(form); formData.get('country'); // "us" </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>placeholder</code></td><td>string</td><td>Placeholder text</td></tr> <tr><td><code>name</code></td><td>string</td><td>Form field name</td></tr> <tr><td><code>disabled</code></td><td>boolean</td><td>Disable select (on option)</td></tr> <tr><td><code>multiple</code></td><td>boolean</td><td>Enable multi-select with tags</td></tr> <tr><td><code>clearable</code></td><td>boolean</td><td>Show clear button (multi)</td></tr> <tr><td><code>max-selections</code></td><td>number</td><td>Max selections (multi)</td></tr> </tbody> </table> <h3 style="margin-top: var(--ry-space-6);">Multi-Select</h3> <ry-example title="select-multi" stacked> <template> <select multiple clearable placeholder="Choose countries..."> <option value="us">United States</option> <option value="uk">United Kingdom</option> <option value="ca">Canada</option> <option value="au">Australia</option> <option value="de">Germany</option> <option value="fr">France</option> </select> </template> </ry-example> </ry-section> <!-- Combobox --> <ry-section> <h2>Combobox</h2> <ry-example title="combobox"> <template> <combobox placeholder="Search countries..." name="country"> <option value="us">United States</option> <option value="uk">United Kingdom</option> <option value="ca">Canada</option> <option value="au">Australia</option> <option value="de">Germany</option> <option value="fr">France</option> <option value="jp">Japan</option> <option value="br">Brazil</option> </combobox> </template> <script slot="js" type="text/plain"> const combobox = document.querySelector('ry-combobox'); // Listen for selection combobox.addEventListener('ry:change', (e) => { console.log(e.detail.value); // "us" console.log(e.detail.label); // "United States" }); // Get/set programmatically combobox.value; // "us" combobox.value = 'uk'; // Select United Kingdom // Form integration const formData = new FormData(form); formData.get('country'); // "us" </script> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>placeholder</code></td><td>string</td><td>Placeholder text for input</td></tr> <tr><td><code>name</code></td><td>string</td><td>Form field name</td></tr> <tr><td><code>value</code></td><td>string</td><td>Currently selected value</td></tr> <tr><td><code>disabled</code></td><td>boolean</td><td>Disable combobox</td></tr> </tbody> </table> </ry-section> <!-- Code --> <ry-section> <h2>Code</h2> <ry-example title="ry-code"> <template> <ry-code language="js" title="app.js"> const greeting = "Hello!"; console.log(greeting); </ry-code> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>language</code></td><td>js | css | html | json</td><td>Syntax highlighting</td></tr> <tr><td><code>title</code></td><td>string</td><td>Header title</td></tr> <tr><td><code>line-numbers</code></td><td>boolean</td><td>Show line numbers</td></tr> </tbody> </table> <p><em>Note: Use <code>&lt;ry-code&gt;</code> with prefix to avoid conflict with native <code>&lt;code&gt;</code> tag.</em></p> </ry-section> <!-- Table --> <ry-section> <h2>Table</h2> <ry-example title="table"> <template> <table> <thead> <tr> <th>Name</th> <th>Role</th> <th>Status</th> </tr> </thead> <tbody> <tr> <td>Alice Johnson</td> <td>Engineer</td> <td>Active</td> </tr> <tr> <td>Bob Smith</td> <td>Designer</td> <td>Active</td> </tr> <tr> <td>Carol White</td> <td>Manager</td> <td>Away</td> </tr> </tbody> </table> </template> </ry-example> <table> <thead><tr><th>Attribute</th><th>Values</th><th>Description</th></tr></thead> <tbody> <tr><td><code>data-bordered</code></td><td>boolean</td><td>Add borders to all cells</td></tr> <tr><td><code>data-striped</code></td><td>boolean</td><td>Alternate row backgrounds</td></tr> </tbody> </table> </ry-section> <!-- Tree --> <ry-section> <h2>Tree</h2> <ry-example title="tree" stacked> <template> <tree sortable> <tree-item label="src" open> <tree-item label="app" open>