@ryanhelsing/ry-ui 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENT.md CHANGED
@@ -66,6 +66,7 @@ Wrap markup in `<ry>` to use unprefixed tags:
66
66
  | `<ry-tabs>` | — | Container. Children: `<ry-tab title="...">content</ry-tab>` |
67
67
  | `<ry-dropdown>` | — | Dropdown trigger + menu |
68
68
  | `<ry-select>` | `placeholder`, `name`, `value`, `disabled` | `ry:change` `{value}`. Children: `<ry-option value="...">` |
69
+ | `<ry-combobox>` | `placeholder`, `name`, `value`, `disabled` | `ry:change` `{value, label}`, `ry:input` `{value}`. Searchable dropdown — type to filter. Children: `<ry-option value="...">` |
69
70
  | `<ry-switch>` | `checked`, `disabled`, `name` | `ry:change` `{checked}` |
70
71
  | `<ry-tooltip>` | `content`, `position` | Hover tooltip |
71
72
  | `<ry-toast>` | — | Programmatic: `RyToast.success('msg')`, `.error()`, `.warning()`, `.info()` |
@@ -85,7 +86,7 @@ Wrap markup in `<ry>` to use unprefixed tags:
85
86
 
86
87
  | Component | Key Attributes | Description |
87
88
  |-----------|---------------|-------------|
88
- | `<ry-card>` | | Bordered card with padding. Put any content inside |
89
+ | `<ry-card>` | `interactive`, `href` | Card container. All cards lift on hover. `interactive` adds click/keyboard + primary border hover. `href` navigates on click |
89
90
  | `<ry-badge>` | `variant="primary\|success\|warning\|danger\|accent"` | Pill badge. Arbitrary color: `style="--ry-badge-color: #8B5CF6"` |
90
91
  | `<ry-alert>` | `type="info\|success\|warning\|danger"` | Alert box with optional `[slot="title"]` |
