noph-ui 0.38.0 → 0.39.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.
package/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Noph UI
2
2
 
3
- Noph UI is a modern, powerful UI library for Svelte 5, fully aligned with the Material 3
4
- guidelines. Build stunning, consistent user interfaces with the efficiency and flexibility of
5
- Svelte and Google’s Material Design framework.
3
+ Noph UI is a modern, powerful UI library for Svelte, fully aligned with the Material 3 Expressive guidelines. Build stunning, consistent user interfaces with the efficiency and flexibility of Svelte and Google’s Material Design framework.
6
4
 
7
5
  ## Install
8
6
 
@@ -50,33 +48,43 @@ body {
50
48
  }
51
49
  ```
52
50
 
53
- ## Roadmap
54
-
55
- Beta
56
-
57
- - Auto complete
58
- - Badges
59
- - Buttons
60
- - Cards
61
- - Checkbox
62
- - Chips
63
- - DatePicker
64
- - Dialogs
65
- - Divider
66
- - Icon buttons
67
- - Lists
68
- - Loading indicator
69
- - Menus
70
- - Navigation Drawer
71
- - Navigation Rail
72
- - Progress indicators
73
- - Radio
74
- - Ripple
75
- - Segmented buttons
76
- - Select
77
- - Slider
78
- - Snackbar
79
- - Switch
80
- - Tabs
81
- - Text fields
82
- - Tooltips
51
+ ## Components
52
+
53
+ Live demos and usage examples for every component are available on [noph.dev](https://noph.dev).
54
+
55
+ **Actions**
56
+ [Buttons](https://noph.dev/components/button) ·
57
+ [Icon buttons](https://noph.dev/components/icon-button) ·
58
+ [Segmented buttons](https://noph.dev/components/segmented-button) ·
59
+ [Ripple](https://noph.dev/components/ripple)
60
+
61
+ **Inputs & selection**
62
+ [Autocomplete](https://noph.dev/components/autocomplete) ·
63
+ [Checkbox](https://noph.dev/components/checkbox) ·
64
+ [Chips](https://noph.dev/components/chip) ·
65
+ [DatePicker](https://noph.dev/components/date-picker) ·
66
+ [DateTimePicker](https://noph.dev/components/date-time-picker) ·
67
+ [Radio](https://noph.dev/components/radio) ·
68
+ [Select](https://noph.dev/components/select) ·
69
+ [Slider](https://noph.dev/components/slider) ·
70
+ [Switch](https://noph.dev/components/switch) ·
71
+ [Text fields](https://noph.dev/components/text-field)
72
+
73
+ **Navigation**
74
+ [Menus](https://noph.dev/components/menu) ·
75
+ [Navigation Drawer](https://noph.dev/components/navigation-drawer) ·
76
+ [Navigation Rail](https://noph.dev/components/navigation-rail) ·
77
+ [Tabs](https://noph.dev/components/tabs)
78
+
79
+ **Communication**
80
+ [Badges](https://noph.dev/components/badge) ·
81
+ [Dialogs](https://noph.dev/components/dialog) ·
82
+ [Loading indicator](https://noph.dev/components/loading-indicator) ·
83
+ [Progress indicators](https://noph.dev/components/progress) ·
84
+ [Snackbar](https://noph.dev/components/snackbar) ·
85
+ [Tooltips](https://noph.dev/components/tooltip)
86
+
87
+ **Containment**
88
+ [Cards](https://noph.dev/components/card) ·
89
+ [Divider](https://noph.dev/components/divider) ·
90
+ [Lists](https://noph.dev/components/list)
@@ -206,6 +206,7 @@
206
206
  style="position-anchor:--{uid};{widthProp}:{clientWidth}px"
207
207
  role="listbox"
208
208
  class={[!displayOptions.length && 'np-auto-complete-empty']}
209
+ coverAnchor={false}
209
210
  --np-menu-justify-self="none"
210
211
  --np-menu-position-area="bottom span-right"
211
212
  --np-menu-margin="2px 0"
@@ -475,13 +475,9 @@
475
475
 
476
476
  .np-calendar-cell.range-start::before {
477
477
  inset-inline-start: 50%;
478
- border-start-start-radius: var(--np-shape-corner-full);
479
- border-end-start-radius: var(--np-shape-corner-full);
480
478
  }
481
479
 
482
480
  .np-calendar-cell.range-end::before {
483
481
  inset-inline-end: 50%;
484
- border-start-end-radius: var(--np-shape-corner-full);
485
- border-end-end-radius: var(--np-shape-corner-full);
486
482
  }
487
483
  </style>
@@ -173,6 +173,15 @@
173
173
 
174
174
  .np-date-picker-stepper.hidden {
175
175
  visibility: hidden;
176
+ opacity: 0;
177
+ }
178
+
179
+ @media (prefers-reduced-motion: no-preference) {
180
+ .np-date-picker-stepper {
181
+ transition:
182
+ opacity var(--np-motion-expressive-default-effects),
183
+ visibility var(--np-motion-expressive-default-effects);
184
+ }
176
185
  }
177
186
 
178
187
  .np-date-picker-stepper {
@@ -28,7 +28,6 @@
28
28
  toISODate,
29
29
  today as getToday,
30
30
  } from './dateUtils.js'
31
- import { afterTwoFrames } from './enterAnimation.js'
32
31
  import type { DockedDatePickerProps, ISODate } from './types.js'
33
32
 
34
33
  let {
@@ -76,6 +75,7 @@
76
75
  let anchorElement = $state<HTMLDivElement>()
77
76
  let menuElement = $state<HTMLDivElement>()
78
77
  let mode = $state<'days' | 'months' | 'years'>('days')
78
+ let listMode = $state<'months' | 'years' | undefined>(undefined)
79
79
  let pending = $state<ISODate | undefined>(undefined)
80
80
  let focusedDay = $state<Date | undefined>(undefined)
81
81
  let inputElement = $state<HTMLInputElement | HTMLTextAreaElement>()
@@ -105,15 +105,16 @@
105
105
  let shortMonthNames = $derived(getMonthNames(locale, 'short'))
106
106
  let rowCount = $derived(getWeekRowCount(monthDate, week))
107
107
 
108
- let viewEnter = $state(false)
109
- let cancelViewEnter: (() => void) | undefined
110
-
111
108
  const setMode = (next: 'days' | 'months' | 'years') => {
112
109
  if (mode === next) return
113
110
  mode = next
114
- cancelViewEnter?.()
115
- viewEnter = true
116
- cancelViewEnter = afterTwoFrames(() => (viewEnter = false))
111
+ if (next !== 'days') listMode = next
112
+ }
113
+
114
+ const leaveList = async () => {
115
+ setMode('days')
116
+ await tick()
117
+ focusCalendar()
117
118
  }
118
119
 
119
120
  const formatValue = () => {
@@ -254,14 +255,14 @@
254
255
 
255
256
  const chooseMonth = (month: number) => {
256
257
  setMonth(new Date(monthDate.getFullYear(), month, 1))
257
- setMode('days')
258
+ leaveList()
258
259
  }
259
260
 
260
261
  const chooseYear = (year: number) => {
261
262
  // A year is offered as soon as any of its days is in range, so keeping the month the list was
262
263
  // opened from can land on a month where nothing at all is selectable.
263
264
  setMonth(new Date(year, clampMonthInYear(year, monthDate.getMonth(), minDate, maxDate), 1))
264
- setMode('days')
265
+ leaveList()
265
266
  }
266
267
  </script>
267
268
 
@@ -319,6 +320,7 @@
319
320
  style={`position-anchor:--${uid};`}
320
321
  --np-menu-justify-self="none"
321
322
  --np-menu-position-area="bottom span-right"
323
+ --np-menu-over-anchor-position-area="span-all span-right"
322
324
  --np-menu-margin="0"
323
325
  --np-menu-container-color="var(--np-docked-date-picker-container-color, var(--np-color-surface-container-high))"
324
326
  --np-menu-container-shape="var(--np-docked-date-picker-container-shape, var(--np-shape-corner-large))"
@@ -333,16 +335,14 @@
333
335
  }
334
336
  if (newState === 'closed') {
335
337
  setMode('days')
338
+ listMode = undefined
336
339
  focusedDay = undefined
337
340
  displayMonth = monthBeforeOpen
338
341
  }
339
342
  }}
340
343
  >
341
344
  {#if open}
342
- <div
343
- class={['np-docked-date-picker-container', mode !== 'days' && 'menu']}
344
- style:--np-calendar-rows={rowCount}
345
- >
345
+ <div class="np-docked-date-picker-container" style:--np-calendar-rows={rowCount}>
346
346
  <CalendarHeader
347
347
  {mode}
348
348
  monthLabel={shortMonthNames[monthDate.getMonth()]}
@@ -367,8 +367,8 @@
367
367
  ontoggleyears={() => setMode(mode === 'years' ? 'days' : 'years')}
368
368
  />
369
369
 
370
- {#if mode === 'days'}
371
- <div class={['np-docked-date-picker-body', viewEnter && 'entering']}>
370
+ <div class="np-docked-date-picker-views">
371
+ <div class="np-docked-date-picker-body" inert={mode !== 'days'}>
372
372
  <Calendar
373
373
  month={monthDate}
374
374
  selected={pendingDate}
@@ -395,33 +395,37 @@
395
395
  }}
396
396
  />
397
397
  </div>
398
- <div class="np-docked-date-picker-actions">
399
- <Button type="button" variant="text" onclick={() => closePicker()}>{cancelLabel}</Button
400
- >
401
- <Button type="button" variant="text" onclick={confirm}>{confirmLabel}</Button>
402
- </div>
403
- {:else}
404
- <div class={['np-docked-date-picker-menu-view', viewEnter && 'entering']}>
405
- <Divider --np-divider-color="var(--np-color-outline-variant)" />
406
- {#if mode === 'months'}
407
- <SelectionList
408
- id="{uid}-months"
409
- aria-label={selectMonthLabel}
410
- options={monthOptions}
411
- value={monthDate.getMonth()}
412
- onselect={chooseMonth}
413
- />
414
- {:else}
415
- <SelectionList
416
- id="{uid}-years"
417
- aria-label={selectYearLabel}
418
- options={yearOptions}
419
- value={monthDate.getFullYear()}
420
- onselect={chooseYear}
421
- />
422
- {/if}
423
- </div>
424
- {/if}
398
+
399
+ {#if listMode}
400
+ <div class={['np-docked-date-picker-menu-view', mode !== 'days' && 'open']}>
401
+ <div class="np-docked-date-picker-menu-shade">
402
+ <Divider --np-divider-color="var(--np-color-outline-variant)" />
403
+ {#if listMode === 'months'}
404
+ <SelectionList
405
+ id="{uid}-months"
406
+ aria-label={selectMonthLabel}
407
+ options={monthOptions}
408
+ value={monthDate.getMonth()}
409
+ onselect={chooseMonth}
410
+ />
411
+ {:else}
412
+ <SelectionList
413
+ id="{uid}-years"
414
+ aria-label={selectYearLabel}
415
+ options={yearOptions}
416
+ value={monthDate.getFullYear()}
417
+ onselect={chooseYear}
418
+ />
419
+ {/if}
420
+ </div>
421
+ </div>
422
+ {/if}
423
+ </div>
424
+
425
+ <div class="np-docked-date-picker-actions">
426
+ <Button type="button" variant="text" onclick={() => closePicker()}>{cancelLabel}</Button>
427
+ <Button type="button" variant="text" onclick={confirm}>{confirmLabel}</Button>
428
+ </div>
425
429
  </div>
426
430
  {/if}
427
431
  </Menu>
@@ -456,8 +460,12 @@
456
460
  margin-inline: 0.75rem;
457
461
  }
458
462
 
463
+ .np-docked-date-picker-views {
464
+ position: relative;
465
+ margin-top: 1.875rem;
466
+ }
467
+
459
468
  .np-docked-date-picker-body {
460
- padding-top: 1.875rem;
461
469
  padding-inline: 0.75rem;
462
470
  }
463
471
 
@@ -476,45 +484,61 @@
476
484
  height: 2.25rem;
477
485
  }
478
486
 
479
- .np-docked-date-picker-container.menu {
480
- padding-bottom: 1.25rem;
481
- }
482
-
483
487
  .np-docked-date-picker-menu-view {
484
- display: flex;
485
- flex-direction: column;
488
+ position: absolute;
489
+ inset: 0;
486
490
  overflow: hidden;
487
- margin-top: 1.875rem;
488
- height: calc(2.5rem + var(--np-calendar-rows) * 3rem);
491
+ visibility: hidden;
489
492
  }
490
493
 
491
- .np-docked-date-picker-menu-view :global(.np-date-picker-selection-list) {
492
- flex: 1;
493
- min-height: 0;
494
+ .np-docked-date-picker-menu-view.open {
495
+ visibility: visible;
494
496
  }
495
497
 
496
- .np-docked-date-picker-menu-view.entering {
497
- height: 0;
498
- margin-top: 0;
498
+ .np-docked-date-picker-menu-shade {
499
+ display: flex;
500
+ flex-direction: column;
501
+ height: 100%;
502
+ translate: 0 -100%;
499
503
  opacity: 0.6;
500
- transition: none;
504
+ background-color: var(
505
+ --np-docked-date-picker-container-color,
506
+ var(--np-color-surface-container-high)
507
+ );
501
508
  }
502
509
 
503
- .np-docked-date-picker-body.entering {
504
- opacity: 0.6;
505
- transition: none;
510
+ .np-docked-date-picker-menu-view.open .np-docked-date-picker-menu-shade {
511
+ translate: 0 0;
512
+ opacity: 1;
513
+ }
514
+
515
+ .np-docked-date-picker-menu-view :global(.np-date-picker-selection-list) {
516
+ flex: 1;
517
+ min-height: 0;
506
518
  }
507
519
 
508
520
  @media (prefers-reduced-motion: no-preference) {
509
521
  .np-docked-date-picker-menu-view {
522
+ transition: visibility var(--np-motion-expressive-default-effects);
523
+ }
524
+
525
+ .np-docked-date-picker-menu-shade {
510
526
  transition:
511
- height var(--np-motion-expressive-default-effects),
512
- margin-top var(--np-motion-expressive-default-effects),
527
+ translate var(--np-motion-expressive-default-effects),
528
+ opacity var(--np-motion-expressive-fast-effects);
529
+ }
530
+
531
+ .np-docked-date-picker-menu-view.open .np-docked-date-picker-menu-shade {
532
+ transition:
533
+ translate var(--np-motion-expressive-default-effects),
513
534
  opacity var(--np-motion-expressive-default-effects);
514
535
  }
515
536
 
516
- .np-docked-date-picker-body {
517
- transition: opacity var(--np-motion-expressive-default-effects);
537
+ @starting-style {
538
+ .np-docked-date-picker-menu-view.open .np-docked-date-picker-menu-shade {
539
+ translate: 0 -100%;
540
+ opacity: 0.6;
541
+ }
518
542
  }
519
543
  }
520
544