@ryanhelsing/ry-ui 1.0.5 → 1.0.7

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()` |
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,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,22 +816,18 @@ ry-card {
823
816
  display: block;
824
817
  padding: var(--ry-space-6, 1.5rem);
825
818
  container-type: inline-size;
826
- transition: transform var(--ry-duration-fast, 100ms) var(--ry-ease, ease);
827
- }
828
-
829
- ry-card:hover {
830
- transform: translateY(-2px);
831
819
  }
832
820
 
833
- ry-card[interactive] {
821
+ :is(a, [interactive]):is(ry-card) {
834
822
  cursor: pointer;
823
+ transition: transform var(--ry-duration-normal, 200ms) var(--ry-ease, ease);
835
824
  }
836
825
 
837
- ry-card[interactive]:hover {
838
- transform: translateY(-3px);
826
+ :is(a, [interactive]):is(ry-card):hover {
827
+ transform: translateY(-1px);
839
828
  }
840
829
 
841
- ry-card[interactive]:active {
830
+ :is(a, [interactive]):is(ry-card):active {
842
831
  transform: translateY(0);
843
832
  }
844
833
 
@@ -2824,4 +2813,124 @@ ry-carousel [data-ry-target="dot"] {
2824
2813
  cursor: pointer;
2825
2814
  }
2826
2815
 
2816
+ /* ═══════════════════════════════════════════════════════════════
2817
+ COMBOBOX
2818
+ ═══════════════════════════════════════════════════════════════ */
2819
+
2820
+ ry-combobox {
2821
+ position: relative;
2822
+ display: inline-block;
2823
+ min-width: 12rem;
2824
+ }
2825
+
2826
+ ry-combobox[disabled] {
2827
+ cursor: not-allowed;
2828
+ }
2829
+
2830
+ ry-combobox [data-ry-target="input-wrapper"] {
2831
+ display: flex;
2832
+ align-items: center;
2833
+ gap: var(--ry-space-2, 0.5rem);
2834
+ width: 100%;
2835
+ }
2836
+
2837
+ ry-combobox [data-ry-target="input"] {
2838
+ flex: 1;
2839
+ width: 100%;
2840
+ min-width: 0;
2841
+ padding: var(--ry-space-2, 0.5rem) var(--ry-space-3, 0.75rem);
2842
+ border: none;
2843
+ outline: none;
2844
+ background: transparent;
2845
+ }
2846
+
2847
+ ry-combobox [data-ry-target="arrow"] {
2848
+ flex-shrink: 0;
2849
+ padding-inline-end: var(--ry-space-2, 0.5rem);
2850
+ cursor: pointer;
2851
+ transition: transform var(--ry-duration-fast, 100ms) var(--ry-ease, ease);
2852
+ }
2853
+
2854
+ ry-combobox[data-ry-state="open"] [data-ry-target="arrow"] {
2855
+ transform: rotate(180deg);
2856
+ }
2857
+
2858
+ ry-combobox [data-ry-target="dropdown"] {
2859
+ position: absolute;
2860
+ top: 100%;
2861
+ left: 0;
2862
+ right: 0;
2863
+ z-index: var(--ry-z-dropdown, 1000);
2864
+ margin-block-start: var(--ry-space-1, 0.25rem);
2865
+ padding: var(--ry-space-1, 0.25rem);
2866
+ max-height: 15rem;
2867
+ overflow-y: auto;
2868
+ overscroll-behavior: contain;
2869
+ opacity: 0;
2870
+ visibility: hidden;
2871
+ transform: translateY(-0.5rem);
2872
+ transition: opacity var(--ry-duration-fast, 100ms) var(--ry-ease, ease),
2873
+ visibility var(--ry-duration-fast, 100ms) var(--ry-ease, ease),
2874
+ transform var(--ry-duration-fast, 100ms) var(--ry-ease, ease);
2875
+ transition-behavior: allow-discrete;
2876
+ }
2877
+
2878
+ ry-combobox[data-ry-state="open"] [data-ry-target="dropdown"] {
2879
+ opacity: 1;
2880
+ visibility: visible;
2881
+ transform: translateY(0);
2882
+ }
2883
+
2884
+ @starting-style {
2885
+ ry-combobox[data-ry-state="open"] [data-ry-target="dropdown"] {
2886
+ opacity: 0;
2887
+ transform: translateY(-0.5rem);
2888
+ }
2889
+ }
2890
+
2891
+ ry-combobox[data-ry-position="top"] [data-ry-target="dropdown"] {
2892
+ top: auto;
2893
+ bottom: 100%;
2894
+ margin-block-start: 0;
2895
+ margin-block-end: var(--ry-space-1, 0.25rem);
2896
+ transform: translateY(0.5rem);
2897
+ }
2898
+
2899
+ ry-combobox[data-ry-position="top"][data-ry-state="open"] [data-ry-target="dropdown"] {
2900
+ transform: translateY(0);
2901
+ }
2902
+
2903
+ ry-combobox [data-ry-target="option"] {
2904
+ padding: var(--ry-space-2, 0.5rem) var(--ry-space-3, 0.75rem);
2905
+ cursor: pointer;
2906
+ }
2907
+
2908
+ ry-combobox [data-ry-target="option"][hidden] {
2909
+ display: none;
2910
+ }
2911
+
2912
+ ry-combobox [data-ry-target="option"][data-disabled] {
2913
+ cursor: not-allowed;
2914
+ }
2915
+
2916
+ ry-combobox [data-ry-target="empty"] {
2917
+ padding: var(--ry-space-3, 0.75rem);
2918
+ text-align: center;
2919
+ }
2920
+
2921
+ ry-combobox [data-ry-target="empty"][hidden] {
2922
+ display: none;
2923
+ }
2924
+
2925
+ ry-combobox [data-ry-target="native"] {
2926
+ position: absolute;
2927
+ width: 1px;
2928
+ height: 1px;
2929
+ margin: -1px;
2930
+ padding: 0;
2931
+ overflow: hidden;
2932
+ clip: rect(0, 0, 0, 0);
2933
+ border: 0;
2934
+ }
2935
+
2827
2936
  } /* @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,18 +675,16 @@ 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);
683
- transition: border-color var(--ry-duration-fast) var(--ry-ease),
684
- box-shadow var(--ry-duration-fast) var(--ry-ease);
685
678
  }
686
679
 
687
- ry-card:hover {
688
- border-color: var(--ry-color-border-strong);
689
- box-shadow: var(--ry-shadow-md);
680
+ :is(a, [interactive]):is(ry-card) {
681
+ transition: border-color var(--ry-duration-normal) var(--ry-ease),
682
+ box-shadow var(--ry-duration-normal) var(--ry-ease);
690
683
  }
691
684
 
692
- ry-card[interactive]:hover {
693
- border-color: var(--ry-color-primary);
694
- box-shadow: var(--ry-shadow-lg);
685
+ :is(a, [interactive]):is(ry-card):hover {
686
+ border-color: var(--ry-color-border-strong);
687
+ box-shadow: var(--ry-shadow-md);
695
688
  }
696
689
 
697
690
  ry-card h3 {
@@ -2208,4 +2201,86 @@ ry-pricing-card ul li:last-child {
2208
2201
  font-weight: var(--ry-font-bold);
2209
2202
  }
2210
2203
 
2204
+ /* ═══════════════════════════════════════════════════════════════
2205
+ COMBOBOX
2206
+ ═══════════════════════════════════════════════════════════════ */
2207
+
2208
+ ry-combobox[disabled] {
2209
+ opacity: 0.5;
2210
+ }
2211
+
2212
+ .ry-combobox__input-wrapper {
2213
+ font-family: var(--ry-font-sans);
2214
+ font-size: var(--ry-text-base);
2215
+ line-height: var(--ry-leading-normal);
2216
+ background-color: var(--ry-color-bg);
2217
+ border: var(--ry-border-width) solid var(--ry-color-border);
2218
+ border-radius: var(--ry-radius-md);
2219
+ transition: border-color var(--ry-duration-fast) var(--ry-ease),
2220
+ box-shadow var(--ry-duration-fast) var(--ry-ease);
2221
+ }
2222
+
2223
+ ry-combobox:hover .ry-combobox__input-wrapper {
2224
+ border-color: var(--ry-color-border-strong);
2225
+ }
2226
+
2227
+ ry-combobox:focus-within .ry-combobox__input-wrapper {
2228
+ border-color: var(--ry-color-primary);
2229
+ box-shadow: var(--ry-focus-ring);
2230
+ }
2231
+
2232
+ ry-combobox[data-ry-state="open"] .ry-combobox__input-wrapper {
2233
+ border-color: var(--ry-color-primary);
2234
+ }
2235
+
2236
+ .ry-combobox__input {
2237
+ font: inherit;
2238
+ color: var(--ry-color-text);
2239
+ }
2240
+
2241
+ .ry-combobox__input::placeholder {
2242
+ color: var(--ry-color-text-muted);
2243
+ }
2244
+
2245
+ .ry-combobox__arrow {
2246
+ font-size: var(--ry-text-xs);
2247
+ color: var(--ry-color-text-muted);
2248
+ }
2249
+
2250
+ .ry-combobox__dropdown {
2251
+ background-color: var(--ry-color-bg);
2252
+ border: var(--ry-border-width) solid var(--ry-color-border);
2253
+ border-radius: var(--ry-radius-lg);
2254
+ box-shadow: var(--ry-shadow-lg);
2255
+ }
2256
+
2257
+ .ry-combobox__option {
2258
+ font-size: var(--ry-text-sm);
2259
+ color: var(--ry-color-text);
2260
+ border-radius: var(--ry-radius-md);
2261
+ transition: background-color var(--ry-duration-fast) var(--ry-ease);
2262
+ }
2263
+
2264
+ .ry-combobox__option:is(:hover, [data-highlighted]) {
2265
+ background-color: var(--ry-color-bg-muted);
2266
+ }
2267
+
2268
+ .ry-combobox__option[aria-selected="true"] {
2269
+ background-color: var(--ry-color-primary);
2270
+ color: var(--ry-color-text-inverse);
2271
+ }
2272
+
2273
+ .ry-combobox__option[data-disabled] {
2274
+ opacity: 0.5;
2275
+ }
2276
+
2277
+ .ry-combobox__option[data-disabled]:hover {
2278
+ background-color: transparent;
2279
+ }
2280
+
2281
+ .ry-combobox__empty {
2282
+ font-size: var(--ry-text-sm);
2283
+ color: var(--ry-color-text-muted);
2284
+ }
2285
+
2211
2286
  } /* @layer ry-theme */