@inglorious/ui 9.8.2 → 9.10.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 (39) hide show
  1. package/package.json +28 -3
  2. package/src/controls/chip-picker/chip-picker.stories.js +22 -0
  3. package/src/controls/chip-picker/index.js +3 -0
  4. package/src/controls/chip-picker/style.css +26 -0
  5. package/src/controls/chip-picker/template.js +53 -0
  6. package/src/controls/combobox/combobox.stories.js +14 -0
  7. package/src/controls/combobox/handlers.js +87 -20
  8. package/src/controls/combobox/helpers.js +50 -4
  9. package/src/controls/combobox/index.js +1 -0
  10. package/src/controls/combobox/template.js +13 -14
  11. package/src/controls/controls.css +3 -2
  12. package/src/data-display/data-display.css +1 -0
  13. package/src/data-display/lightbox/index.js +3 -0
  14. package/src/data-display/lightbox/lightbox.stories.js +25 -0
  15. package/src/data-display/lightbox/style.css +97 -0
  16. package/src/data-display/lightbox/template.js +153 -0
  17. package/src/feedback/confirm-dialog/confirm-dialog.stories.js +33 -0
  18. package/src/feedback/confirm-dialog/index.js +3 -0
  19. package/src/feedback/confirm-dialog/style.css +16 -0
  20. package/src/feedback/confirm-dialog/template.js +70 -0
  21. package/src/feedback/crop-dialog/index.js +3 -0
  22. package/src/feedback/crop-dialog/style.css +43 -0
  23. package/src/feedback/crop-dialog/template.js +82 -0
  24. package/src/feedback/dialog/style.css +2 -1
  25. package/src/feedback/dialog/template.js +5 -2
  26. package/src/feedback/feedback.css +3 -0
  27. package/src/feedback/load-status/index.js +3 -0
  28. package/src/feedback/load-status/load-status.stories.js +16 -0
  29. package/src/feedback/load-status/style.css +10 -0
  30. package/src/feedback/load-status/template.js +43 -0
  31. package/src/index.js +5 -0
  32. package/src/shared/icons.js +12 -0
  33. package/types/controls/chip-picker.d.ts +23 -0
  34. package/types/controls/combobox.d.ts +38 -2
  35. package/types/data-display/lightbox.d.ts +35 -0
  36. package/types/feedback/confirm-dialog.d.ts +34 -0
  37. package/types/feedback/crop-dialog.d.ts +35 -0
  38. package/types/feedback/load-status.d.ts +22 -0
  39. package/types/index.d.ts +17 -0
