@furystack/shades-common-components 15.0.3 → 15.1.0

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.
Files changed (114) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/esm/components/button-group.d.ts +6 -5
  3. package/esm/components/button-group.d.ts.map +1 -1
  4. package/esm/components/button-group.js +5 -1
  5. package/esm/components/button-group.js.map +1 -1
  6. package/esm/components/button-group.spec.js +5 -0
  7. package/esm/components/button-group.spec.js.map +1 -1
  8. package/esm/components/button.d.ts +3 -2
  9. package/esm/components/button.d.ts.map +1 -1
  10. package/esm/components/button.js.map +1 -1
  11. package/esm/components/chip.d.ts +3 -2
  12. package/esm/components/chip.d.ts.map +1 -1
  13. package/esm/components/chip.js +6 -1
  14. package/esm/components/chip.js.map +1 -1
  15. package/esm/components/chip.spec.js +16 -0
  16. package/esm/components/chip.spec.js.map +1 -1
  17. package/esm/components/component-size.d.ts +6 -0
  18. package/esm/components/component-size.d.ts.map +1 -0
  19. package/esm/components/component-size.js +2 -0
  20. package/esm/components/component-size.js.map +1 -0
  21. package/esm/components/index.d.ts +2 -0
  22. package/esm/components/index.d.ts.map +1 -1
  23. package/esm/components/index.js +2 -0
  24. package/esm/components/index.js.map +1 -1
  25. package/esm/components/inputs/checkbox.d.ts +6 -0
  26. package/esm/components/inputs/checkbox.d.ts.map +1 -1
  27. package/esm/components/inputs/checkbox.js +47 -0
  28. package/esm/components/inputs/checkbox.js.map +1 -1
  29. package/esm/components/inputs/checkbox.spec.js +38 -0
  30. package/esm/components/inputs/checkbox.spec.js.map +1 -1
  31. package/esm/components/inputs/input-number.d.ts +6 -0
  32. package/esm/components/inputs/input-number.d.ts.map +1 -1
  33. package/esm/components/inputs/input-number.js +26 -0
  34. package/esm/components/inputs/input-number.js.map +1 -1
  35. package/esm/components/inputs/input-number.spec.js +38 -0
  36. package/esm/components/inputs/input-number.spec.js.map +1 -1
  37. package/esm/components/inputs/input.d.ts +7 -1
  38. package/esm/components/inputs/input.d.ts.map +1 -1
  39. package/esm/components/inputs/input.js +24 -1
  40. package/esm/components/inputs/input.js.map +1 -1
  41. package/esm/components/inputs/input.spec.js +38 -0
  42. package/esm/components/inputs/input.spec.js.map +1 -1
  43. package/esm/components/inputs/radio.d.ts +6 -0
  44. package/esm/components/inputs/radio.d.ts.map +1 -1
  45. package/esm/components/inputs/radio.js +37 -0
  46. package/esm/components/inputs/radio.js.map +1 -1
  47. package/esm/components/inputs/radio.spec.js +38 -0
  48. package/esm/components/inputs/radio.spec.js.map +1 -1
  49. package/esm/components/inputs/select.d.ts +6 -0
  50. package/esm/components/inputs/select.d.ts.map +1 -1
  51. package/esm/components/inputs/select.js +24 -0
  52. package/esm/components/inputs/select.js.map +1 -1
  53. package/esm/components/inputs/select.spec.js +22 -0
  54. package/esm/components/inputs/select.spec.js.map +1 -1
  55. package/esm/components/inputs/switch.d.ts +2 -1
  56. package/esm/components/inputs/switch.d.ts.map +1 -1
  57. package/esm/components/inputs/switch.js +14 -1
  58. package/esm/components/inputs/switch.js.map +1 -1
  59. package/esm/components/inputs/switch.spec.js +38 -0
  60. package/esm/components/inputs/switch.spec.js.map +1 -1
  61. package/esm/components/inputs/text-area.d.ts +6 -0
  62. package/esm/components/inputs/text-area.d.ts.map +1 -1
  63. package/esm/components/inputs/text-area.js +17 -0
  64. package/esm/components/inputs/text-area.js.map +1 -1
  65. package/esm/components/inputs/text-area.spec.js +38 -0
  66. package/esm/components/inputs/text-area.spec.js.map +1 -1
  67. package/esm/components/pagination.d.ts +3 -2
  68. package/esm/components/pagination.d.ts.map +1 -1
  69. package/esm/components/pagination.js.map +1 -1
  70. package/esm/components/rating.d.ts +2 -1
  71. package/esm/components/rating.d.ts.map +1 -1
  72. package/esm/components/rating.js.map +1 -1
  73. package/esm/components/route-breadcrumb.d.ts +46 -0
  74. package/esm/components/route-breadcrumb.d.ts.map +1 -0
  75. package/esm/components/route-breadcrumb.js +56 -0
  76. package/esm/components/route-breadcrumb.js.map +1 -0
  77. package/esm/components/route-breadcrumb.spec.d.ts +2 -0
  78. package/esm/components/route-breadcrumb.spec.d.ts.map +1 -0
  79. package/esm/components/route-breadcrumb.spec.js +186 -0
  80. package/esm/components/route-breadcrumb.spec.js.map +1 -0
  81. package/esm/components/timeline.d.ts +4 -0
  82. package/esm/components/timeline.d.ts.map +1 -1
  83. package/esm/components/timeline.js +77 -1
  84. package/esm/components/timeline.js.map +1 -1
  85. package/esm/components/timeline.spec.js +74 -0
  86. package/esm/components/timeline.spec.js.map +1 -1
  87. package/package.json +2 -2
  88. package/src/components/button-group.spec.tsx +6 -0
  89. package/src/components/button-group.tsx +10 -4
  90. package/src/components/button.tsx +2 -1
  91. package/src/components/chip.spec.tsx +24 -0
  92. package/src/components/chip.tsx +9 -2
  93. package/src/components/component-size.ts +5 -0
  94. package/src/components/index.ts +2 -0
  95. package/src/components/inputs/checkbox.spec.tsx +42 -0
  96. package/src/components/inputs/checkbox.tsx +55 -0
  97. package/src/components/inputs/input-number.spec.tsx +42 -0
  98. package/src/components/inputs/input-number.tsx +35 -0
  99. package/src/components/inputs/input.spec.tsx +42 -0
  100. package/src/components/inputs/input.tsx +34 -2
  101. package/src/components/inputs/radio.spec.tsx +42 -0
  102. package/src/components/inputs/radio.tsx +45 -0
  103. package/src/components/inputs/select.spec.tsx +26 -0
  104. package/src/components/inputs/select.tsx +32 -0
  105. package/src/components/inputs/switch.spec.tsx +42 -0
  106. package/src/components/inputs/switch.tsx +19 -2
  107. package/src/components/inputs/text-area.spec.tsx +42 -0
  108. package/src/components/inputs/text-area.tsx +25 -0
  109. package/src/components/pagination.tsx +2 -1
  110. package/src/components/rating.tsx +2 -1
  111. package/src/components/route-breadcrumb.spec.tsx +239 -0
  112. package/src/components/route-breadcrumb.tsx +83 -0
  113. package/src/components/timeline.spec.tsx +109 -0
  114. package/src/components/timeline.tsx +94 -1