91
92
  | `<ry-field>` | `label`, `error`, `hint` | Form field wrapper. See [Forms](#forms) |
@@ -200,6 +201,35 @@ document.querySelector('ry-button-group').addEventListener('ry:change', e => {
200
201
  <ry-button size="lg">Large</ry-button>
201
202
  ```
202
203
 
204
+ ### Interactive Card Grid
205
+
206
+ ```html
207
+ <!-- Clickable card grid (e.g., demo index, dashboard) -->
208
+ <ry-grid cols="3">
209
+ <ry-card interactive href="/demos/goap">
210
+ <h3>GOAP</h3>
211
+ <p>Goal-Oriented Action Planning</p>
212
+ </ry-card>
213
+ <ry-card interactive href="/demos/fsm">
214
+ <h3>FSM</h3>
215
+ <p>Finite State Machine</p>
216
+ </ry-card>
217
+ <ry-card interactive href="/demos/bt">
218
+ <h3>Behavior Trees</h3>
219
+ <p>Hierarchical task planning</p>
220
+ </ry-card>
221
+ </ry-grid>
222
+
223
+ <!-- Listen for clicks -->
224
+ <script>
225
+ document.querySelectorAll('ry-card[interactive]').forEach(card => {
226
+ card.addEventListener('ry:click', () => console.log('clicked', card.getAttribute('href')));
227
+ });
228
+ </script>
229
+ ```
230
+
231
+ All cards hover-lift by default. `interactive` adds: cursor pointer, stronger lift, primary border on hover, keyboard (Tab/Enter/Space), and `ry:click` event.
232
+
203
233
  ### Badge with Arbitrary Color
204
234
 
205
235
  ```html
package/README.md CHANGED
@@ -12,6 +12,16 @@ ry-ui normalizes this. No decisions to make. No architecture to debate. An LLM c
12
12
 
13
13
  **The goal:** Write once in HTML/CSS/JS. Deploy to web, iOS, Android, desktop. The primitives are portable because they're universal.
14
14
 
15
+ ### FUNCTION / FORM / THEME
16
+
17
+ Every component is three independent layers:
18
+
19
+ - **FUNCTION** (JS) — Behavior. Extends `RyElement`. Manages state, events, keyboard, ARIA. Queries via `data-ry-target`.
20
+ - **FORM** (Structure CSS) — Layout. `ry-structure.css`. Display, flex, grid, padding, overflow, transform/opacity transitions. No colors.
21
+ - **THEME** (Visual CSS) — Appearance. `ry-theme.css`. Colors, shadows, borders, typography, focus rings. Entirely swappable.
22
+
23
+ These layers are independently replaceable. Structure works with any theme or no theme. Function works regardless of CSS loaded.
24
+
15
25
  ---
16
26
 
17
27
  - [x] separate the css minimal structure from the theme
@@ -66,6 +76,7 @@ ry-ui normalizes this. No decisions to make. No architecture to debate. An LLM c
66
76
  - `<ry-tabs>` - Tabbed content
67
77
  - `<ry-dropdown>` - Dropdown menus
68
78
  - `<ry-select>` - Custom select
79
+ - `<ry-combobox>` - Searchable dropdown
69
80
  - `<ry-switch>` - Toggle switch
70
81
  - `<ry-tooltip>` - Hover tooltips
71
82
  - `<ry-toast>` - Notifications
@@ -0,0 +1,17 @@
1
+ /**
2
+ * <ry-card>
3
+ *
4
+ * Card container. CSS-only by default.
5
+ * Add `interactive` attribute for clickable cards with href navigation.
6
+ *
7
+ * Usage:
8
+ * <ry-card>Static content</ry-card>
9
+ * <ry-card interactive href="/demos/goap">Clickable card</ry-card>
10
+ * <ry-card interactive>Emits ry:click on click</ry-card>
11
+ */
12
+ import { RyElement } from '../core/ry-element.js';
13
+ export declare class RyCard extends RyElement {
14
+ #private;
15
+ setup(): void;
16
+ }
17
+ //# sourceMappingURL=ry-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ry-card.d.ts","sourceRoot":"","sources":["../../src/ts/components/ry-card.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD,qBAAa,MAAO,SAAQ,SAAS;;IACnC,KAAK,IAAI,IAAI;CA8Bd"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * <ry-combobox>
3
+ *
4
+ * Searchable dropdown with text input filtering.
5
+ *
6
+ * Usage:
7
+ * <ry-combobox placeholder="Search countries..." name="country">
8
+ * <ry-option value="us">United States</ry-option>
9
+ * <ry-option value="uk">United Kingdom</ry-option>
10
+ * </ry-combobox>
11
+ *
12
+ * JS uses data-ry-target for queries, CSS uses classes for styling.
13
+ */
14
+ import { RyElement } from '../core/ry-element.js';
15
+ export declare class RyCombobox extends RyElement {
16
+ #private;
17
+ static observedAttributes: readonly ["value", "disabled"];
18
+ setup(): void;
19
+ open(): void;
20
+ close(): void;
21
+ toggle(): void;
22
+ get value(): string;
23
+ set value(val: string);
24
+ }
25
+ //# sourceMappingURL=ry-combobox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ry-combobox.d.ts","sourceRoot":"","sources":["../../src/ts/components/ry-combobox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAKlD,qBAAa,UAAW,SAAQ,SAAS;;IAKvC,MAAM,CAAC,kBAAkB,iCAAkC;IAE3D,KAAK,IAAI,IAAI;IAmSb,IAAI,IAAI,IAAI;IAsCZ,KAAK,IAAI,IAAI;IAmBb,MAAM,IAAI,IAAI;IAQd,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,KAAK,CAAC,GAAG,EAAE,MAAM,EAKpB;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"ry-transform.d.ts","sourceRoot":"","sources":["../../src/ts/core/ry-transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAsDH;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAgBxC"}
1
+ {"version":3,"file":"ry-transform.d.ts","sourceRoot":"","sources":["../../src/ts/core/ry-transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAuDH;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAgBxC"}
@@ -48,7 +48,7 @@ ry {
48
48
  ry-toggle-button, ry-knob, ry-slider, ry-number-select, ry-color-picker, ry-color-input,
49
49
  ry-gradient-picker, ry-tree, ry-tree-item,
50
50
  ry-tag, ry-tag-input, ry-hero, ry-stat, ry-feature, ry-feature-grid,
51
- ry-pricing, ry-pricing-card, ry-carousel) {
51
+ ry-pricing, ry-pricing-card, ry-carousel, ry-combobox) {
52
52
  box-sizing: border-box;
53
53
  }
54
54
 
@@ -433,20 +433,13 @@ ry-button[icon][size="sm"]:empty {
433
433
 
434
434
  ry-button-group {
435
435
  display: inline-flex;
436
+ gap: 2px;
437
+ padding: 2px;
438
+ border-radius: var(--ry-radius-md, 0.375rem);
436
439
  }
437
440
 
438
441
  ry-button-group > :is(ry-button, ry-toggle-button) {
439
- border-radius: 0;
440
- }
441
-
442
- ry-button-group > :is(ry-button, ry-toggle-button):first-child {
443
- border-start-start-radius: var(--ry-radius-md, 0.375rem);
444
- border-end-start-radius: var(--ry-radius-md, 0.375rem);
445
- }
446
-
447
- ry-button-group > :is(ry-button, ry-toggle-button):last-child {
448
- border-start-end-radius: var(--ry-radius-md, 0.375rem);
449
- border-end-end-radius: var(--ry-radius-md, 0.375rem);
442
+ border-radius: calc(var(--ry-radius-md, 0.375rem) - 2px);
450
443
  }
451
444
 
452
445
  /* ═══════════════════════════════════════════════════════════════
@@ -823,6 +816,23 @@ ry-card {
823
816
  display: block;
824
817
  padding: var(--ry-space-6, 1.5rem);
825
818
  container-type: inline-size;
819
+ transition: transform var(--ry-duration-fast, 100ms) var(--ry-ease, ease);
820
+ }
821
+
822
+ ry-card:hover {
823
+ transform: translateY(-2px);
824
+ }
825
+
826
+ ry-card[interactive] {
827
+ cursor: pointer;
828
+ }
829
+
830
+ ry-card[interactive]:hover {
831
+ transform: translateY(-3px);
832
+ }
833
+
834
+ ry-card[interactive]:active {
835
+ transform: translateY(0);
826
836
  }
827
837
 
828
838
  ry-card h3 {
@@ -2807,4 +2817,124 @@ ry-carousel [data-ry-target="dot"] {
2807
2817
  cursor: pointer;
2808
2818
  }
2809
2819
 
2820
+ /* ═══════════════════════════════════════════════════════════════
2821
+ COMBOBOX
2822
+ ═══════════════════════════════════════════════════════════════ */
2823
+
2824
+ ry-combobox {
2825
+ position: relative;
2826
+ display: inline-block;
2827
+ min-width: 12rem;
2828
+ }
2829
+
2830
+ ry-combobox[disabled] {
2831
+ cursor: not-allowed;
2832
+ }
2833
+
2834
+ ry-combobox [data-ry-target="input-wrapper"] {
2835
+ display: flex;
2836
+ align-items: center;
2837
+ gap: var(--ry-space-2, 0.5rem);
2838
+ width: 100%;
2839
+ }
2840
+
2841
+ ry-combobox [data-ry-target="input"] {
2842
+ flex: 1;
2843
+ width: 100%;
2844
+ min-width: 0;
2845
+ padding: var(--ry-space-2, 0.5rem) var(--ry-space-3, 0.75rem);
2846
+ border: none;
2847
+ outline: none;
2848
+ background: transparent;
2849
+ }
2850
+
2851
+ ry-combobox [data-ry-target="arrow"] {
2852
+ flex-shrink: 0;
2853
+ padding-inline-end: var(--ry-space-2, 0.5rem);
2854
+ cursor: pointer;
2855
+ transition: transform var(--ry-duration-fast, 100ms) var(--ry-ease, ease);
2856
+ }
2857
+
2858
+ ry-combobox[data-ry-state="open"] [data-ry-target="arrow"] {
2859
+ transform: rotate(180deg);
2860
+ }
2861
+
2862
+ ry-combobox [data-ry-target="dropdown"] {
2863
+ position: absolute;
2864
+ top: 100%;
2865
+ left: 0;
2866
+ right: 0;
2867
+ z-index: var(--ry-z-dropdown, 1000);
2868
+ margin-block-start: var(--ry-space-1, 0.25rem);
2869
+ padding: var(--ry-space-1, 0.25rem);
2870
+ max-height: 15rem;
2871
+ overflow-y: auto;
2872
+ overscroll-behavior: contain;
2873
+ opacity: 0;
2874
+ visibility: hidden;
2875
+ transform: translateY(-0.5rem);
2876
+ transition: opacity var(--ry-duration-fast, 100ms) var(--ry-ease, ease),
2877
+ visibility var(--ry-duration-fast, 100ms) var(--ry-ease, ease),
2878
+ transform var(--ry-duration-fast, 100ms) var(--ry-ease, ease);
2879
+ transition-behavior: allow-discrete;
2880
+ }
2881
+
2882
+ ry-combobox[data-ry-state="open"] [data-ry-target="dropdown"] {
2883
+ opacity: 1;
2884
+ visibility: visible;
2885
+ transform: translateY(0);
2886
+ }
2887
+
2888
+ @starting-style {
2889
+ ry-combobox[data-ry-state="open"] [data-ry-target="dropdown"] {
2890
+ opacity: 0;
2891
+ transform: translateY(-0.5rem);
2892
+ }
2893
+ }
2894
+
2895
+ ry-combobox[data-ry-position="top"] [data-ry-target="dropdown"] {
2896
+ top: auto;
2897
+ bottom: 100%;
2898
+ margin-block-start: 0;
2899
+ margin-block-end: var(--ry-space-1, 0.25rem);
2900
+ transform: translateY(0.5rem);
2901
+ }
2902
+
2903
+ ry-combobox[data-ry-position="top"][data-ry-state="open"] [data-ry-target="dropdown"] {
2904
+ transform: translateY(0);
2905
+ }
2906
+
2907
+ ry-combobox [data-ry-target="option"] {
2908
+ padding: var(--ry-space-2, 0.5rem) var(--ry-space-3, 0.75rem);
2909
+ cursor: pointer;
2910
+ }
2911
+
2912
+ ry-combobox [data-ry-target="option"][hidden] {
2913
+ display: none;
2914
+ }
2915
+
2916
+ ry-combobox [data-ry-target="option"][data-disabled] {
2917
+ cursor: not-allowed;
2918
+ }
2919
+
2920
+ ry-combobox [data-ry-target="empty"] {
2921
+ padding: var(--ry-space-3, 0.75rem);
2922
+ text-align: center;
2923
+ }
2924
+
2925
+ ry-combobox [data-ry-target="empty"][hidden] {
2926
+ display: none;
2927
+ }
2928
+
2929
+ ry-combobox [data-ry-target="native"] {
2930
+ position: absolute;
2931
+ width: 1px;
2932
+ height: 1px;
2933
+ margin: -1px;
2934
+ padding: 0;
2935
+ overflow: hidden;
2936
+ clip: rect(0, 0, 0, 0);
2937
+ border: 0;
2938
+ }
2939
+
2810
2940
  } /* @layer ry-structure */
@@ -228,23 +228,18 @@ ry-split [data-ry-target="handle"]:focus-visible::after {
228
228
  BUTTON GROUP
229
229
  ═══════════════════════════════════════════════════════════════ */
230
230
 
231
- ry-button-group > :is(ry-button, ry-toggle-button) {
232
- border-color: var(--ry-color-border);
231
+ ry-button-group {
232
+ background-color: var(--ry-color-bg-muted);
233
233
  }
234
234
 
235
- ry-button-group > :is(ry-button, ry-toggle-button):not(:first-child) {
236
- border-inline-start-color: transparent;
235
+ ry-button-group > :is(ry-button, ry-toggle-button) {
236
+ border-color: transparent;
237
+ background-color: transparent;
238
+ color: var(--ry-color-text-muted);
237
239
  }
238
240
 
239
- /* Default children to outline style for segmented look */
240
- ry-button-group:not([variant]) > :is(ry-button, ry-toggle-button):not([pressed]) {
241
- background-color: var(--ry-color-bg);
241
+ ry-button-group > :is(ry-button, ry-toggle-button):hover:not([pressed]) {
242
242
  color: var(--ry-color-text);
243
- border-color: var(--ry-color-border);
244
- }
245
-
246
- ry-button-group:not([variant]) > :is(ry-button, ry-toggle-button):not([pressed]):hover {
247
- background-color: var(--ry-color-bg-muted);
248
243
  }
249
244
 
250
245
  /* Button sizes (typography only - padding is structural) */
@@ -680,6 +675,18 @@ ry-card {
680
675
  border: var(--ry-border-width) solid var(--ry-color-border);
681
676
  border-radius: var(--ry-radius-lg);
682
677
  box-shadow: var(--ry-shadow-sm);
678
+ transition: border-color var(--ry-duration-fast) var(--ry-ease),
679
+ box-shadow var(--ry-duration-fast) var(--ry-ease);
680
+ }
681
+
682
+ ry-card:hover {
683
+ border-color: var(--ry-color-border-strong);
684
+ box-shadow: var(--ry-shadow-md);
685
+ }
686
+
687
+ ry-card[interactive]:hover {
688
+ border-color: var(--ry-color-primary);
689
+ box-shadow: var(--ry-shadow-lg);
683
690
  }
684
691
 
685
692
  ry-card h3 {
@@ -2196,4 +2203,86 @@ ry-pricing-card ul li:last-child {
2196
2203
  font-weight: var(--ry-font-bold);
2197
2204
  }
2198
2205
 
2206
+ /* ═══════════════════════════════════════════════════════════════
2207
+ COMBOBOX
2208
+ ═══════════════════════════════════════════════════════════════ */
2209
+
2210
+ ry-combobox[disabled] {
2211
+ opacity: 0.5;
2212
+ }
2213
+
2214
+ .ry-combobox__input-wrapper {
2215
+ font-family: var(--ry-font-sans);
2216
+ font-size: var(--ry-text-base);
2217
+ line-height: var(--ry-leading-normal);
2218
+ background-color: var(--ry-color-bg);
2219
+ border: var(--ry-border-width) solid var(--ry-color-border);
2220
+ border-radius: var(--ry-radius-md);
2221
+ transition: border-color var(--ry-duration-fast) var(--ry-ease),
2222
+ box-shadow var(--ry-duration-fast) var(--ry-ease);
2223
+ }
2224
+
2225
+ ry-combobox:hover .ry-combobox__input-wrapper {
2226
+ border-color: var(--ry-color-border-strong);
2227
+ }
2228
+
2229
+ ry-combobox:focus-within .ry-combobox__input-wrapper {
2230
+ border-color: var(--ry-color-primary);
2231
+ box-shadow: var(--ry-focus-ring);
2232
+ }
2233
+
2234
+ ry-combobox[data-ry-state="open"] .ry-combobox__input-wrapper {
2235
+ border-color: var(--ry-color-primary);
2236
+ }
2237
+
2238
+ .ry-combobox__input {
2239
+ font: inherit;
2240
+ color: var(--ry-color-text);
2241
+ }
2242
+
2243
+ .ry-combobox__input::placeholder {
2244
+ color: var(--ry-color-text-muted);
2245
+ }
2246
+
2247
+ .ry-combobox__arrow {
2248
+ font-size: var(--ry-text-xs);
2249
+ color: var(--ry-color-text-muted);
2250
+ }
2251
+
2252
+ .ry-combobox__dropdown {
2253
+ background-color: var(--ry-color-bg);
2254
+ border: var(--ry-border-width) solid var(--ry-color-border);
2255
+ border-radius: var(--ry-radius-lg);
2256
+ box-shadow: var(--ry-shadow-lg);
2257
+ }
2258
+
2259
+ .ry-combobox__option {
2260
+ font-size: var(--ry-text-sm);
2261
+ color: var(--ry-color-text);
2262
+ border-radius: var(--ry-radius-md);
2263
+ transition: background-color var(--ry-duration-fast) var(--ry-ease);
2264
+ }
2265
+
2266
+ .ry-combobox__option:is(:hover, [data-highlighted]) {
2267
+ background-color: var(--ry-color-bg-muted);
2268
+ }
2269
+
2270
+ .ry-combobox__option[aria-selected="true"] {
2271
+ background-color: var(--ry-color-primary);
2272
+ color: var(--ry-color-text-inverse);
2273
+ }
2274
+
2275
+ .ry-combobox__option[data-disabled] {
2276
+ opacity: 0.5;
2277
+ }
2278
+
2279
+ .ry-combobox__option[data-disabled]:hover {
2280
+ background-color: transparent;
2281
+ }
2282
+
2283
+ .ry-combobox__empty {
2284
+ font-size: var(--ry-text-sm);
2285
+ color: var(--ry-color-text-muted);
2286
+ }
2287
+
2199
2288
  } /* @layer ry-theme */