@hotelcard/ui 0.0.3 → 0.0.5
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/README.md +147 -626
- package/dist/index.cjs +1213 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +204 -0
- package/dist/index.css.map +1 -1
- package/dist/{index.d.mts → index.d.cts} +46 -1
- package/dist/index.d.ts +46 -1
- package/dist/index.js +315 -231
- package/dist/index.js.map +1 -1
- package/package.json +19 -4
- package/dist/index.mjs +0 -1042
- package/dist/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,283 +1,163 @@
|
|
|
1
1
|
# @hotelcard/ui
|
|
2
2
|
|
|
3
|
-
Shared UI
|
|
3
|
+
Shared UI components for HotelCard website and mobile app.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@hotelcard/ui)
|
|
6
|
+
|
|
7
|
+
---
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## Installation
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
|
-
# In your project
|
|
11
12
|
npm install @hotelcard/ui
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
### Import Components
|
|
18
|
-
|
|
19
|
-
```tsx
|
|
20
|
-
import { Button, Badge, Rating, Input, Checkbox, Modal } from '@hotelcard/ui';
|
|
21
|
-
import type { ButtonProps, BadgeProps, InputProps } from '@hotelcard/ui';
|
|
13
|
+
# or
|
|
14
|
+
pnpm add @hotelcard/ui
|
|
15
|
+
# or
|
|
16
|
+
yarn add @hotelcard/ui
|
|
22
17
|
```
|
|
23
18
|
|
|
24
|
-
|
|
19
|
+
## Peer Dependencies
|
|
25
20
|
|
|
26
|
-
|
|
21
|
+
Make sure you have these installed:
|
|
27
22
|
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
import '@hotelcard/ui/dist/index.css';
|
|
23
|
+
```bash
|
|
24
|
+
npm install react react-dom
|
|
31
25
|
```
|
|
32
26
|
|
|
33
|
-
**If you skip this step, components will render without styling!**
|
|
34
|
-
|
|
35
27
|
---
|
|
36
28
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
### Button
|
|
29
|
+
## Quick Start
|
|
40
30
|
|
|
41
31
|
```tsx
|
|
42
|
-
import { Button } from '@hotelcard/ui';
|
|
43
|
-
|
|
44
|
-
// Primary button (default)
|
|
45
|
-
<Button onClick={handleClick}>Submit</Button>
|
|
32
|
+
import { Button, Card, Badge } from '@hotelcard/ui';
|
|
46
33
|
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// Small size
|
|
51
|
-
<Button size="small">Small Button</Button>
|
|
52
|
-
|
|
53
|
-
// Link style
|
|
54
|
-
<Button variant="link">Learn More</Button>
|
|
55
|
-
|
|
56
|
-
// With icons
|
|
57
|
-
<Button leftIcon={<SearchIcon />}>Search</Button>
|
|
34
|
+
// Import styles (required - do this once in your app entry)
|
|
35
|
+
import '@hotelcard/ui/dist/index.css';
|
|
58
36
|
|
|
59
|
-
|
|
60
|
-
|
|
37
|
+
function App() {
|
|
38
|
+
return (
|
|
39
|
+
<div>
|
|
40
|
+
<Button variant="primary" onClick={() => alert('Clicked!')}>
|
|
41
|
+
Book Now
|
|
42
|
+
</Button>
|
|
43
|
+
|
|
44
|
+
<Card
|
|
45
|
+
image="https://example.com/hotel.jpg"
|
|
46
|
+
title="Hotel Schweizerhof"
|
|
47
|
+
location="Luzern, Switzerland"
|
|
48
|
+
stars={5}
|
|
49
|
+
price="CHF 195"
|
|
50
|
+
originalPrice="CHF 390"
|
|
51
|
+
onClick={() => console.log('Card clicked')}
|
|
52
|
+
>
|
|
53
|
+
<Badge color="primary" size="small">-50%</Badge>
|
|
54
|
+
</Card>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
61
58
|
```
|
|
62
59
|
|
|
63
|
-
|
|
64
|
-
| Prop | Type | Default | Description |
|
|
65
|
-
|------|------|---------|-------------|
|
|
66
|
-
| `variant` | `'primary' \| 'secondary' \| 'link'` | `'primary'` | Button style |
|
|
67
|
-
| `size` | `'small' \| 'medium'` | `'medium'` | Button size |
|
|
68
|
-
| `iconOnly` | `boolean` | `false` | Icon-only circular button |
|
|
69
|
-
| `leftIcon` | `ReactNode` | - | Icon on the left |
|
|
70
|
-
| `rightIcon` | `ReactNode` | - | Icon on the right |
|
|
71
|
-
| `disabled` | `boolean` | `false` | Disabled state |
|
|
72
|
-
|
|
73
|
-
### Badge
|
|
60
|
+
---
|
|
74
61
|
|
|
75
|
-
|
|
76
|
-
import { Badge } from '@hotelcard/ui';
|
|
62
|
+
## Available Components
|
|
77
63
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
64
|
+
### UI Components
|
|
65
|
+
|
|
66
|
+
| Component | Description |
|
|
67
|
+
|-----------|-------------|
|
|
68
|
+
| `Button` | Primary, secondary, link buttons with icon support |
|
|
69
|
+
| `Input` | Text input with label, helper text, and validation |
|
|
70
|
+
| `Badge` | Status and discount badges |
|
|
71
|
+
| `Card` | Hotel card component |
|
|
72
|
+
| `CompactCard` | Compact hotel card for carousels |
|
|
73
|
+
| `Chip` | Filter and tag chips |
|
|
74
|
+
| `Checkbox` | Checkbox with label |
|
|
75
|
+
| `RadioButton` | Radio button with label |
|
|
76
|
+
| `Rating` | Star rating display |
|
|
77
|
+
| `Divider` | Horizontal/vertical divider |
|
|
78
|
+
| `Dropdown` | Select dropdown |
|
|
79
|
+
| `Modal` | Modal dialog |
|
|
80
|
+
| `SectionHeader` | Section title with "Show all" button |
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
<Badge color="success" style="light">
|
|
85
|
-
Available
|
|
86
|
-
</Badge>
|
|
87
|
-
```
|
|
82
|
+
### Icons
|
|
88
83
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
84
|
+
| Icon | Description |
|
|
85
|
+
|------|-------------|
|
|
86
|
+
| `HeartIcon` | Heart icon (filled/outline) |
|
|
87
|
+
| `StarIcon` | Star icon for ratings |
|
|
88
|
+
| `ChevronLeftIcon` | Left arrow |
|
|
89
|
+
| `ChevronRightIcon` | Right arrow |
|
|
90
|
+
| `PinIcon` | Location pin |
|
|
95
91
|
|
|
96
|
-
|
|
92
|
+
---
|
|
97
93
|
|
|
98
|
-
|
|
99
|
-
import { Rating } from '@hotelcard/ui';
|
|
94
|
+
## Component Examples
|
|
100
95
|
|
|
101
|
-
|
|
102
|
-
<Rating value={4.5} />
|
|
96
|
+
### Button
|
|
103
97
|
|
|
104
|
-
|
|
105
|
-
|
|
98
|
+
```tsx
|
|
99
|
+
import { Button } from '@hotelcard/ui';
|
|
106
100
|
|
|
107
|
-
|
|
108
|
-
<
|
|
109
|
-
<
|
|
101
|
+
<Button variant="primary">Primary</Button>
|
|
102
|
+
<Button variant="secondary">Secondary</Button>
|
|
103
|
+
<Button variant="link">Link Style</Button>
|
|
104
|
+
<Button size="small">Small Button</Button>
|
|
105
|
+
<Button disabled>Disabled</Button>
|
|
106
|
+
<Button leftIcon={<SearchIcon />}>Search</Button>
|
|
107
|
+
<Button iconOnly><HeartIcon /></Button>
|
|
110
108
|
```
|
|
111
109
|
|
|
112
|
-
**Props:**
|
|
113
|
-
| Prop | Type | Default | Description |
|
|
114
|
-
|------|------|---------|-------------|
|
|
115
|
-
| `value` | `number` | - | Rating value (0-5) |
|
|
116
|
-
| `maxValue` | `number` | `5` | Maximum stars |
|
|
117
|
-
| `showValue` | `boolean` | `false` | Show numeric value |
|
|
118
|
-
| `size` | `'small' \| 'medium' \| 'large'` | `'medium'` | Star size |
|
|
119
|
-
|
|
120
110
|
### Input
|
|
121
111
|
|
|
122
112
|
```tsx
|
|
123
113
|
import { Input } from '@hotelcard/ui';
|
|
124
114
|
|
|
125
|
-
// Basic input
|
|
126
115
|
<Input
|
|
127
116
|
label="Email"
|
|
128
|
-
|
|
117
|
+
type="email"
|
|
118
|
+
placeholder="you@example.com"
|
|
129
119
|
value={email}
|
|
130
120
|
onChange={setEmail}
|
|
121
|
+
helper="We'll never share your email"
|
|
131
122
|
/>
|
|
132
123
|
|
|
133
|
-
// With helper text
|
|
134
124
|
<Input
|
|
135
125
|
label="Password"
|
|
136
126
|
type="password"
|
|
137
|
-
helper="Must be at least 8 characters"
|
|
138
|
-
/>
|
|
139
|
-
|
|
140
|
-
// Error state
|
|
141
|
-
<Input
|
|
142
|
-
label="Email"
|
|
143
127
|
error
|
|
144
|
-
helper="
|
|
145
|
-
/>
|
|
146
|
-
|
|
147
|
-
// With icons
|
|
148
|
-
<Input
|
|
149
|
-
leftIcon={<SearchIcon />}
|
|
150
|
-
placeholder="Search..."
|
|
151
|
-
/>
|
|
152
|
-
|
|
153
|
-
// Disabled
|
|
154
|
-
<Input
|
|
155
|
-
label="Disabled"
|
|
156
|
-
disabled
|
|
157
|
-
value="Cannot edit"
|
|
128
|
+
helper="Password is required"
|
|
158
129
|
/>
|
|
159
130
|
```
|
|
160
131
|
|
|
161
|
-
|
|
162
|
-
| Prop | Type | Default | Description |
|
|
163
|
-
|------|------|---------|-------------|
|
|
164
|
-
| `label` | `string` | - | Label text above input |
|
|
165
|
-
| `placeholder` | `string` | `'Placeholder'` | Placeholder text |
|
|
166
|
-
| `helper` | `string` | - | Helper text below input |
|
|
167
|
-
| `value` | `string` | - | Controlled value |
|
|
168
|
-
| `onChange` | `(value: string) => void` | - | Change handler |
|
|
169
|
-
| `type` | `'text' \| 'email' \| 'password' \| 'tel' \| 'number'` | `'text'` | Input type |
|
|
170
|
-
| `error` | `boolean` | `false` | Show error state |
|
|
171
|
-
| `disabled` | `boolean` | `false` | Disable input |
|
|
172
|
-
| `leftIcon` | `ReactNode` | - | Icon on the left |
|
|
173
|
-
| `rightIcon` | `ReactNode` | - | Icon on the right |
|
|
174
|
-
|
|
175
|
-
### Checkbox
|
|
176
|
-
|
|
177
|
-
```tsx
|
|
178
|
-
import { Checkbox } from '@hotelcard/ui';
|
|
179
|
-
|
|
180
|
-
// Controlled checkbox
|
|
181
|
-
<Checkbox
|
|
182
|
-
label="Accept terms"
|
|
183
|
-
checked={accepted}
|
|
184
|
-
onChange={setAccepted}
|
|
185
|
-
/>
|
|
186
|
-
|
|
187
|
-
// Uncontrolled with default
|
|
188
|
-
<Checkbox
|
|
189
|
-
label="Remember me"
|
|
190
|
-
defaultChecked
|
|
191
|
-
/>
|
|
192
|
-
|
|
193
|
-
// Error state
|
|
194
|
-
<Checkbox
|
|
195
|
-
label="Required field"
|
|
196
|
-
error
|
|
197
|
-
/>
|
|
198
|
-
|
|
199
|
-
// Sizes
|
|
200
|
-
<Checkbox label="Medium" size="medium" />
|
|
201
|
-
<Checkbox label="Small" size="small" />
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
**Props:**
|
|
205
|
-
| Prop | Type | Default | Description |
|
|
206
|
-
|------|------|---------|-------------|
|
|
207
|
-
| `label` | `string` | - | Label text |
|
|
208
|
-
| `checked` | `boolean` | - | Controlled checked state |
|
|
209
|
-
| `defaultChecked` | `boolean` | `false` | Default checked state |
|
|
210
|
-
| `onChange` | `(checked: boolean) => void` | - | Change handler |
|
|
211
|
-
| `size` | `'small' \| 'medium'` | `'medium'` | Checkbox size |
|
|
212
|
-
| `disabled` | `boolean` | `false` | Disable checkbox |
|
|
213
|
-
| `error` | `boolean` | `false` | Show error state |
|
|
214
|
-
|
|
215
|
-
### RadioButton
|
|
132
|
+
### Badge
|
|
216
133
|
|
|
217
134
|
```tsx
|
|
218
|
-
import {
|
|
135
|
+
import { Badge } from '@hotelcard/ui';
|
|
219
136
|
|
|
220
|
-
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
checked={selected === '1'}
|
|
224
|
-
onChange={() => setSelected('1')}
|
|
225
|
-
name="options"
|
|
226
|
-
/>
|
|
227
|
-
<RadioButton
|
|
228
|
-
label="Option 2"
|
|
229
|
-
checked={selected === '2'}
|
|
230
|
-
onChange={() => setSelected('2')}
|
|
231
|
-
name="options"
|
|
232
|
-
/>
|
|
137
|
+
<Badge color="primary" size="small" style="heavy">-50%</Badge>
|
|
138
|
+
<Badge color="success" style="light">Available</Badge>
|
|
139
|
+
<Badge color="warning">Limited</Badge>
|
|
233
140
|
```
|
|
234
141
|
|
|
235
|
-
|
|
236
|
-
| Prop | Type | Default | Description |
|
|
237
|
-
|------|------|---------|-------------|
|
|
238
|
-
| `label` | `string` | - | Label text |
|
|
239
|
-
| `checked` | `boolean` | - | Checked state (required) |
|
|
240
|
-
| `onChange` | `(checked: boolean) => void` | - | Change handler (required) |
|
|
241
|
-
| `name` | `string` | - | Group name for form |
|
|
242
|
-
| `disabled` | `boolean` | `false` | Disable radio |
|
|
243
|
-
|
|
244
|
-
### Dropdown
|
|
142
|
+
### Card
|
|
245
143
|
|
|
246
144
|
```tsx
|
|
247
|
-
import {
|
|
248
|
-
|
|
249
|
-
const options = [
|
|
250
|
-
{ value: '1', label: 'Option 1' },
|
|
251
|
-
{ value: '2', label: 'Option 2' },
|
|
252
|
-
{ value: '3', label: 'Option 3' },
|
|
253
|
-
];
|
|
254
|
-
|
|
255
|
-
// Controlled
|
|
256
|
-
<Dropdown
|
|
257
|
-
options={options}
|
|
258
|
-
value={selected}
|
|
259
|
-
onChange={setSelected}
|
|
260
|
-
placeholder="Select..."
|
|
261
|
-
/>
|
|
145
|
+
import { Card } from '@hotelcard/ui';
|
|
262
146
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
147
|
+
<Card
|
|
148
|
+
image="/hotel.jpg"
|
|
149
|
+
title="Grand Hotel Belvedere"
|
|
150
|
+
location="Lauterbrunnen"
|
|
151
|
+
stars={5}
|
|
152
|
+
price="From CHF 170"
|
|
153
|
+
originalPrice="CHF 340"
|
|
154
|
+
badge={{ text: '-50%', variant: 'primary' }}
|
|
155
|
+
isFavorite={false}
|
|
156
|
+
onFavoriteClick={() => toggleFavorite()}
|
|
157
|
+
onClick={() => navigateToHotel()}
|
|
267
158
|
/>
|
|
268
159
|
```
|
|
269
160
|
|
|
270
|
-
**Props:**
|
|
271
|
-
| Prop | Type | Default | Description |
|
|
272
|
-
|------|------|---------|-------------|
|
|
273
|
-
| `options` | `Array<{value, label}>` | `[]` | Dropdown options |
|
|
274
|
-
| `value` | `string` | - | Controlled value |
|
|
275
|
-
| `defaultValue` | `string` | - | Default value |
|
|
276
|
-
| `onChange` | `(value: string) => void` | - | Change handler |
|
|
277
|
-
| `placeholder` | `string` | `'Select...'` | Placeholder text |
|
|
278
|
-
| `disabled` | `boolean` | `false` | Disable dropdown |
|
|
279
|
-
| `error` | `boolean` | `false` | Show error state |
|
|
280
|
-
|
|
281
161
|
### Modal
|
|
282
162
|
|
|
283
163
|
```tsx
|
|
@@ -291,464 +171,105 @@ import { Modal } from '@hotelcard/ui';
|
|
|
291
171
|
<h2>Modal Title</h2>
|
|
292
172
|
<p>Modal content goes here.</p>
|
|
293
173
|
</Modal>
|
|
294
|
-
|
|
295
|
-
// Without close button
|
|
296
|
-
<Modal
|
|
297
|
-
isOpen={isOpen}
|
|
298
|
-
onClose={handleClose}
|
|
299
|
-
showCloseButton={false}
|
|
300
|
-
>
|
|
301
|
-
<CustomCloseButton />
|
|
302
|
-
</Modal>
|
|
303
|
-
|
|
304
|
-
// Disable backdrop click
|
|
305
|
-
<Modal
|
|
306
|
-
isOpen={isOpen}
|
|
307
|
-
onClose={handleClose}
|
|
308
|
-
disableBackdropClick
|
|
309
|
-
>
|
|
310
|
-
Must use close button
|
|
311
|
-
</Modal>
|
|
312
174
|
```
|
|
313
175
|
|
|
314
|
-
|
|
315
|
-
| Prop | Type | Default | Description |
|
|
316
|
-
|------|------|---------|-------------|
|
|
317
|
-
| `isOpen` | `boolean` | - | Show/hide modal |
|
|
318
|
-
| `onClose` | `() => void` | - | Close handler |
|
|
319
|
-
| `children` | `ReactNode` | - | Modal content |
|
|
320
|
-
| `width` | `string` | `'600px'` | Max width |
|
|
321
|
-
| `showCloseButton` | `boolean` | `true` | Show X button |
|
|
322
|
-
| `disableBackdropClick` | `boolean` | `false` | Prevent backdrop close |
|
|
323
|
-
|
|
324
|
-
### Chip
|
|
176
|
+
### Checkbox & RadioButton
|
|
325
177
|
|
|
326
178
|
```tsx
|
|
327
|
-
import {
|
|
328
|
-
|
|
329
|
-
// Basic chip
|
|
330
|
-
<Chip label="Filter" />
|
|
331
|
-
|
|
332
|
-
// With count
|
|
333
|
-
<Chip label="Hotels" count={42} />
|
|
334
|
-
|
|
335
|
-
// Active state
|
|
336
|
-
<Chip label="Selected" state="active" />
|
|
337
|
-
|
|
338
|
-
// Non-removable
|
|
339
|
-
<Chip label="Category" removable={false} onClick={() => {}} />
|
|
340
|
-
|
|
341
|
-
// Sizes
|
|
342
|
-
<Chip label="Medium" size="medium" />
|
|
343
|
-
<Chip label="Small" size="small" />
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
**Props:**
|
|
347
|
-
| Prop | Type | Default | Description |
|
|
348
|
-
|------|------|---------|-------------|
|
|
349
|
-
| `label` | `string` | - | Chip text |
|
|
350
|
-
| `count` | `number \| string` | - | Badge count |
|
|
351
|
-
| `state` | `'idle' \| 'active' \| 'disabled'` | `'idle'` | State variant |
|
|
352
|
-
| `size` | `'small' \| 'medium'` | `'small'` | Chip size |
|
|
353
|
-
| `removable` | `boolean` | `true` | Show X button |
|
|
354
|
-
| `onRemove` | `() => void` | - | Remove handler |
|
|
355
|
-
| `onClick` | `() => void` | - | Click handler |
|
|
179
|
+
import { Checkbox, RadioButton } from '@hotelcard/ui';
|
|
356
180
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
// Horizontal divider
|
|
363
|
-
<Divider />
|
|
364
|
-
|
|
365
|
-
// With label
|
|
366
|
-
<Divider label="OR" />
|
|
367
|
-
|
|
368
|
-
// Vertical divider
|
|
369
|
-
<Divider orientation="vertical" />
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
**Props:**
|
|
373
|
-
| Prop | Type | Default | Description |
|
|
374
|
-
|------|------|---------|-------------|
|
|
375
|
-
| `label` | `string` | - | Center label text |
|
|
376
|
-
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Divider direction |
|
|
377
|
-
|
|
378
|
-
### SectionHeader
|
|
379
|
-
|
|
380
|
-
```tsx
|
|
381
|
-
import { SectionHeader } from '@hotelcard/ui';
|
|
382
|
-
|
|
383
|
-
// With "Show all" button
|
|
384
|
-
<SectionHeader
|
|
385
|
-
title="Latest Hotels"
|
|
386
|
-
showAllLabel="Show all"
|
|
387
|
-
onShowAllClick={() => navigate('/hotels')}
|
|
388
|
-
/>
|
|
389
|
-
|
|
390
|
-
// Title only
|
|
391
|
-
<SectionHeader title="Featured" />
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
**Props:**
|
|
395
|
-
| Prop | Type | Default | Description |
|
|
396
|
-
|------|------|---------|-------------|
|
|
397
|
-
| `title` | `string` | - | Section title |
|
|
398
|
-
| `showAllLabel` | `string` | - | "Show all" button text |
|
|
399
|
-
| `onShowAllClick` | `() => void` | - | Button click handler |
|
|
400
|
-
|
|
401
|
-
### CompactCard
|
|
402
|
-
|
|
403
|
-
```tsx
|
|
404
|
-
import { CompactCard } from '@hotelcard/ui';
|
|
405
|
-
|
|
406
|
-
<CompactCard
|
|
407
|
-
image="/hotel.jpg"
|
|
408
|
-
imageAlt="Hotel Grand"
|
|
409
|
-
label="Hotel Grand Zurich"
|
|
410
|
-
price="From CHF 120"
|
|
411
|
-
stars={4}
|
|
412
|
-
isSuperior
|
|
413
|
-
badge={{ text: '-30%', variant: 'primary' }}
|
|
414
|
-
onClick={() => navigate('/hotel/123')}
|
|
181
|
+
<Checkbox
|
|
182
|
+
label="Accept terms"
|
|
183
|
+
checked={accepted}
|
|
184
|
+
onChange={setAccepted}
|
|
415
185
|
/>
|
|
416
186
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
187
|
+
<RadioButton
|
|
188
|
+
label="Option A"
|
|
189
|
+
checked={selected === 'a'}
|
|
190
|
+
onChange={() => setSelected('a')}
|
|
191
|
+
name="options"
|
|
422
192
|
/>
|
|
423
193
|
```
|
|
424
194
|
|
|
425
|
-
**Props:**
|
|
426
|
-
| Prop | Type | Default | Description |
|
|
427
|
-
|------|------|---------|-------------|
|
|
428
|
-
| `image` | `string` | - | Image URL |
|
|
429
|
-
| `imageAlt` | `string` | `''` | Image alt text |
|
|
430
|
-
| `label` | `string` | - | Hotel name |
|
|
431
|
-
| `price` | `string` | - | Price text |
|
|
432
|
-
| `stars` | `number` | - | Star rating (1-6) |
|
|
433
|
-
| `isSuperior` | `boolean` | `false` | Superior indicator |
|
|
434
|
-
| `badge` | `{text, variant}` | - | Badge config |
|
|
435
|
-
| `onClick` | `() => void` | - | Click handler |
|
|
436
|
-
| `swissLodgeLabel` | `string` | `'Swiss Lodge'` | Label for 6-star |
|
|
437
|
-
|
|
438
|
-
### Icons
|
|
439
|
-
|
|
440
|
-
```tsx
|
|
441
|
-
import { HeartIcon, StarIcon, ChevronLeftIcon, ChevronRightIcon, PinIcon } from '@hotelcard/ui';
|
|
442
|
-
|
|
443
|
-
<HeartIcon filled={false} size={24} />
|
|
444
|
-
<StarIcon size={9} />
|
|
445
|
-
<ChevronLeftIcon size={20} />
|
|
446
|
-
<ChevronRightIcon size={20} />
|
|
447
|
-
<PinIcon size={16} />
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
---
|
|
451
|
-
|
|
452
|
-
## CSS Variables (Theming)
|
|
453
|
-
|
|
454
|
-
Components use CSS variables for theming. Your app should define these in a global CSS file:
|
|
455
|
-
|
|
456
|
-
```css
|
|
457
|
-
:root {
|
|
458
|
-
/* Colors */
|
|
459
|
-
--background-action-primary-idle: #C81E4C;
|
|
460
|
-
--background-action-primary-hover: #a3183d;
|
|
461
|
-
--content-action-primary-inverse-idle: #ffffff;
|
|
462
|
-
--content-general-primary: #1F2937;
|
|
463
|
-
--content-general-secondary: #6B7280;
|
|
464
|
-
--border-general-divider: #D1D5DB;
|
|
465
|
-
|
|
466
|
-
/* Typography */
|
|
467
|
-
--font-primary: system-ui, -apple-system, sans-serif;
|
|
468
|
-
--font-weight-medium: 500;
|
|
469
|
-
--font-weight-semibold: 600;
|
|
470
|
-
|
|
471
|
-
/* Spacing & Sizing */
|
|
472
|
-
--radius-lg: 12px;
|
|
473
|
-
--radius-full: 9999px;
|
|
474
|
-
|
|
475
|
-
/* ... see tokens.css for full list */
|
|
476
|
-
}
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
Components have fallback values, so they work without custom CSS variables.
|
|
480
|
-
|
|
481
195
|
---
|
|
482
196
|
|
|
483
197
|
## Types
|
|
484
198
|
|
|
485
|
-
|
|
199
|
+
All components export their prop types:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
486
202
|
import type {
|
|
487
203
|
ButtonProps,
|
|
488
204
|
BadgeProps,
|
|
489
|
-
RatingProps,
|
|
490
205
|
InputProps,
|
|
206
|
+
CardProps,
|
|
207
|
+
CompactCardProps,
|
|
491
208
|
CheckboxProps,
|
|
492
209
|
RadioButtonProps,
|
|
493
210
|
DropdownProps,
|
|
494
211
|
ModalProps,
|
|
495
212
|
ChipProps,
|
|
213
|
+
RatingProps,
|
|
496
214
|
DividerProps,
|
|
497
215
|
SectionHeaderProps,
|
|
498
|
-
CompactCardProps,
|
|
499
216
|
} from '@hotelcard/ui';
|
|
500
217
|
```
|
|
501
218
|
|
|
502
219
|
---
|
|
503
220
|
|
|
504
|
-
##
|
|
505
|
-
|
|
506
|
-
### Project Structure
|
|
507
|
-
|
|
508
|
-
```
|
|
509
|
-
packages/ui/
|
|
510
|
-
├── src/
|
|
511
|
-
│ ├── components/
|
|
512
|
-
│ │ ├── Button/
|
|
513
|
-
│ │ │ ├── Button.tsx
|
|
514
|
-
│ │ │ ├── Button.css # Prefixed classes (hc-btn-*)
|
|
515
|
-
│ │ │ ├── Button.types.ts
|
|
516
|
-
│ │ │ └── index.ts
|
|
517
|
-
│ │ ├── Badge/
|
|
518
|
-
│ │ ├── Rating/
|
|
519
|
-
│ │ ├── Input/
|
|
520
|
-
│ │ ├── Checkbox/
|
|
521
|
-
│ │ ├── RadioButton/
|
|
522
|
-
│ │ ├── Dropdown/
|
|
523
|
-
│ │ ├── Modal/
|
|
524
|
-
│ │ ├── Chip/
|
|
525
|
-
│ │ ├── Divider/
|
|
526
|
-
│ │ ├── SectionHeader/
|
|
527
|
-
│ │ ├── CompactCard/
|
|
528
|
-
│ │ └── icons/
|
|
529
|
-
│ └── index.ts # Main exports
|
|
530
|
-
├── dist/ # Built output
|
|
531
|
-
├── package.json
|
|
532
|
-
└── tsup.config.ts
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
### Adding a New Component
|
|
536
|
-
|
|
537
|
-
1. **Create component folder:**
|
|
538
|
-
```
|
|
539
|
-
src/components/NewComponent/
|
|
540
|
-
├── NewComponent.tsx
|
|
541
|
-
├── NewComponent.css
|
|
542
|
-
├── NewComponent.types.ts
|
|
543
|
-
└── index.ts
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
2. **Use prefixed CSS classes** (NOT CSS modules):
|
|
547
|
-
```tsx
|
|
548
|
-
// NewComponent.tsx
|
|
549
|
-
import './NewComponent.css';
|
|
550
|
-
|
|
551
|
-
const cx = (className: string) => `hc-newcomp-${className}`;
|
|
552
|
-
|
|
553
|
-
export const NewComponent = () => (
|
|
554
|
-
<div className={cx('container')}>
|
|
555
|
-
<span className={cx('text')}>Hello</span>
|
|
556
|
-
</div>
|
|
557
|
-
);
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
3. **Define CSS with prefixes:**
|
|
561
|
-
```css
|
|
562
|
-
/* NewComponent.css */
|
|
563
|
-
.hc-newcomp-container {
|
|
564
|
-
display: flex;
|
|
565
|
-
padding: var(--size-rem-1, 16px);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
.hc-newcomp-text {
|
|
569
|
-
color: var(--content-general-primary, #1F2937);
|
|
570
|
-
}
|
|
571
|
-
```
|
|
572
|
-
|
|
573
|
-
4. **Export from index.ts:**
|
|
574
|
-
```tsx
|
|
575
|
-
// src/index.ts
|
|
576
|
-
export { NewComponent } from './components/NewComponent';
|
|
577
|
-
export type { NewComponentProps } from './components/NewComponent';
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
5. **Build and test:**
|
|
581
|
-
```bash
|
|
582
|
-
pnpm build
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
### CSS Class Naming Convention
|
|
586
|
-
|
|
587
|
-
**Why prefixed classes instead of CSS Modules?**
|
|
588
|
-
|
|
589
|
-
CSS Modules don't work well with tsup bundling - the class name mappings become empty objects. We use prefixed classes instead:
|
|
590
|
-
|
|
591
|
-
- `hc-btn-*` for Button
|
|
592
|
-
- `hc-badge-*` for Badge
|
|
593
|
-
- `hc-input-*` for Input
|
|
594
|
-
- `hc-checkbox-*` for Checkbox
|
|
595
|
-
- `hc-radio-*` for RadioButton
|
|
596
|
-
- `hc-dropdown-*` for Dropdown
|
|
597
|
-
- `hc-modal-*` for Modal
|
|
598
|
-
- `hc-chip-*` for Chip
|
|
599
|
-
- `hc-divider-*` for Divider
|
|
600
|
-
- `hc-section-*` for SectionHeader
|
|
601
|
-
- `hc-compact-*` for CompactCard
|
|
602
|
-
|
|
603
|
-
This prevents conflicts with consuming app's styles while ensuring styles are always applied.
|
|
604
|
-
|
|
605
|
-
### Building
|
|
221
|
+
## Styling
|
|
606
222
|
|
|
607
|
-
|
|
608
|
-
cd packages/ui
|
|
609
|
-
|
|
610
|
-
# Build once
|
|
611
|
-
pnpm build
|
|
223
|
+
Components use CSS variables for theming. Import the styles once in your app entry:
|
|
612
224
|
|
|
613
|
-
|
|
614
|
-
|
|
225
|
+
```tsx
|
|
226
|
+
// main.tsx or App.tsx
|
|
227
|
+
import '@hotelcard/ui/dist/index.css';
|
|
615
228
|
```
|
|
616
229
|
|
|
617
|
-
###
|
|
618
|
-
|
|
619
|
-
1. Build the package: `pnpm build`
|
|
620
|
-
2. In consuming app, reinstall: `npm install`
|
|
621
|
-
3. Restart dev server if needed
|
|
622
|
-
|
|
623
|
-
---
|
|
624
|
-
|
|
625
|
-
## Publishing to NPM
|
|
626
|
-
|
|
627
|
-
### Prerequisites
|
|
628
|
-
|
|
629
|
-
1. NPM account with access to `@hotelcard` organization
|
|
630
|
-
2. Logged in: `npm login`
|
|
230
|
+
### Customizing with CSS Variables
|
|
631
231
|
|
|
632
|
-
|
|
232
|
+
Override these variables in your app's CSS:
|
|
633
233
|
|
|
634
|
-
```
|
|
635
|
-
|
|
234
|
+
```css
|
|
235
|
+
:root {
|
|
236
|
+
/* Primary brand color */
|
|
237
|
+
--background-action-primary-idle: #C81E4C;
|
|
238
|
+
--background-action-primary-hover: #a3183d;
|
|
636
239
|
|
|
637
|
-
|
|
638
|
-
|
|
240
|
+
/* Text colors */
|
|
241
|
+
--content-general-primary: #1F2937;
|
|
242
|
+
--content-general-secondary: #6B7280;
|
|
639
243
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
# or
|
|
643
|
-
npm version minor # 0.0.2 -> 0.1.0
|
|
644
|
-
# or
|
|
645
|
-
npm version major # 0.0.2 -> 1.0.0
|
|
244
|
+
/* Border colors */
|
|
245
|
+
--border-general-divider: #D1D5DB;
|
|
646
246
|
|
|
647
|
-
|
|
648
|
-
|
|
247
|
+
/* Typography */
|
|
248
|
+
--font-primary: system-ui, -apple-system, sans-serif;
|
|
649
249
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
250
|
+
/* Border radius */
|
|
251
|
+
--radius-lg: 12px;
|
|
252
|
+
--radius-full: 9999px;
|
|
253
|
+
}
|
|
654
254
|
```
|
|
655
255
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
- `patch` (0.0.x): Bug fixes, style tweaks
|
|
659
|
-
- `minor` (0.x.0): New components, new features
|
|
660
|
-
- `major` (x.0.0): Breaking changes (API changes, removed props)
|
|
256
|
+
Components have built-in fallback values, so they work without custom CSS variables.
|
|
661
257
|
|
|
662
258
|
---
|
|
663
259
|
|
|
664
|
-
##
|
|
665
|
-
|
|
666
|
-
### hotelcard-app-v2 (Mobile App)
|
|
667
|
-
|
|
668
|
-
```tsx
|
|
669
|
-
// src/main.tsx
|
|
670
|
-
import '@hotelcard/ui/dist/index.css'; // REQUIRED
|
|
260
|
+
## Requirements
|
|
671
261
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
export type { ButtonProps, BadgeProps, InputProps } from '@hotelcard/ui';
|
|
675
|
-
|
|
676
|
-
// src/pages/SomePage.tsx
|
|
677
|
-
import { Button, Input } from '@/library';
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
### hotelcard-ui (Website)
|
|
681
|
-
|
|
682
|
-
```tsx
|
|
683
|
-
// src/main.tsx or app entry
|
|
684
|
-
import '@hotelcard/ui/dist/index.css'; // REQUIRED
|
|
685
|
-
|
|
686
|
-
// Use directly
|
|
687
|
-
import { Button, Badge, Modal } from '@hotelcard/ui';
|
|
688
|
-
```
|
|
689
|
-
|
|
690
|
-
### Updating the Package
|
|
691
|
-
|
|
692
|
-
```bash
|
|
693
|
-
# After a new version is published
|
|
694
|
-
npm update @hotelcard/ui
|
|
695
|
-
|
|
696
|
-
# Or for local development
|
|
697
|
-
rm -rf node_modules/.cache
|
|
698
|
-
npm install
|
|
699
|
-
```
|
|
262
|
+
- React 18+
|
|
263
|
+
- react-dom 18+
|
|
700
264
|
|
|
701
265
|
---
|
|
702
266
|
|
|
703
|
-
##
|
|
704
|
-
|
|
705
|
-
### "Components render without styles"
|
|
706
|
-
|
|
707
|
-
Make sure you imported the CSS:
|
|
708
|
-
```tsx
|
|
709
|
-
import '@hotelcard/ui/dist/index.css';
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
### "Module not found" errors
|
|
713
|
-
|
|
714
|
-
```bash
|
|
715
|
-
# Clear cache and reinstall
|
|
716
|
-
rm -rf node_modules/.cache
|
|
717
|
-
npm install
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
### "Types not found"
|
|
721
|
-
|
|
722
|
-
Make sure you're importing types with `type`:
|
|
723
|
-
```tsx
|
|
724
|
-
import type { ButtonProps } from '@hotelcard/ui';
|
|
725
|
-
```
|
|
726
|
-
|
|
727
|
-
### Local changes not reflecting
|
|
728
|
-
|
|
729
|
-
```bash
|
|
730
|
-
# In @hotelcard/ui
|
|
731
|
-
pnpm build
|
|
267
|
+
## License
|
|
732
268
|
|
|
733
|
-
|
|
734
|
-
npm install
|
|
735
|
-
# Restart dev server
|
|
736
|
-
```
|
|
269
|
+
MIT
|
|
737
270
|
|
|
738
271
|
---
|
|
739
272
|
|
|
740
|
-
##
|
|
741
|
-
|
|
742
|
-
When adding/modifying components:
|
|
743
|
-
|
|
744
|
-
- [ ] Uses prefixed CSS classes (not CSS modules)
|
|
745
|
-
- [ ] All CSS variables have fallback values
|
|
746
|
-
- [ ] Props are typed in `.types.ts`
|
|
747
|
-
- [ ] Exported from `src/index.ts`
|
|
748
|
-
- [ ] Build passes: `pnpm build`
|
|
749
|
-
- [ ] Tested in consuming app
|
|
750
|
-
- [ ] README updated if new component
|
|
273
|
+
## Maintained By
|
|
751
274
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
MIT
|
|
275
|
+
HotelCard AG - [hotelcard.ch](https://hotelcard.ch)
|