@@ -3,6 +3,7 @@ import { Shade, createComponent } from '@furystack/shades'
3
3
  import { cssVariableTheme } from '../../services/css-variable-theme.js'
4
4
  import type { Palette } from '../../services/theme-provider-service.js'
5
5
  import { ThemeProviderService } from '../../services/theme-provider-service.js'
6
+ import type { ComponentSize } from '../component-size.js'
6
7
  import { FormService } from '../form.js'
7
8
 
8
9
  const emptyValidity = {} as ValidityState
@@ -13,7 +14,7 @@ export type InvalidInputValidationResult = { isValid: false; message: string }
13
14
 
14
15
  export type InputValidationResult = ValidInputValidationResult | InvalidInputValidationResult
15
16
 
16
- export interface TextInputProps extends PartialElement<HTMLInputElement> {
17
+ export interface TextInputProps extends Omit<PartialElement<HTMLInputElement>, 'size'> {
17
18
  /**
18
19
  * Callback that will be called when the input value changes
19
20
  */
@@ -36,6 +37,12 @@ export interface TextInputProps extends PartialElement<HTMLInputElement> {
36
37
  * The variant of the input
37
38
  */
38
39
  variant?: 'contained' | 'outlined'
40
+ /**
41
+ * The size of the input.
42
+ * @default 'medium'
43
+ */
44
+ size?: ComponentSize
45
+
39
46
  /**
40
47
  * The default color of the input (Error color will be used in case of invalid input value)
41
48
  */
@@ -223,8 +230,32 @@ export const Input = Shade<TextInputProps>({
223
230
  alignItems: 'center',
224
231
  fontSize: cssVariableTheme.typography.fontSize.lg,
225
232
  },
233
+
234
+ // Size: small
235
+ '&[data-size="small"] label': {
236
+ padding: `${cssVariableTheme.spacing.xs} ${cssVariableTheme.spacing.sm}`,
237
+ },
238
+ '&[data-size="small"] input': {
239
+ fontSize: cssVariableTheme.typography.fontSize.xs,
240
+ },
241
+ '&[data-size="small"] .startIcon, &[data-size="small"] .endIcon': {
242
+ fontSize: cssVariableTheme.typography.fontSize.md,
243
+ },
244
+
245
+ // Size: large
246
+ '&[data-size="large"] label': {
247
+ padding: `${cssVariableTheme.spacing.md} ${cssVariableTheme.spacing.lg}`,
248
+ fontSize: cssVariableTheme.typography.fontSize.sm,
249
+ },
250
+ '&[data-size="large"] input': {
251
+ fontSize: cssVariableTheme.typography.fontSize.md,
252
+ },
253
+ '&[data-size="large"] .startIcon, &[data-size="large"] .endIcon': {
254
+ fontSize: cssVariableTheme.typography.fontSize.xl,
255
+ },
226
256
  },
227
257
  render: ({ props, injector, useState, useDisposable, useHostProps, useRef }) => {
258
+ const { size: componentSize, ...nativeProps } = props
228
259
  const inputRef = useRef<HTMLInputElement>('formInput')
229
260
 
230
261
  useDisposable('form-registration', () => {
@@ -277,6 +308,7 @@ export const Input = Shade<TextInputProps>({
277
308
  const primaryColor = themeProvider.theme.palette[props.defaultColor || 'primary'].main
278
309
  useHostProps({
279
310
  'data-variant': props.variant || undefined,
311
+ 'data-size': componentSize && componentSize !== 'medium' ? componentSize : undefined,
280
312
  'data-disabled': props.disabled ? '' : undefined,
281
313
  'data-invalid': isInvalid ? '' : undefined,
282
314
  style: {
@@ -324,7 +356,7 @@ export const Input = Shade<TextInputProps>({
324
356
  setFocused(false)
325
357
  setValidity((ev.target as HTMLInputElement).validity)
326
358
  }}
327
- {...props}
359
+ {...nativeProps}
328
360
  style={props.style}
329
361
  {...(props.value !== undefined ? { value: props.value } : {})}
330
362
  />
@@ -270,4 +270,46 @@ describe('Radio', () => {
270
270
  })
271
271
  })
272
272
  })
273
+
274
+ describe('size', () => {
275
+ it('should not set data-size when size is not specified', async () => {
276
+ await usingAsync(new Injector(), async (injector) => {
277
+ const rootElement = document.getElementById('root') as HTMLDivElement
278
+ initializeShadeRoot({ injector, rootElement, jsxElement: <Radio value="opt1" /> })
279
+ await flushUpdates()
280
+ const el = document.querySelector('shade-radio') as HTMLElement
281
+ expect(el.getAttribute('data-size')).toBeNull()
282
+ })
283
+ })
284
+
285
+ it('should not set data-size for medium size (default)', async () => {
286
+ await usingAsync(new Injector(), async (injector) => {
287
+ const rootElement = document.getElementById('root') as HTMLDivElement
288
+ initializeShadeRoot({ injector, rootElement, jsxElement: <Radio value="opt1" size="medium" /> })
289
+ await flushUpdates()
290
+ const el = document.querySelector('shade-radio') as HTMLElement
291
+ expect(el.getAttribute('data-size')).toBeNull()
292
+ })
293
+ })
294
+
295
+ it('should set data-size="small" for small size', async () => {
296
+ await usingAsync(new Injector(), async (injector) => {
297
+ const rootElement = document.getElementById('root') as HTMLDivElement
298
+ initializeShadeRoot({ injector, rootElement, jsxElement: <Radio value="opt1" size="small" /> })
299
+ await flushUpdates()
300
+ const el = document.querySelector('shade-radio') as HTMLElement
301
+ expect(el.getAttribute('data-size')).toBe('small')
302
+ })
303
+ })
304
+
305
+ it('should set data-size="large" for large size', async () => {
306
+ await usingAsync(new Injector(), async (injector) => {
307
+ const rootElement = document.getElementById('root') as HTMLDivElement
308
+ initializeShadeRoot({ injector, rootElement, jsxElement: <Radio value="opt1" size="large" /> })
309
+ await flushUpdates()
310
+ const el = document.querySelector('shade-radio') as HTMLElement
311
+ expect(el.getAttribute('data-size')).toBe('large')
312
+ })
313
+ })
314
+ })
273
315
  })
@@ -3,6 +3,7 @@ import { Shade, createComponent } from '@furystack/shades'
3
3
  import { cssVariableTheme } from '../../services/css-variable-theme.js'
4
4
  import type { Palette } from '../../services/theme-provider-service.js'
5
5
  import { ThemeProviderService } from '../../services/theme-provider-service.js'
6
+ import type { ComponentSize } from '../component-size.js'
6
7
  import { FormService } from '../form.js'
7
8
 
8
9
  export type RadioProps = {
@@ -34,6 +35,11 @@ export type RadioProps = {
34
35
  * Whether the radio button is checked
35
36
  */
36
37
  checked?: boolean
38
+ /**
39
+ * The size of the radio button.
40
+ * @default 'medium'
41
+ */
42
+ size?: ComponentSize
37
43
  /**
38
44
  * Optional props for the label element
39
45
  */
@@ -116,6 +122,44 @@ export const Radio = Shade<RadioProps>({
116
122
  opacity: cssVariableTheme.action.disabledOpacity,
117
123
  cursor: 'not-allowed',
118
124
  },
125
+
126
+ // Size: small
127
+ '&[data-size="small"] label': {
128
+ fontSize: cssVariableTheme.typography.fontSize.xs,
129
+ },
130
+ '&[data-size="small"] .radio-control': {
131
+ width: '16px',
132
+ height: '16px',
133
+ },
134
+ '&[data-size="small"] input[type="radio"]': {
135
+ width: '16px',
136
+ height: '16px',
137
+ },
138
+ '&[data-size="small"] input[type="radio"]:checked::after': {
139
+ left: '4px',
140
+ top: '4px',
141
+ width: '8px',
142
+ height: '8px',
143
+ },
144
+
145
+ // Size: large
146
+ '&[data-size="large"] label': {
147
+ fontSize: cssVariableTheme.typography.fontSize.md,
148
+ },
149
+ '&[data-size="large"] .radio-control': {
150
+ width: '24px',
151
+ height: '24px',
152
+ },
153
+ '&[data-size="large"] input[type="radio"]': {
154
+ width: '24px',
155
+ height: '24px',
156
+ },
157
+ '&[data-size="large"] input[type="radio"]:checked::after': {
158
+ left: '6px',
159
+ top: '6px',
160
+ width: '12px',
161
+ height: '12px',
162
+ },
119
163
  },
120
164
  render: ({ props, injector, useDisposable, useHostProps, useRef }) => {
121
165
  const inputRef = useRef<HTMLInputElement>('formInput')
@@ -177,6 +221,7 @@ export const Radio = Shade<RadioProps>({
177
221
 
178
222
  const color = themeProvider.theme.palette[props.color || 'primary'].main
179
223
  useHostProps({
224
+ 'data-size': props.size && props.size !== 'medium' ? props.size : undefined,
180
225
  'data-disabled': isDisabled ? '' : undefined,
181
226
  style: { '--radio-color': color },
182
227
  })
@@ -1231,4 +1231,30 @@ describe('Select', () => {
1231
1231
  })
1232
1232
  })
1233
1233
  })
1234
+
1235
+ describe('size', () => {
1236
+ it('should not set data-size when size is not specified', async () => {
1237
+ await usingAsync(await renderSelect({ options: defaultOptions }), async ({ select }) => {
1238
+ expect(select.getAttribute('data-size')).toBeNull()
1239
+ })
1240
+ })
1241
+
1242
+ it('should not set data-size for medium size (default)', async () => {
1243
+ await usingAsync(await renderSelect({ options: defaultOptions, size: 'medium' }), async ({ select }) => {
1244
+ expect(select.getAttribute('data-size')).toBeNull()
1245
+ })
1246
+ })
1247
+
1248
+ it('should set data-size="small" for small size', async () => {
1249
+ await usingAsync(await renderSelect({ options: defaultOptions, size: 'small' }), async ({ select }) => {
1250
+ expect(select.getAttribute('data-size')).toBe('small')
1251
+ })
1252
+ })
1253
+
1254
+ it('should set data-size="large" for large size', async () => {
1255
+ await usingAsync(await renderSelect({ options: defaultOptions, size: 'large' }), async ({ select }) => {
1256
+ expect(select.getAttribute('data-size')).toBe('large')
1257
+ })
1258
+ })
1259
+ })
1234
1260
  })
@@ -3,6 +3,7 @@ import { Shade, createComponent } from '@furystack/shades'
3
3
  import { cssVariableTheme } from '../../services/css-variable-theme.js'
4
4
  import type { Palette } from '../../services/theme-provider-service.js'
5
5
  import { ThemeProviderService } from '../../services/theme-provider-service.js'
6
+ import type { ComponentSize } from '../component-size.js'
6
7
  import { FormService } from '../form.js'
7
8
  import { check, close } from '../icons/icon-definitions.js'
8
9
  import { Icon } from '../icons/icon.js'
@@ -47,6 +48,11 @@ export type SelectProps = {
47
48
  labelProps?: PartialElement<HTMLLabelElement>
48
49
  /** The visual variant of the select */
49
50
  variant?: 'contained' | 'outlined'
51
+ /**
52
+ * The size of the select.
53
+ * @default 'medium'
54
+ */
55
+ size?: ComponentSize
50
56
  /** The default color of the select */
51
57
  defaultColor?: keyof Palette
52
58
  /** The name attribute for form integration */
@@ -340,6 +346,31 @@ export const Select = Shade<SelectProps>({
340
346
  opacity: '0.85',
341
347
  lineHeight: '1.4',
342
348
  },
349
+
350
+ // Size: small
351
+ '&[data-size="small"] label': {
352
+ padding: `${cssVariableTheme.spacing.xs} ${cssVariableTheme.spacing.sm}`,
353
+ },
354
+ '&[data-size="small"] .select-value': {
355
+ fontSize: cssVariableTheme.typography.fontSize.xs,
356
+ },
357
+ '&[data-size="small"] .dropdown-item': {
358
+ padding: `6px ${cssVariableTheme.spacing.sm}`,
359
+ fontSize: cssVariableTheme.typography.fontSize.xs,
360
+ },
361
+
362
+ // Size: large
363
+ '&[data-size="large"] label': {
364
+ padding: `${cssVariableTheme.spacing.md} ${cssVariableTheme.spacing.lg}`,
365
+ fontSize: cssVariableTheme.typography.fontSize.sm,
366
+ },
367
+ '&[data-size="large"] .select-value': {
368
+ fontSize: cssVariableTheme.typography.fontSize.md,
369
+ },
370
+ '&[data-size="large"] .dropdown-item': {
371
+ padding: `${cssVariableTheme.spacing.sm} ${cssVariableTheme.spacing.lg}`,
372
+ fontSize: cssVariableTheme.typography.fontSize.md,
373
+ },
343
374
  },
344
375
  render: ({ props, injector, useState, useDisposable, useHostProps, useRef }) => {
345
376
  const selectRootRef = useRef<HTMLDivElement>('selectRoot')
@@ -394,6 +425,7 @@ export const Select = Shade<SelectProps>({
394
425
  const primaryColor = themeProvider.theme.palette[props.defaultColor || 'primary'].main
395
426
  useHostProps({
396
427
  'data-variant': props.variant || undefined,
428
+ 'data-size': props.size && props.size !== 'medium' ? props.size : undefined,
397
429
  'data-disabled': props.disabled ? '' : undefined,
398
430
  'data-multiple': isMultiple ? '' : undefined,
399
431
  'data-open': state.isOpen && !props.disabled ? '' : undefined,
@@ -407,4 +407,46 @@ describe('Switch', () => {
407
407
  })
408
408
  })
409
409
  })
410
+
411
+ describe('size', () => {
412
+ it('should not set data-size when size is not specified', async () => {
413
+ await usingAsync(new Injector(), async (injector) => {
414
+ const rootElement = document.getElementById('root') as HTMLDivElement
415
+ initializeShadeRoot({ injector, rootElement, jsxElement: <Switch /> })
416
+ await flushUpdates()
417
+ const el = document.querySelector('shade-switch') as HTMLElement
418
+ expect(el.getAttribute('data-size')).toBeNull()
419
+ })
420
+ })
421
+
422
+ it('should not set data-size for medium size (default)', async () => {
423
+ await usingAsync(new Injector(), async (injector) => {
424
+ const rootElement = document.getElementById('root') as HTMLDivElement
425
+ initializeShadeRoot({ injector, rootElement, jsxElement: <Switch size="medium" /> })
426
+ await flushUpdates()
427
+ const el = document.querySelector('shade-switch') as HTMLElement
428
+ expect(el.getAttribute('data-size')).toBeNull()
429
+ })
430
+ })
431
+
432
+ it('should set data-size="small" for small size', async () => {
433
+ await usingAsync(new Injector(), async (injector) => {
434
+ const rootElement = document.getElementById('root') as HTMLDivElement
435
+ initializeShadeRoot({ injector, rootElement, jsxElement: <Switch size="small" /> })
436
+ await flushUpdates()
437
+ const el = document.querySelector('shade-switch') as HTMLElement
438
+ expect(el.getAttribute('data-size')).toBe('small')
439
+ })
440
+ })
441
+
442
+ it('should set data-size="large" for large size', async () => {
443
+ await usingAsync(new Injector(), async (injector) => {
444
+ const rootElement = document.getElementById('root') as HTMLDivElement
445
+ initializeShadeRoot({ injector, rootElement, jsxElement: <Switch size="large" /> })
446
+ await flushUpdates()
447
+ const el = document.querySelector('shade-switch') as HTMLElement
448
+ expect(el.getAttribute('data-size')).toBe('large')
449
+ })
450
+ })
451
+ })
410
452
  })
@@ -3,6 +3,7 @@ import { Shade, createComponent } from '@furystack/shades'
3
3
  import { buildTransition, cssVariableTheme } from '../../services/css-variable-theme.js'
4
4
  import type { Palette } from '../../services/theme-provider-service.js'
5
5
  import { ThemeProviderService } from '../../services/theme-provider-service.js'
6
+ import type { ComponentSize } from '../component-size.js'
6
7
  import { FormService } from '../form.js'
7
8
 
8
9
  export type SwitchProps = {
@@ -41,7 +42,7 @@ export type SwitchProps = {
41
42
  /**
42
43
  * The size of the switch
43
44
  */
44
- size?: 'small' | 'medium'
45
+ size?: ComponentSize
45
46
  /**
46
47
  * Optional props for the label element
47
48
  */
@@ -116,6 +117,18 @@ export const Switch = Shade<SwitchProps>({
116
117
  height: '14px',
117
118
  },
118
119
 
120
+ // Large size
121
+ '&[data-size="large"] .switch-track': {
122
+ width: '48px',
123
+ height: '26px',
124
+ borderRadius: '13px',
125
+ },
126
+
127
+ '&[data-size="large"] .switch-thumb': {
128
+ width: '22px',
129
+ height: '22px',
130
+ },
131
+
119
132
  // Hidden input
120
133
  '& input[type="checkbox"]': {
121
134
  position: 'absolute',
@@ -143,6 +156,10 @@ export const Switch = Shade<SwitchProps>({
143
156
  transform: 'translateX(14px)',
144
157
  },
145
158
 
159
+ '&[data-size="large"] input[type="checkbox"]:checked + .switch-track .switch-thumb': {
160
+ transform: 'translateX(22px)',
161
+ },
162
+
146
163
  // Hover state
147
164
  '& .switch-control:hover .switch-track': {
148
165
  opacity: '0.7',
@@ -191,7 +208,7 @@ export const Switch = Shade<SwitchProps>({
191
208
  const color = themeProvider.theme.palette[props.color || 'primary'].main
192
209
  useHostProps({
193
210
  'data-disabled': props.disabled ? '' : undefined,
194
- 'data-size': props.size === 'small' ? 'small' : undefined,
211
+ 'data-size': props.size && props.size !== 'medium' ? props.size : undefined,
195
212
  style: { '--switch-color': color },
196
213
  })
197
214
 
@@ -297,4 +297,46 @@ describe('TextArea', () => {
297
297
  expect(computedStyle.marginBottom).toBe('1em')
298
298
  })
299
299
  })
300
+
301
+ describe('size', () => {
302
+ it('should not set data-size when size is not specified', async () => {
303
+ await usingAsync(new Injector(), async (injector) => {
304
+ const rootElement = document.getElementById('root') as HTMLDivElement
305
+ initializeShadeRoot({ injector, rootElement, jsxElement: <TextArea /> })
306
+ await flushUpdates()
307
+ const el = document.querySelector('shade-text-area') as HTMLElement
308
+ expect(el.getAttribute('data-size')).toBeNull()
309
+ })
310
+ })
311
+
312
+ it('should not set data-size for medium size (default)', async () => {
313
+ await usingAsync(new Injector(), async (injector) => {
314
+ const rootElement = document.getElementById('root') as HTMLDivElement
315
+ initializeShadeRoot({ injector, rootElement, jsxElement: <TextArea size="medium" /> })
316
+ await flushUpdates()
317
+ const el = document.querySelector('shade-text-area') as HTMLElement
318
+ expect(el.getAttribute('data-size')).toBeNull()
319
+ })
320
+ })
321
+
322
+ it('should set data-size="small" for small size', async () => {
323
+ await usingAsync(new Injector(), async (injector) => {
324
+ const rootElement = document.getElementById('root') as HTMLDivElement
325
+ initializeShadeRoot({ injector, rootElement, jsxElement: <TextArea size="small" /> })
326
+ await flushUpdates()
327
+ const el = document.querySelector('shade-text-area') as HTMLElement
328
+ expect(el.getAttribute('data-size')).toBe('small')
329
+ })
330
+ })
331
+
332
+ it('should set data-size="large" for large size', async () => {
333
+ await usingAsync(new Injector(), async (injector) => {
334
+ const rootElement = document.getElementById('root') as HTMLDivElement
335
+ initializeShadeRoot({ injector, rootElement, jsxElement: <TextArea size="large" /> })
336
+ await flushUpdates()
337
+ const el = document.querySelector('shade-text-area') as HTMLElement
338
+ expect(el.getAttribute('data-size')).toBe('large')
339
+ })
340
+ })
341
+ })
300
342
  })
@@ -1,12 +1,18 @@
1
1
  import type { PartialElement } from '@furystack/shades'
2
2
  import { createComponent, Shade } from '@furystack/shades'
3
3
  import { cssVariableTheme } from '../../services/css-variable-theme.js'
4
+ import type { ComponentSize } from '../component-size.js'
4
5
 
5
6
  export interface TextAreaProps extends PartialElement<HTMLTextAreaElement> {
6
7
  labelTitle?: string
7
8
  labelProps?: PartialElement<HTMLLabelElement>
8
9
  autofocus?: boolean
9
10
  variant?: 'contained' | 'outlined'
11
+ /**
12
+ * The size of the text area.
13
+ * @default 'medium'
14
+ */
15
+ size?: ComponentSize
10
16
  }
11
17
 
12
18
  export const TextArea = Shade<TextAreaProps>({
@@ -49,10 +55,29 @@ export const TextArea = Shade<TextAreaProps>({
49
55
  '&:focus-within .textarea-content': {
50
56
  boxShadow: `0px 3px 0px ${cssVariableTheme.palette.primary.main}`,
51
57
  },
58
+
59
+ // Size: small
60
+ '&[data-size="small"] label': {
61
+ padding: cssVariableTheme.spacing.sm,
62
+ fontSize: cssVariableTheme.typography.fontSize.xs,
63
+ },
64
+ '&[data-size="small"] .textarea-content': {
65
+ fontSize: cssVariableTheme.typography.fontSize.xs,
66
+ },
67
+
68
+ // Size: large
69
+ '&[data-size="large"] label': {
70
+ padding: cssVariableTheme.spacing.lg,
71
+ fontSize: cssVariableTheme.typography.fontSize.sm,
72
+ },
73
+ '&[data-size="large"] .textarea-content': {
74
+ fontSize: cssVariableTheme.typography.fontSize.md,
75
+ },
52
76
  },
53
77
  render: ({ props, useHostProps }) => {
54
78
  useHostProps({
55
79
  'data-variant': props.variant || undefined,
80
+ 'data-size': props.size && props.size !== 'medium' ? props.size : undefined,
56
81
  'data-disabled': props.disabled ? '' : undefined,
57
82
  })
58
83
 
@@ -3,6 +3,7 @@ import { Shade, createComponent } from '@furystack/shades'
3
3
  import { buildTransition, cssVariableTheme } from '../services/css-variable-theme.js'
4
4
  import { paletteMainColors } from '../services/palette-css-vars.js'
5
5
  import type { Palette } from '../services/theme-provider-service.js'
6
+ import type { ComponentSize } from './component-size.js'
6
7
 
7
8
  export type PaginationProps = PartialElement<HTMLElement> & {
8
9
  /** Total number of pages */
@@ -18,7 +19,7 @@ export type PaginationProps = PartialElement<HTMLElement> & {
18
19
  /** If true, the pagination is disabled */
19
20
  disabled?: boolean
20
21
  /** Size variant */
21
- size?: 'small' | 'medium' | 'large'
22
+ size?: ComponentSize
22
23
  /** Palette color */
23
24
  color?: keyof Palette
24
25
  }
@@ -2,6 +2,7 @@ import { Shade, createComponent } from '@furystack/shades'
2
2
  import { buildTransition, cssVariableTheme } from '../services/css-variable-theme.js'
3
3
  import type { Palette } from '../services/theme-provider-service.js'
4
4
  import { ThemeProviderService } from '../services/theme-provider-service.js'
5
+ import type { ComponentSize } from './component-size.js'
5
6
  import { Icon } from './icons/icon.js'
6
7
  import { star as starIcon, starOutline } from './icons/icon-definitions.js'
7
8
 
@@ -24,7 +25,7 @@ export type RatingProps = {
24
25
  /**
25
26
  * Size of the rating stars
26
27
  */
27
- size?: 'small' | 'medium' | 'large'
28
+ size?: ComponentSize
28
29
  /**
29
30
  * Whether the rating is disabled
30
31
  */