react-wheel-select 0.0.1

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.
@@ -0,0 +1,391 @@
1
+ /**
2
+ * WheelSelect - iOS-style Wheel Picker for React
3
+ *
4
+ * A fully customizable wheel select component with CSS custom properties.
5
+ * All visual aspects can be configured via props or CSS variables.
6
+ *
7
+ * @package @wheel-select/react
8
+ * @version 1.0.0
9
+ */
10
+
11
+ /* ============================================================================
12
+ CSS Custom Properties (Design Tokens)
13
+ ============================================================================
14
+
15
+ These variables can be overridden via:
16
+ 1. Component props (theme, sizing)
17
+ 2. CSS custom properties on parent elements
18
+ 3. Global CSS overrides
19
+ */
20
+
21
+ .ws-root {
22
+ /* Colors - Dark theme defaults */
23
+ --ws-color-text: inherit;
24
+ --ws-color-text-muted: inherit;
25
+ --ws-color-active-bg: rgba(255, 255, 255, 0.12);
26
+ --ws-color-hover-bg: rgba(255, 255, 255, 0.12);
27
+ --ws-color-backdrop-bg: rgba(0, 0, 0, 0.5);
28
+ --ws-color-focus-ring: rgba(255, 255, 255, 0.5);
29
+
30
+ /* Border radius */
31
+ --ws-border-radius: 12px;
32
+
33
+ /* Typography */
34
+ --ws-font-family: inherit;
35
+ --ws-font-size: 28px;
36
+ --ws-font-weight: 500;
37
+ --ws-font-size-trigger: inherit;
38
+
39
+ /* Animation */
40
+ --ws-animation-duration: 200ms;
41
+ --ws-animation-easing: ease;
42
+
43
+ /* Spacing */
44
+ --ws-trigger-gap: 16px;
45
+ --ws-trigger-padding: 8px 16px;
46
+ --ws-option-gap: 16px;
47
+ --ws-option-padding: 0 20px;
48
+
49
+ /* Sizing */
50
+ --ws-wheel-height: 320px;
51
+ --ws-wheel-min-width: 220px;
52
+ --ws-option-height: 56px;
53
+ --ws-icon-size: 20px;
54
+ --ws-spacer-height: 132px;
55
+
56
+ /* Z-index */
57
+ --ws-z-index: 10001;
58
+
59
+ /* Internal variables */
60
+ --ws-inactive-opacity: 0.35;
61
+ --ws-hover-opacity: 0.5;
62
+ --ws-backdrop-blur: 4px;
63
+ }
64
+
65
+ /* ============================================================================
66
+ Light Theme
67
+ ============================================================================ */
68
+
69
+ .ws-root.ws-light {
70
+ --ws-color-active-bg: rgba(0, 0, 0, 0.08);
71
+ --ws-color-hover-bg: rgba(0, 0, 0, 0.08);
72
+ --ws-color-backdrop-bg: rgba(255, 255, 255, 0.8);
73
+ --ws-color-focus-ring: rgba(0, 0, 0, 0.3);
74
+ }
75
+
76
+ /* ============================================================================
77
+ Root Container
78
+ ============================================================================ */
79
+
80
+ .ws-root {
81
+ display: inline;
82
+ position: relative;
83
+ font-family: var(--ws-font-family);
84
+ }
85
+
86
+ /* ============================================================================
87
+ Hidden Native Select (for form submission & accessibility)
88
+ ============================================================================ */
89
+
90
+ .ws-native-select {
91
+ position: absolute;
92
+ width: 1px;
93
+ height: 1px;
94
+ padding: 0;
95
+ margin: -1px;
96
+ overflow: hidden;
97
+ clip: rect(0, 0, 0, 0);
98
+ white-space: nowrap;
99
+ border: 0;
100
+ }
101
+
102
+ /* ============================================================================
103
+ Trigger Button
104
+ ============================================================================ */
105
+
106
+ .ws-trigger {
107
+ /* Reset button styles */
108
+ border: none;
109
+ background: transparent;
110
+ font: inherit;
111
+ font-size: var(--ws-font-size-trigger);
112
+ color: var(--ws-color-text);
113
+ cursor: pointer;
114
+ margin: 0;
115
+
116
+ /* Layout */
117
+ display: inline-flex;
118
+ align-items: center;
119
+ gap: var(--ws-trigger-gap);
120
+ padding: var(--ws-trigger-padding);
121
+ border-radius: var(--ws-border-radius);
122
+
123
+ /* Transitions */
124
+ transition: background-color var(--ws-animation-duration) var(--ws-animation-easing);
125
+ }
126
+
127
+ .ws-trigger:hover:not(:disabled) {
128
+ background-color: var(--ws-color-hover-bg);
129
+ }
130
+
131
+ .ws-trigger:focus {
132
+ outline: none;
133
+ }
134
+
135
+ .ws-trigger:focus-visible {
136
+ outline: 2px solid var(--ws-color-focus-ring);
137
+ outline-offset: 2px;
138
+ }
139
+
140
+ .ws-trigger.ws-open {
141
+ background-color: var(--ws-color-hover-bg);
142
+ }
143
+
144
+ .ws-trigger:disabled {
145
+ cursor: not-allowed;
146
+ opacity: 0.5;
147
+ }
148
+
149
+ .ws-trigger-text {
150
+ /* Allow text to be styled independently */
151
+ }
152
+
153
+ .ws-chevron {
154
+ opacity: 0.5;
155
+ flex-shrink: 0;
156
+ width: var(--ws-icon-size);
157
+ height: var(--ws-icon-size);
158
+ }
159
+
160
+ /* ============================================================================
161
+ Backdrop Overlay
162
+ ============================================================================ */
163
+
164
+ .ws-backdrop {
165
+ position: fixed;
166
+ inset: 0;
167
+ z-index: var(--ws-z-index);
168
+
169
+ /* Semi-transparent blur */
170
+ background: var(--ws-color-backdrop-bg);
171
+ backdrop-filter: blur(var(--ws-backdrop-blur));
172
+ -webkit-backdrop-filter: blur(var(--ws-backdrop-blur));
173
+
174
+ /* Fade in */
175
+ animation: ws-backdrop-fade-in var(--ws-animation-duration) var(--ws-animation-easing);
176
+ }
177
+
178
+ @keyframes ws-backdrop-fade-in {
179
+ from {
180
+ opacity: 0;
181
+ }
182
+ to {
183
+ opacity: 1;
184
+ }
185
+ }
186
+
187
+ /* Reduced motion support */
188
+ @media (prefers-reduced-motion: reduce) {
189
+ .ws-backdrop {
190
+ animation: none;
191
+ }
192
+ }
193
+
194
+ /* ============================================================================
195
+ Picker Container
196
+ ============================================================================ */
197
+
198
+ .ws-picker {
199
+ position: absolute;
200
+ transform: translateY(-50%);
201
+
202
+ /* Scale in animation */
203
+ animation: ws-picker-fade-in var(--ws-animation-duration) var(--ws-animation-easing);
204
+ }
205
+
206
+ @keyframes ws-picker-fade-in {
207
+ from {
208
+ opacity: 0;
209
+ transform: translateY(-50%) scale(0.95);
210
+ }
211
+ to {
212
+ opacity: 1;
213
+ transform: translateY(-50%) scale(1);
214
+ }
215
+ }
216
+
217
+ @media (prefers-reduced-motion: reduce) {
218
+ .ws-picker {
219
+ animation: none;
220
+ }
221
+ }
222
+
223
+ /* ============================================================================
224
+ Wheel (Scrollable Options Container)
225
+ ============================================================================ */
226
+
227
+ .ws-wheel {
228
+ /* Dimensions */
229
+ height: var(--ws-wheel-height);
230
+ min-width: var(--ws-wheel-min-width);
231
+
232
+ /* Scrolling */
233
+ overflow-y: auto;
234
+ overflow-x: hidden;
235
+ overscroll-behavior: contain;
236
+
237
+ /* CSS Scroll Snapping */
238
+ scroll-snap-type: y proximity;
239
+
240
+ /* Touch optimization */
241
+ -webkit-overflow-scrolling: touch;
242
+ touch-action: pan-y;
243
+
244
+ /* Hide scrollbar */
245
+ scrollbar-width: none;
246
+ -ms-overflow-style: none;
247
+ }
248
+
249
+ .ws-wheel::-webkit-scrollbar {
250
+ display: none;
251
+ }
252
+
253
+ .ws-wheel:focus {
254
+ outline: none;
255
+ }
256
+
257
+ /* ============================================================================
258
+ Spacers (for centering first/last items)
259
+ ============================================================================ */
260
+
261
+ .ws-spacer {
262
+ height: var(--ws-spacer-height);
263
+ flex-shrink: 0;
264
+ }
265
+
266
+ /* ============================================================================
267
+ Option Items
268
+ ============================================================================ */
269
+
270
+ .ws-option {
271
+ /* Dimensions */
272
+ height: var(--ws-option-height);
273
+ min-width: calc(var(--ws-wheel-min-width) - 20px);
274
+
275
+ /* Layout */
276
+ display: flex;
277
+ align-items: center;
278
+ justify-content: flex-start;
279
+ gap: var(--ws-option-gap);
280
+ padding: var(--ws-option-padding);
281
+
282
+ /* Scroll snapping */
283
+ scroll-snap-align: center;
284
+
285
+ /* Typography */
286
+ font-family: var(--ws-font-family);
287
+ font-size: var(--ws-font-size);
288
+ font-weight: var(--ws-font-weight);
289
+ color: var(--ws-color-text);
290
+
291
+ /* Interaction */
292
+ cursor: pointer;
293
+ user-select: none;
294
+ touch-action: manipulation;
295
+
296
+ /* Inactive state */
297
+ opacity: var(--ws-inactive-opacity);
298
+
299
+ /* Visual */
300
+ border-radius: var(--ws-border-radius);
301
+
302
+ /* Transitions */
303
+ transition:
304
+ opacity var(--ws-animation-duration) var(--ws-animation-easing),
305
+ background-color var(--ws-animation-duration) var(--ws-animation-easing);
306
+
307
+ /* Stacking */
308
+ position: relative;
309
+ z-index: 1;
310
+ }
311
+
312
+ .ws-option:hover:not(.ws-disabled) {
313
+ opacity: var(--ws-hover-opacity);
314
+ }
315
+
316
+ /* Active (centered) option */
317
+ .ws-option.ws-active {
318
+ opacity: 1;
319
+ background-color: var(--ws-color-active-bg);
320
+ }
321
+
322
+ .ws-option.ws-active:hover {
323
+ opacity: 1;
324
+ }
325
+
326
+ /* Disabled option */
327
+ .ws-option.ws-disabled {
328
+ cursor: not-allowed;
329
+ opacity: 0.2;
330
+ }
331
+
332
+ .ws-option-text {
333
+ flex: 1;
334
+ pointer-events: none;
335
+ }
336
+
337
+ .ws-arrow {
338
+ opacity: 0.7;
339
+ flex-shrink: 0;
340
+ pointer-events: none;
341
+ width: var(--ws-icon-size);
342
+ height: var(--ws-icon-size);
343
+ }
344
+
345
+ /* ============================================================================
346
+ Utility Classes
347
+ ============================================================================ */
348
+
349
+ /* Hide visually but keep accessible */
350
+ .ws-sr-only {
351
+ position: absolute;
352
+ width: 1px;
353
+ height: 1px;
354
+ padding: 0;
355
+ margin: -1px;
356
+ overflow: hidden;
357
+ clip: rect(0, 0, 0, 0);
358
+ white-space: nowrap;
359
+ border: 0;
360
+ }
361
+
362
+ /* ============================================================================
363
+ Print Styles
364
+ ============================================================================ */
365
+
366
+ @media print {
367
+ .ws-backdrop,
368
+ .ws-picker {
369
+ display: none;
370
+ }
371
+
372
+ .ws-trigger {
373
+ background: transparent !important;
374
+ }
375
+ }
376
+
377
+ /* ============================================================================
378
+ High Contrast Mode
379
+ ============================================================================ */
380
+
381
+ @media (forced-colors: active) {
382
+ .ws-trigger:focus-visible {
383
+ outline: 2px solid CanvasText;
384
+ }
385
+
386
+ .ws-option.ws-active {
387
+ outline: 2px solid CanvasText;
388
+ background: Highlight;
389
+ color: HighlightText;
390
+ }
391
+ }
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "react-wheel-select",
3
+ "version": "0.0.1",
4
+ "description": "A beautiful, accessible iOS-style wheel picker component for React. Features smooth scroll snapping, keyboard navigation, full customization via CSS variables, and TypeScript support.",
5
+ "author": "Vasil Rashkov <vasil.rashkov@example.com>",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/vasilrashkov/react-wheel-select.git"
10
+ },
11
+ "homepage": "https://github.com/vasilrashkov/react-wheel-select#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/vasilrashkov/react-wheel-select/issues"
14
+ },
15
+ "keywords": [
16
+ "react",
17
+ "select",
18
+ "picker",
19
+ "wheel",
20
+ "wheel-picker",
21
+ "ios",
22
+ "ios-picker",
23
+ "scroll",
24
+ "scroll-select",
25
+ "dropdown",
26
+ "combobox",
27
+ "listbox",
28
+ "accessible",
29
+ "a11y",
30
+ "typescript",
31
+ "component",
32
+ "ui",
33
+ "form",
34
+ "input"
35
+ ],
36
+ "type": "module",
37
+ "main": "./dist/index.cjs",
38
+ "module": "./dist/index.js",
39
+ "types": "./dist/index.d.ts",
40
+ "exports": {
41
+ ".": {
42
+ "import": {
43
+ "types": "./dist/index.d.ts",
44
+ "default": "./dist/index.js"
45
+ },
46
+ "require": {
47
+ "types": "./dist/index.d.cts",
48
+ "default": "./dist/index.cjs"
49
+ }
50
+ },
51
+ "./styles.css": "./dist/styles.css",
52
+ "./package.json": "./package.json"
53
+ },
54
+ "files": [
55
+ "dist",
56
+ "README.md",
57
+ "LICENSE"
58
+ ],
59
+ "sideEffects": [
60
+ "*.css"
61
+ ],
62
+ "scripts": {
63
+ "dev": "vite",
64
+ "build": "vite build && yarn run build:lib",
65
+ "build:lib": "tsup src/components/index.ts --format esm,cjs --dts --clean && cp src/components/WheelSelect.css dist/styles.css",
66
+ "preview": "vite preview",
67
+ "typecheck": "tsc --noEmit",
68
+ "prepublishOnly": "yarn run build:lib"
69
+ },
70
+ "devDependencies": {
71
+ "@types/bun": "latest",
72
+ "@types/react": "^19.2.10",
73
+ "@types/react-dom": "^19.2.3",
74
+ "@vitejs/plugin-react": "^5.1.3",
75
+ "react": "^19.0.0",
76
+ "react-dom": "^19.0.0",
77
+ "tsup": "^8.0.0",
78
+ "typescript": "^5.0.0",
79
+ "vite": "^7.3.1"
80
+ },
81
+ "peerDependencies": {
82
+ "react": ">=17.0.0",
83
+ "react-dom": ">=17.0.0"
84
+ },
85
+ "peerDependenciesMeta": {
86
+ "react-dom": {
87
+ "optional": true
88
+ }
89
+ },
90
+ "engines": {
91
+ "node": ">=18.0.0"
92
+ }
93
+ }