@@ -0,0 +1,97 @@
1
+ .iw-lightbox {
2
+ width: 100vw;
3
+ max-width: none;
4
+ height: 100dvh;
5
+ max-height: none;
6
+ margin: 0;
7
+ padding: var(--iw-space-4);
8
+ border: none;
9
+ background: #0c0c0c;
10
+ color: #fff;
11
+
12
+ &[open] {
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ }
17
+
18
+ &::backdrop {
19
+ background: #0c0c0c;
20
+ }
21
+
22
+ .iw-button.iw-lightbox-close {
23
+ position: absolute;
24
+ top: calc(var(--iw-space-3) + var(--safe-area-top, 0px));
25
+ inset-inline-end: var(--iw-space-3);
26
+ z-index: 1;
27
+ width: 2.5rem;
28
+ height: 2.5rem;
29
+ background: color-mix(in srgb, #fff 14%, transparent);
30
+ color: #fff;
31
+
32
+ &:hover {
33
+ background: color-mix(in srgb, #fff 24%, transparent);
34
+ }
35
+ }
36
+
37
+ .iw-button.iw-lightbox-nav {
38
+ position: absolute;
39
+ top: 50%;
40
+ transform: translateY(-50%);
41
+ z-index: 1;
42
+ width: 2.5rem;
43
+ height: 2.5rem;
44
+ background: color-mix(in srgb, #000 45%, transparent);
45
+ color: #fff;
46
+
47
+ &:hover {
48
+ background: color-mix(in srgb, #000 62%, transparent);
49
+ }
50
+
51
+ &:dir(rtl) .iw-icon {
52
+ scale: -1 1;
53
+ }
54
+ }
55
+ }
56
+
57
+ .iw-lightbox-stage {
58
+ position: relative;
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ width: min(100%, 56rem);
63
+ height: min(100%, 90dvh);
64
+ user-select: none;
65
+ touch-action: pan-y;
66
+ }
67
+
68
+ .iw-lightbox-image {
69
+ display: block;
70
+ max-width: 100%;
71
+ max-height: 100%;
72
+ width: auto;
73
+ height: auto;
74
+ object-fit: contain;
75
+ border-radius: var(--iw-radius-xl);
76
+ pointer-events: none;
77
+ }
78
+
79
+ .iw-lightbox-previous {
80
+ inset-inline-start: var(--iw-space-2);
81
+ }
82
+
83
+ .iw-lightbox-next {
84
+ inset-inline-end: var(--iw-space-2);
85
+ }
86
+
87
+ .iw-lightbox-counter {
88
+ position: absolute;
89
+ inset-inline-end: var(--iw-space-3);
90
+ bottom: var(--iw-space-2);
91
+ padding: 0.125rem 0.5rem;
92
+ border-radius: 999px;
93
+ background: color-mix(in srgb, #000 50%, transparent);
94
+ color: #fff;
95
+ font-size: var(--iw-font-size-xs);
96
+ font-variant-numeric: tabular-nums;
97
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * @typedef {import('../../../types/data-display/lightbox').LightboxProps} LightboxProps
3
+ * @typedef {import('../../../types/data-display/lightbox').LightboxImage} LightboxImage
4
+ * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
5
+ */
6
+
7
+ import { html, nothing } from "@inglorious/web"
8
+ import { ref } from "@inglorious/web/directives/ref"
9
+ import { when } from "@inglorious/web/directives/when"
10
+
11
+ import { IconButton } from "../../controls/icon-button/index.js"
12
+ import { chevronLeft, chevronRight, close } from "../../shared/icons.js"
13
+ import { syncDialog } from "../../shared/top-layer.js"
14
+ import { Icon } from "../icon/index.js"
15
+
16
+ const SWIPE_PX = 40
17
+ const NEXT = 1
18
+ const PREVIOUS = -1
19
+
20
+ /**
21
+ * Where each lightbox's current swipe started.
22
+ * @type {WeakMap<HTMLDialogElement, number>}
23
+ */
24
+ const swipeStarts = new WeakMap()
25
+
26
+ /**
27
+ * Renders images full screen in a modal `<dialog>`, one at a time. With more
28
+ * than one, the arrows, the arrow keys and a horizontal swipe step through
29
+ * them, following the writing direction. Esc, the close button and a press
30
+ * outside the image close it.
31
+ * @param {LightboxProps} props
32
+ * @returns {TemplateResult | typeof nothing}
33
+ */
34
+ export function render(props) {
35
+ const {
36
+ id,
37
+ images = [],
38
+ index = 0,
39
+ isOpen = false,
40
+ label = "Photos",
41
+ closeLabel = "Close",
42
+ previousLabel = "Previous photo",
43
+ nextLabel = "Next photo",
44
+ className = "",
45
+ onStep,
46
+ onClose,
47
+ } = props
48
+
49
+ const total = images.length
50
+ if (!total) return nothing
51
+
52
+ const current = ((index % total) + total) % total
53
+ const image = toImage(images[current])
54
+ const isNavigable = total > 1 && Boolean(onStep)
55
+
56
+ return html`<dialog
57
+ id=${id || nothing}
58
+ class=${["iw-lightbox", className].filter(Boolean).join(" ")}
59
+ aria-label=${label}
60
+ @click=${(event) => {
61
+ if (event.target === event.currentTarget) onClose?.()
62
+ }}
63
+ @cancel=${(event) => {
64
+ if (!onClose) event.preventDefault()
65
+ }}
66
+ @keydown=${(event) => {
67
+ if (!isNavigable) return
68
+ const step = arrowStep(event)
69
+ if (!step) return
70
+ event.preventDefault()
71
+ onStep(step)
72
+ }}
73
+ @pointerdown=${(event) => {
74
+ swipeStarts.set(event.currentTarget, event.clientX)
75
+ }}
76
+ @pointerup=${(event) => {
77
+ const startX = swipeStarts.get(event.currentTarget)
78
+ swipeStarts.delete(event.currentTarget)
79
+ if (!isNavigable || startX == null) return
80
+ const distance = event.clientX - startX
81
+ if (Math.abs(distance) < SWIPE_PX) return
82
+ const towardsEnd = isRtl(event.currentTarget)
83
+ ? distance > 0
84
+ : distance < 0
85
+ onStep(towardsEnd ? NEXT : PREVIOUS)
86
+ }}
87
+ @pointercancel=${(event) => swipeStarts.delete(event.currentTarget)}
88
+ ${ref((element) => syncDialog(element, { isOpen, onClose }))}
89
+ >
90
+ ${when(onClose, () =>
91
+ IconButton.render({
92
+ icon: Icon.render({ icon: close, size: "lg" }),
93
+ variant: "ghost",
94
+ shape: "round",
95
+ ariaLabel: closeLabel,
96
+ className: "iw-lightbox-close",
97
+ onClick: onClose,
98
+ }),
99
+ )}
100
+ <div class="iw-lightbox-stage">
101
+ <img
102
+ class="iw-lightbox-image"
103
+ src=${image.src}
104
+ alt=${image.alt ?? ""}
105
+ draggable="false"
106
+ />
107
+ ${when(
108
+ isNavigable,
109
+ () => html`
110
+ ${IconButton.render({
111
+ icon: Icon.render({ icon: chevronLeft, size: "lg" }),
112
+ variant: "ghost",
113
+ shape: "round",
114
+ ariaLabel: previousLabel,
115
+ className: "iw-lightbox-nav iw-lightbox-previous",
116
+ onClick: () => onStep(PREVIOUS),
117
+ })}
118
+ ${IconButton.render({
119
+ icon: Icon.render({ icon: chevronRight, size: "lg" }),
120
+ variant: "ghost",
121
+ shape: "round",
122
+ ariaLabel: nextLabel,
123
+ className: "iw-lightbox-nav iw-lightbox-next",
124
+ onClick: () => onStep(NEXT),
125
+ })}
126
+ <div class="iw-lightbox-counter" aria-hidden="true">
127
+ ${current + 1}/${total}
128
+ </div>
129
+ `,
130
+ )}
131
+ </div>
132
+ </dialog>`
133
+ }
134
+
135
+ /**
136
+ * @param {string | LightboxImage} image
137
+ * @returns {LightboxImage}
138
+ */
139
+ function toImage(image) {
140
+ return typeof image === "string" ? { src: image } : image
141
+ }
142
+
143
+ function arrowStep(event) {
144
+ const towardsEnd = isRtl(event.currentTarget) ? "ArrowLeft" : "ArrowRight"
145
+ const towardsStart = isRtl(event.currentTarget) ? "ArrowRight" : "ArrowLeft"
146
+ if (event.key === towardsEnd) return NEXT
147
+ if (event.key === towardsStart) return PREVIOUS
148
+ return 0
149
+ }
150
+
151
+ function isRtl(element) {
152
+ return getComputedStyle(element).direction === "rtl"
153
+ }
@@ -0,0 +1,33 @@
1
+ import { createRender } from "../../stories/notify-story.js"
2
+ import { ConfirmDialog } from "."
3
+
4
+ export default {
5
+ title: "Feedback/ConfirmDialog",
6
+ tags: ["autodocs"],
7
+ render: createRender(ConfirmDialog),
8
+ argTypes: {
9
+ tone: { control: "select", options: ["danger", "primary"] },
10
+ isBusy: { control: "boolean" },
11
+ onConfirm: { action: "onConfirm" },
12
+ onCancel: { action: "onCancel" },
13
+ },
14
+ }
15
+
16
+ export const Danger = {
17
+ args: {
18
+ isOpen: true,
19
+ message: "Delete this event? This cannot be undone.",
20
+ confirmLabel: "Delete",
21
+ busyLabel: "Deleting…",
22
+ cancelLabel: "Keep it",
23
+ },
24
+ }
25
+
26
+ export const Primary = {
27
+ args: {
28
+ isOpen: true,
29
+ tone: "primary",
30
+ message: "Publish this event now?",
31
+ confirmLabel: "Publish",
32
+ },
33
+ }
@@ -0,0 +1,3 @@
1
+ import * as renderers from "./template.js"
2
+
3
+ export const ConfirmDialog = { ...renderers }
@@ -0,0 +1,16 @@
1
+ .iw-confirm-dialog-actions {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--iw-space-2);
5
+ width: 100%;
6
+
7
+ > * {
8
+ flex: 1;
9
+ }
10
+ }
11
+
12
+ @media (min-width: 48rem) {
13
+ .iw-confirm-dialog-actions {
14
+ flex-direction: row;
15
+ }
16
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @typedef {import('../../../types/feedback/confirm-dialog').ConfirmDialogProps} ConfirmDialogProps
3
+ * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
4
+ */
5
+
6
+ import { html } from "@inglorious/web"
7
+
8
+ import { Button } from "../../controls/button/index.js"
9
+ import { Dialog } from "../dialog/index.js"
10
+
11
+ /**
12
+ * Renders a sheet asking to confirm an action, with the confirm button first.
13
+ * While `isBusy` it cannot be dismissed, and the confirm button shows
14
+ * `busyLabel`.
15
+ * @param {ConfirmDialogProps} props
16
+ * @returns {TemplateResult}
17
+ */
18
+ export function render(props) {
19
+ const {
20
+ id,
21
+ isOpen = false,
22
+ message,
23
+ children,
24
+ confirmLabel,
25
+ busyLabel = confirmLabel,
26
+ cancelLabel = "Cancel",
27
+ closeLabel,
28
+ tone = "danger",
29
+ isBusy = false,
30
+ isDisabled = false,
31
+ className = "",
32
+ onConfirm,
33
+ onCancel,
34
+ } = props
35
+
36
+ const isDanger = tone === "danger"
37
+
38
+ return Dialog.render({
39
+ id,
40
+ isOpen,
41
+ variant: "sheet",
42
+ size: "sm",
43
+ description: message,
44
+ children,
45
+ className: ["iw-confirm-dialog", className].filter(Boolean).join(" "),
46
+ closeLabel,
47
+ actions: html`<div class="iw-confirm-dialog-actions">
48
+ ${Button.render({
49
+ children: isBusy ? busyLabel : confirmLabel,
50
+ color: isDanger ? "error" : "primary",
51
+ variant: isDanger ? "outline" : "default",
52
+ shape: "pill",
53
+ size: "md",
54
+ isDisabled: isBusy || isDisabled,
55
+ onClick: () => {
56
+ if (!isBusy && !isDisabled) onConfirm?.()
57
+ },
58
+ })}
59
+ ${Button.render({
60
+ children: cancelLabel,
61
+ variant: "outline",
62
+ shape: "pill",
63
+ size: "md",
64
+ isDisabled: isBusy,
65
+ onClick: onCancel,
66
+ })}
67
+ </div>`,
68
+ onClose: isBusy ? undefined : onCancel,
69
+ })
70
+ }
@@ -0,0 +1,3 @@
1
+ import * as renderers from "./template.js"
2
+
3
+ export const CropDialog = { ...renderers }
@@ -0,0 +1,43 @@
1
+ .iw-dialog.iw-crop-dialog {
2
+ width: min(48rem, calc(100vw - 2 * var(--iw-space-4)));
3
+ min-width: 0;
4
+ max-width: none;
5
+ max-height: calc(
6
+ 100dvh - 2 * var(--iw-space-4) - var(--safe-area-top, 0px) -
7
+ var(--safe-area-bottom, 0px)
8
+ );
9
+ overflow-x: hidden;
10
+ overflow-y: auto;
11
+ overscroll-behavior: contain;
12
+ }
13
+
14
+ .iw-crop-dialog-viewport {
15
+ width: 100%;
16
+ --iw-image-cropper-max-height: 55dvh;
17
+ }
18
+
19
+ @media (max-height: 32rem) {
20
+ .iw-crop-dialog-viewport {
21
+ --iw-image-cropper-max-height: 35dvh;
22
+ }
23
+ }
24
+
25
+ .iw-crop-dialog-error {
26
+ margin: var(--iw-space-3) 0 0;
27
+ }
28
+
29
+ .iw-crop-dialog-actions {
30
+ display: flex;
31
+ justify-content: flex-end;
32
+ align-items: center;
33
+ gap: var(--iw-space-2);
34
+ width: 100%;
35
+
36
+ .iw-button {
37
+ flex: 0 1 auto;
38
+ min-width: 0;
39
+ padding-inline: var(--iw-space-3);
40
+ font-size: var(--iw-font-size-sm);
41
+ white-space: nowrap;
42
+ }
43
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @typedef {import('../../../types/feedback/crop-dialog').CropDialogProps} CropDialogProps
3
+ * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
4
+ * @typedef {import('@inglorious/web').Api} Api
5
+ */
6
+
7
+ import { html } from "@inglorious/web"
8
+ import { when } from "@inglorious/web/directives/when"
9
+
10
+ import { Button } from "../../controls/button/index.js"
11
+ import { Alert } from "../alert/index.js"
12
+ import { Dialog } from "../dialog/index.js"
13
+
14
+ /**
15
+ * Renders an ImageCropper entity in a dialog, with cancel and apply buttons.
16
+ * Apply stays disabled until the image has loaded, and passes the cropper's
17
+ * `rect`.
18
+ * @param {CropDialogProps} props
19
+ * @param {Api} api
20
+ * @returns {TemplateResult}
21
+ */
22
+ export function render(props, api) {
23
+ const {
24
+ id,
25
+ isOpen = false,
26
+ cropperId,
27
+ title,
28
+ description,
29
+ applyLabel = "Apply",
30
+ savingLabel = "Saving…",
31
+ cancelLabel = "Cancel",
32
+ closeLabel,
33
+ loadErrorText = "The image could not be loaded.",
34
+ error,
35
+ isSaving = false,
36
+ className = "",
37
+ onCancel,
38
+ onApply,
39
+ } = props
40
+
41
+ const cropper = api.getEntity(cropperId)
42
+ const isLoaded = Boolean(cropper?.naturalWidth)
43
+ const shownError = error || (cropper?.hasError ? loadErrorText : null)
44
+
45
+ return Dialog.render({
46
+ id,
47
+ isOpen,
48
+ title,
49
+ description,
50
+ closeLabel,
51
+ className: ["iw-crop-dialog", className].filter(Boolean).join(" "),
52
+ onClose: onCancel,
53
+ children: html`
54
+ <div class="iw-crop-dialog-viewport">${api.render(cropperId)}</div>
55
+ ${when(shownError, () =>
56
+ Alert.render({
57
+ severity: "error",
58
+ className: "iw-crop-dialog-error",
59
+ children: shownError,
60
+ }),
61
+ )}
62
+ `,
63
+ actions: html`<div class="iw-crop-dialog-actions">
64
+ ${Button.render({
65
+ children: cancelLabel,
66
+ variant: "ghost",
67
+ shape: "pill",
68
+ size: "sm",
69
+ isDisabled: isSaving,
70
+ onClick: onCancel,
71
+ })}
72
+ ${Button.render({
73
+ children: isSaving ? savingLabel : applyLabel,
74
+ color: "primary",
75
+ shape: "pill",
76
+ size: "sm",
77
+ isDisabled: isSaving || !isLoaded,
78
+ onClick: () => onApply?.(cropper.rect),
79
+ })}
80
+ </div>`,
81
+ })
82
+ }
@@ -43,8 +43,9 @@
43
43
  font-size: 1.1rem;
44
44
  }
45
45
 
46
- .iw-dialog-close {
46
+ .iw-dialog-header .iw-dialog-close {
47
47
  flex-shrink: 0;
48
+ margin-inline-start: auto;
48
49
  }
49
50
 
50
51
  .iw-dialog-description {
@@ -53,6 +53,9 @@ export function render(props) {
53
53
 
54
54
  const titleId = id && title ? `${id}-title` : ""
55
55
  const descriptionId = id && description ? `${id}-description` : ""
56
+ // Without a title, the description is what the dialog asks: its name.
57
+ const labelId = titleId || descriptionId
58
+ const describedById = titleId ? descriptionId : ""
56
59
  const onBackdrop = onBackdropClick ?? onClose
57
60
 
58
61
  return html`<dialog
@@ -63,8 +66,8 @@ export function render(props) {
63
66
  "iw-dialog-sheet": variant === "sheet",
64
67
  ...extraClasses,
65
68
  })}
66
- aria-labelledby=${titleId || nothing}
67
- aria-describedby=${descriptionId || nothing}
69
+ aria-labelledby=${labelId || nothing}
70
+ aria-describedby=${describedById || nothing}
68
71
  @pointerdown=${(event) => {
69
72
  backdropPresses.set(
70
73
  event.currentTarget,
@@ -1,6 +1,9 @@
1
1
  @import "./alert/style.css";
2
2
  @import "./backdrop/style.css";
3
+ @import "./confirm-dialog/style.css";
4
+ @import "./crop-dialog/style.css";
3
5
  @import "./dialog/style.css";
6
+ @import "./load-status/style.css";
4
7
  @import "./progress/style.css";
5
8
  @import "./skeleton/style.css";
6
9
  @import "./snackbar/style.css";
@@ -0,0 +1,3 @@
1
+ import * as renderers from "./template.js"
2
+
3
+ export const LoadStatus = { ...renderers }
@@ -0,0 +1,16 @@
1
+ import { createRender } from "../../stories/notify-story.js"
2
+ import { LoadStatus } from "."
3
+
4
+ export default {
5
+ title: "Feedback/LoadStatus",
6
+ tags: ["autodocs"],
7
+ render: createRender(LoadStatus),
8
+ }
9
+
10
+ export const Loading = { args: { isLoading: true } }
11
+
12
+ export const Empty = { args: { isEmpty: true, emptyText: "No events yet." } }
13
+
14
+ export const Error = {
15
+ args: { isError: true, error: "The events could not be loaded." },
16
+ }
@@ -0,0 +1,10 @@
1
+ .iw-load-status {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: center;
5
+ grid-column: 1 / -1;
6
+ padding: var(--iw-space-10) 0;
7
+ text-align: center;
8
+ font-size: var(--iw-font-size-sm);
9
+ color: var(--iw-color-text-muted, var(--iw-color-text));
10
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @typedef {import('../../../types/feedback/load-status').LoadStatusProps} LoadStatusProps
3
+ * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
4
+ */
5
+
6
+ import { html, nothing } from "@inglorious/web"
7
+
8
+ import { Progress } from "../progress/index.js"
9
+
10
+ /**
11
+ * Renders where a load stands instead of what it loads: an error, a loader or
12
+ * an empty message, in that order of precedence. Otherwise it renders
13
+ * `content`, a function so that it runs only once there is something to show.
14
+ * @param {LoadStatusProps} props
15
+ * @returns {import('../../../types/shared/renderable').Renderable}
16
+ */
17
+ export function render(props) {
18
+ const {
19
+ isError = false,
20
+ error,
21
+ isLoading = false,
22
+ loader,
23
+ loadingLabel = "Loading…",
24
+ isEmpty = false,
25
+ emptyText,
26
+ content,
27
+ className = "",
28
+ } = props
29
+
30
+ const classes = ["iw-load-status", className].filter(Boolean).join(" ")
31
+
32
+ if (isError) return html`<div class=${classes} role="alert">${error}</div>`
33
+ if (isLoading) {
34
+ return html`<div class=${classes} role="status">
35
+ ${
36
+ loader ??
37
+ Progress.render({ variant: "circular", ariaLabel: loadingLabel })
38
+ }
39
+ </div>`
40
+ }
41
+ if (isEmpty) return html`<div class=${classes}>${emptyText}</div>`
42
+ return content ? content() : nothing
43
+ }
package/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { Button } from "./controls/button/index.js"
2
2
  export { ButtonGroup } from "./controls/button-group/index.js"
3
3
  export { Checkbox } from "./controls/checkbox/index.js"
4
+ export { ChipPicker } from "./controls/chip-picker/index.js"
4
5
  export { Fab } from "./controls/fab/index.js"
5
6
  export { FileUpload } from "./controls/file-upload/index.js"
6
7
  export { IconButton } from "./controls/icon-button/index.js"
@@ -25,6 +26,7 @@ export { Chip } from "./data-display/chip/index.js"
25
26
  export { DataGrid } from "./data-display/data-grid/index.js"
26
27
  export { Divider } from "./data-display/divider/index.js"
27
28
  export { Icon } from "./data-display/icon/index.js"
29
+ export { Lightbox } from "./data-display/lightbox/index.js"
28
30
  export { List } from "./data-display/list/index.js"
29
31
  export { Table } from "./data-display/table/index.js"
30
32
  export { Tooltip } from "./data-display/tooltip/index.js"
@@ -32,7 +34,10 @@ export { Typography } from "./data-display/typography/index.js"
32
34
  export { VirtualList } from "./data-display/virtual-list/index.js"
33
35
  export { Alert } from "./feedback/alert/index.js"
34
36
  export { Backdrop } from "./feedback/backdrop/index.js"
37
+ export { ConfirmDialog } from "./feedback/confirm-dialog/index.js"
38
+ export { CropDialog } from "./feedback/crop-dialog/index.js"
35
39
  export { Dialog } from "./feedback/dialog/index.js"
40
+ export { LoadStatus } from "./feedback/load-status/index.js"
36
41
  export { Progress } from "./feedback/progress/index.js"
37
42
  export { Skeleton } from "./feedback/skeleton/index.js"
38
43
  export { Snackbar } from "./feedback/snackbar/index.js"
@@ -9,3 +9,15 @@ export const leftPanelClose = {
9
9
  height: 24,
10
10
  body: '<path fill="currentColor" d="M16.5 16V8l-4 4zM5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm3-2V5H5v14zm2 0h9V5h-9zm-2 0H5z"/>',
11
11
  }
12
+
13
+ export const chevronLeft = {
14
+ width: 24,
15
+ height: 24,
16
+ body: '<path fill="currentColor" d="m14 18l-6-6l6-6l1.4 1.4l-4.6 4.6l4.6 4.6z"/>',
17
+ }
18
+
19
+ export const chevronRight = {
20
+ width: 24,
21
+ height: 24,
22
+ body: '<path fill="currentColor" d="M12.6 12L8 7.4L9.4 6l6 6l-6 6L8 16.6z"/>',
23
+ }