@inglorious/ui 5.0.0 → 6.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.
@@ -1,4 +1,5 @@
1
- import { html } from "@inglorious/web"
1
+ import { createStore } from "@inglorious/store"
2
+ import { html, mount } from "@inglorious/web"
2
3
 
3
4
  import { Button } from "../../controls/button/index.js"
4
5
  import { ButtonGroup } from "../../controls/button-group/index.js"
@@ -14,6 +15,8 @@ import { Slider } from "../../controls/slider/index.js"
14
15
  import { Switch } from "../../controls/switch/index.js"
15
16
  import { Avatar } from "../../data-display/avatar/index.js"
16
17
  import { Badge } from "../../data-display/badge/index.js"
18
+ import { BeforeAfter } from "../../data-display/before-after/index.js"
19
+ import { Carousel } from "../../data-display/carousel/index.js"
17
20
  import { Chip } from "../../data-display/chip/index.js"
18
21
  import { DataGrid } from "../../data-display/data-grid/index.js"
19
22
  import { Divider } from "../../data-display/divider/index.js"
@@ -45,6 +48,7 @@ import { Accordion } from "../../surfaces/accordion/index.js"
45
48
  import { AppBar } from "../../surfaces/app-bar/index.js"
46
49
  import { Card } from "../../surfaces/card/index.js"
47
50
  import { Paper } from "../../surfaces/paper/index.js"
51
+ import { beforeAfterImages } from "./before-after-data.js"
48
52
 
49
53
  const categoryMeta = {
50
54
  layout: {
@@ -102,6 +106,48 @@ const previewRow = (children) =>
102
106
  const previewColumn = (children) =>
103
107
  html`<div class="iw-primitive-preview-column">${children}</div>`
104
108
 
109
+ const livePreviews = new Map()
110
+
111
+ /**
112
+ * Entity-driven primitives render their controls from state, so a preview
113
+ * without a store freezes: a carousel arrow would recompute from the same page
114
+ * forever, and a dragged divider would never move. Give each one its own store,
115
+ * created once and reused on every re-render so the preview keeps its state.
116
+ * @param {Object.<string, any>} types
117
+ * @param {Object.<string, any>} entity
118
+ * @returns {HTMLElement}
119
+ */
120
+ function livePreview(types, entity) {
121
+ if (!livePreviews.has(entity.id)) {
122
+ const container = document.createElement("div")
123
+ // The mount needs an element, but it must not become a box of its own:
124
+ // as a flex item it would shrink and take the primitive down with it.
125
+ container.style.display = "contents"
126
+
127
+ const store = createStore({ types, entities: { [entity.id]: entity } })
128
+ mount(store, (api) => api.render(entity.id), container)
129
+ livePreviews.set(entity.id, container)
130
+ }
131
+
132
+ return livePreviews.get(entity.id)
133
+ }
134
+
135
+ const carouselSlides = [
136
+ "#22d3ee",
137
+ "#4f46e5",
138
+ "#f59e0b",
139
+ "#10b981",
140
+ "#ef4444",
141
+ ].map(
142
+ (color, index) =>
143
+ html`<div
144
+ style="display:grid;place-items:center;height:14rem;border-radius:0.5rem;
145
+ background:${color};color:#fff;font:700 2rem sans-serif;"
146
+ >
147
+ ${index + 1}
148
+ </div>`,
149
+ )
150
+
105
151
  const primitiveDetailsByPath = {
106
152
  "/layout/container": {
107
153
  summary: "Constrains content width and applies consistent gutters.",
@@ -195,6 +241,38 @@ Grid.render({
195
241
  }),
196
242
  },
197
243
  },
244
+ "/data-display/carousel": {
245
+ summary: "Scroll-snapping carousel with arrows and pagination dots.",
246
+ description:
247
+ "Carousel lays items out in a scroll-snapping viewport: swiping, momentum and snapping come from the browser, while the current page is the only piece of entity state. Arrows and indicators are overridable sub-renders.",
248
+ useCases: ["Image galleries", "Feature tours", "Onboarding slides"],
249
+ example: {
250
+ code: `import { Carousel } from "@inglorious/ui/carousel"
251
+
252
+ Carousel.render({
253
+ id: "demo",
254
+ label: "Example carousel",
255
+ items: [slideOne, slideTwo, slideThree],
256
+ page: 0,
257
+ gap: "md",
258
+ isFullWidth: true
259
+ })`,
260
+ preview: () =>
261
+ livePreview(
262
+ { Carousel },
263
+ {
264
+ id: "carousel",
265
+ type: "Carousel",
266
+ label: "Example carousel",
267
+ items: carouselSlides,
268
+ page: 0,
269
+ gap: "md",
270
+ isInfinite: true,
271
+ isFullWidth: true,
272
+ },
273
+ ),
274
+ },
275
+ },
198
276
  "/controls/button": {
199
277
  summary: "Action trigger with variants, colors, and shapes.",
200
278
  description:
@@ -280,6 +358,36 @@ Checkbox.render({
280
358
  ]),
281
359
  },
282
360
  },
361
+ "/data-display/before-after": {
362
+ name: "Before / After",
363
+ summary: "Drag-to-compare slider for two overlaid images.",
364
+ description:
365
+ "Before/After overlays two images and reveals the top one with a draggable divider. The reveal is pure CSS (clip-path) and the divider position is the only piece of entity state; the divider and handle follow the active theme and color mode.",
366
+ useCases: ["Image comparisons", "Before/after edits", "Visual diffs"],
367
+ example: {
368
+ code: `import { BeforeAfter } from "@inglorious/ui/before-after"
369
+
370
+ BeforeAfter.render({
371
+ id: "demo",
372
+ before: { src: "before.jpg", alt: "Before" },
373
+ after: { src: "after.jpg", alt: "After" },
374
+ position: 50,
375
+ isFullWidth: true
376
+ })`,
377
+ preview: () =>
378
+ livePreview(
379
+ { BeforeAfter },
380
+ {
381
+ id: "beforeAfter",
382
+ type: "BeforeAfter",
383
+ before: beforeAfterImages.before,
384
+ after: beforeAfterImages.after,
385
+ position: 50,
386
+ isFullWidth: true,
387
+ },
388
+ ),
389
+ },
390
+ },
283
391
  "/controls/combobox": {
284
392
  summary: "Searchable dropdown with multi-select and tag support.",
285
393
  description:
@@ -304,21 +412,26 @@ Combobox.render({
304
412
  selectedValue: ["inglorious"]
305
413
  })`,
306
414
  preview: () =>
307
- Combobox.render({
308
- label: "Frameworks",
309
- placeholder: "Select frameworks...",
310
- isMulti: true,
311
- isSearchable: true,
312
- isClearable: true,
313
- options: [
314
- { label: "React", value: "react" },
315
- { label: "Angular", value: "angular" },
316
- { label: "Vue", value: "vue" },
317
- { label: "Svelte", value: "svelte" },
318
- { label: "Inglorious", value: "inglorious" },
319
- ],
320
- selectedValue: ["inglorious"],
321
- }),
415
+ livePreview(
416
+ { Combobox },
417
+ {
418
+ id: "combobox",
419
+ type: "Combobox",
420
+ label: "Frameworks",
421
+ placeholder: "Select frameworks...",
422
+ isMulti: true,
423
+ isSearchable: true,
424
+ isClearable: true,
425
+ options: [
426
+ { label: "React", value: "react" },
427
+ { label: "Angular", value: "angular" },
428
+ { label: "Vue", value: "vue" },
429
+ { label: "Svelte", value: "svelte" },
430
+ { label: "Inglorious", value: "inglorious" },
431
+ ],
432
+ selectedValue: ["inglorious"],
433
+ },
434
+ ),
322
435
  },
323
436
  },
324
437
  "/controls/fab": {
@@ -590,23 +703,28 @@ DataGrid.render({
590
703
  pagination: { page: 0, pageSize: 3, pageSizes: [3, 6] }
591
704
  })`,
592
705
  preview: () =>
593
- DataGrid.render({
594
- rowId: "id",
595
- columns: [
596
- { id: "id", title: "ID", type: "number", isSortable: true },
597
- { id: "name", title: "Name" },
598
- { id: "status", title: "Status" },
599
- ],
600
- rows: [
601
- { id: 1, name: "Alpha", status: "Active" },
602
- { id: 2, name: "Beta", status: "Paused" },
603
- { id: 3, name: "Gamma", status: "Active" },
604
- ],
605
- filters: {},
606
- sorts: [],
607
- selection: [],
608
- pagination: { page: 0, pageSize: 3, pageSizes: [3, 6] },
609
- }),
706
+ livePreview(
707
+ { DataGrid },
708
+ {
709
+ id: "dataGrid",
710
+ type: "DataGrid",
711
+ rowId: "id",
712
+ columns: [
713
+ { id: "id", title: "ID", type: "number", isSortable: true },
714
+ { id: "name", title: "Name" },
715
+ { id: "status", title: "Status" },
716
+ ],
717
+ rows: [
718
+ { id: 1, name: "Alpha", status: "Active" },
719
+ { id: 2, name: "Beta", status: "Paused" },
720
+ { id: 3, name: "Gamma", status: "Active" },
721
+ ],
722
+ filters: {},
723
+ sorts: [],
724
+ selection: [],
725
+ pagination: { page: 0, pageSize: 3, pageSizes: [3, 6] },
726
+ },
727
+ ),
610
728
  },
611
729
  },
612
730
  "/data-display/divider": {
package/src/index.js CHANGED
@@ -11,6 +11,8 @@ export { Slider } from "./controls/slider/index.js"
11
11
  export { Switch } from "./controls/switch/index.js"
12
12
  export { Avatar } from "./data-display/avatar/index.js"
13
13
  export { Badge } from "./data-display/badge/index.js"
14
+ export { BeforeAfter } from "./data-display/before-after/index.js"
15
+ export { Carousel } from "./data-display/carousel/index.js"
14
16
  export { Chip } from "./data-display/chip/index.js"
15
17
  export { DataGrid } from "./data-display/data-grid/index.js"
16
18
  export { Divider } from "./data-display/divider/index.js"
@@ -38,6 +38,12 @@ export default {
38
38
  options: ["none", "sm", "md", "lg", "xl"],
39
39
  description: "Inner padding for the flex container.",
40
40
  },
41
+ overflow: {
42
+ control: "select",
43
+ options: ["visible", "hidden", "clip", "scroll", "auto"],
44
+ description:
45
+ "Overflow behavior. Scrollable values also release flex minimum dimensions.",
46
+ },
41
47
  element: {
42
48
  control: "select",
43
49
  options: [
@@ -84,6 +90,7 @@ Default.args = {
84
90
  align: "stretch",
85
91
  gap: "md",
86
92
  padding: "none",
93
+ overflow: "visible",
87
94
  element: "div",
88
95
  isInline: false,
89
96
  isFullWidth: false,
@@ -119,3 +126,45 @@ Wrapped.args = {
119
126
  html`<div>Six</div>`,
120
127
  ],
121
128
  }
129
+
130
+ export const ScrollableContent = {}
131
+ ScrollableContent.args = {
132
+ direction: "column",
133
+ element: "section",
134
+ overflow: "hidden",
135
+ className: "iw-flex-scrollable-story",
136
+ style: { height: "24rem", border: "1px solid var(--iw-color-border)" },
137
+ children: [
138
+ Flex.render({
139
+ element: "header",
140
+ padding: "md",
141
+ style: {
142
+ flex: "0 0 3.5rem",
143
+ background: "var(--iw-color-surface)",
144
+ },
145
+ children: ["Fixed header"],
146
+ }),
147
+ Flex.render({
148
+ element: "main",
149
+ direction: "column",
150
+ overflow: "auto",
151
+ padding: "md",
152
+ style: { flex: "1 1 0" },
153
+ children: [
154
+ html`<div style="min-height: 48rem">
155
+ This content is intentionally taller than the available space. The
156
+ header and footer stay visible while this main area scrolls.
157
+ </div>`,
158
+ ],
159
+ }),
160
+ Flex.render({
161
+ element: "footer",
162
+ padding: "md",
163
+ style: {
164
+ flex: "0 0 3.5rem",
165
+ background: "var(--iw-color-surface)",
166
+ },
167
+ children: ["Fixed footer"],
168
+ }),
169
+ ],
170
+ }
@@ -120,4 +120,63 @@
120
120
  &.iw-flex-padding-xl {
121
121
  padding: var(--iw-space-8);
122
122
  }
123
+
124
+ /* A flex item needs both minimum dimensions released before either axis can
125
+ overflow instead of expanding its parent. */
126
+ &.iw-flex-overflow-auto,
127
+ &.iw-flex-overflow-scroll,
128
+ &.iw-flex-overflow-hidden,
129
+ &.iw-flex-overflow-clip {
130
+ min-width: 0;
131
+ min-height: 0;
132
+ }
133
+
134
+ &.iw-flex-overflow-auto {
135
+ overflow: auto;
136
+ }
137
+
138
+ &.iw-flex-overflow-scroll {
139
+ overflow: scroll;
140
+ }
141
+
142
+ &.iw-flex-overflow-hidden {
143
+ overflow: hidden;
144
+ }
145
+
146
+ &.iw-flex-overflow-clip {
147
+ overflow: clip;
148
+ }
149
+
150
+ &.iw-flex-overflow-visible {
151
+ overflow: visible;
152
+ }
153
+
154
+ &.iw-flex-overflow-auto,
155
+ &.iw-flex-overflow-scroll {
156
+ scrollbar-color: var(--iw-color-text-muted) var(--iw-color-surface-hover);
157
+ scrollbar-width: thin;
158
+ }
159
+
160
+ &.iw-flex-overflow-auto::-webkit-scrollbar,
161
+ &.iw-flex-overflow-scroll::-webkit-scrollbar {
162
+ width: 0.625rem;
163
+ height: 0.625rem;
164
+ }
165
+
166
+ &.iw-flex-overflow-auto::-webkit-scrollbar-track,
167
+ &.iw-flex-overflow-scroll::-webkit-scrollbar-track {
168
+ background: var(--iw-color-surface-hover);
169
+ }
170
+
171
+ &.iw-flex-overflow-auto::-webkit-scrollbar-thumb,
172
+ &.iw-flex-overflow-scroll::-webkit-scrollbar-thumb {
173
+ border: 0.125rem solid var(--iw-color-surface-hover);
174
+ border-radius: var(--iw-radius-full);
175
+ background: var(--iw-color-text-muted);
176
+ }
177
+
178
+ &.iw-flex-overflow-auto::-webkit-scrollbar-thumb:hover,
179
+ &.iw-flex-overflow-scroll::-webkit-scrollbar-thumb:hover {
180
+ background: var(--iw-color-text-secondary);
181
+ }
123
182
  }
@@ -24,6 +24,7 @@ export function render(props) {
24
24
  align = "stretch",
25
25
  gap = "none",
26
26
  padding = "none",
27
+ overflow = "visible",
27
28
  isInline = false,
28
29
  isFullWidth = false,
29
30
  className = "",
@@ -49,6 +50,7 @@ export function render(props) {
49
50
  [`iw-flex-align-${align}`]: true,
50
51
  [`iw-flex-gap-${gap}`]: true,
51
52
  [`iw-flex-padding-${padding}`]: true,
53
+ [`iw-flex-overflow-${overflow}`]: true,
52
54
  ...extraClasses,
53
55
  }
54
56
 
@@ -0,0 +1,39 @@
1
+ import type { TemplateResult, Api } from "@inglorious/web"
2
+
3
+ export interface BeforeAfterImage {
4
+ src: string
5
+ alt?: string
6
+ }
7
+
8
+ export interface BeforeAfterProps {
9
+ id?: string
10
+ type?: string
11
+ label?: string
12
+
13
+ before?: BeforeAfterImage | null
14
+ after?: BeforeAfterImage | null
15
+
16
+ /** Divider location as a percentage (0-100). The only persistent state. */
17
+ position?: number
18
+ /** Keyboard increment, in percent, for arrow keys. */
19
+ step?: number
20
+
21
+ isDisabled?: boolean
22
+ isFullWidth?: boolean
23
+
24
+ onPositionChange?: (position: number) => void
25
+ [key: string]: unknown
26
+ }
27
+
28
+ export interface BeforeAfterType {
29
+ create(entity: BeforeAfterProps): void
30
+ positionChange(entity: BeforeAfterProps, position: number): void
31
+ render(entity: BeforeAfterProps, api: Api): TemplateResult
32
+ renderBeforeAfter(props: BeforeAfterProps): TemplateResult
33
+ renderBefore(props: BeforeAfterProps): TemplateResult
34
+ renderAfter(props: BeforeAfterProps): TemplateResult
35
+ renderDivider(props: BeforeAfterProps): TemplateResult
36
+ renderHandle(props: BeforeAfterProps): TemplateResult
37
+ }
38
+
39
+ export declare const BeforeAfter: BeforeAfterType
@@ -0,0 +1,72 @@
1
+ import type { TemplateResult, Api } from "@inglorious/web"
2
+
3
+ export type CarouselAxis = "x" | "y"
4
+ export type CarouselGap = "none" | "sm" | "md" | "lg" | "xl"
5
+ export type CarouselAlign = "stretch" | "start" | "center" | "end"
6
+ export type CarouselDirection = "previous" | "next"
7
+ export type CarouselArrowPlacement = "inside" | "outside"
8
+ export type CarouselArrowVariant = "default" | "outline" | "ghost"
9
+ export type CarouselArrowColor =
10
+ | "primary"
11
+ | "secondary"
12
+ | "success"
13
+ | "warning"
14
+ | "error"
15
+ | "info"
16
+
17
+ export interface CarouselProps {
18
+ id?: string
19
+ type?: string
20
+ label?: string
21
+
22
+ items?: unknown[]
23
+ /** Index of the item the viewport has settled on. */
24
+ page?: number
25
+ /** How many items are rotated off the front (infinite carousels only). */
26
+ rotation?: number
27
+
28
+ /** Wrap past the ends by rotating the items instead of stopping. */
29
+ isInfinite?: boolean
30
+
31
+ axis?: CarouselAxis
32
+ gap?: CarouselGap
33
+ align?: CarouselAlign
34
+
35
+ /** Whether the arrows float over the slides or sit beside them. */
36
+ arrowPlacement?: CarouselArrowPlacement
37
+ /** Button variant used for the arrows. */
38
+ arrowVariant?: CarouselArrowVariant
39
+ /** Button color used for the arrows. */
40
+ arrowColor?: CarouselArrowColor
41
+
42
+ hasArrows?: boolean
43
+ hasIndicators?: boolean
44
+ isFullWidth?: boolean
45
+
46
+ onPageChange?: (page: number) => void
47
+ [key: string]: unknown
48
+ }
49
+
50
+ export interface CarouselType {
51
+ create(entity: CarouselProps): void
52
+ indicatorSelect(
53
+ props: CarouselProps,
54
+ index: number,
55
+ viewport: HTMLElement | null | undefined,
56
+ ): void
57
+ pageChange(entity: CarouselProps, page: number): void
58
+ rotate(entity: CarouselProps, step: number): void
59
+
60
+ render(entity: CarouselProps, api: Api): TemplateResult
61
+ renderCarousel(props: CarouselProps): TemplateResult
62
+ renderViewport(props: CarouselProps): TemplateResult
63
+ renderItem(props: CarouselProps, item: unknown, index: number): TemplateResult
64
+ renderArrow(
65
+ props: CarouselProps,
66
+ direction: CarouselDirection,
67
+ ): TemplateResult
68
+ renderIndicators(props: CarouselProps): TemplateResult
69
+ renderIndicator(props: CarouselProps, index: number): TemplateResult
70
+ }
71
+
72
+ export declare const Carousel: CarouselType
@@ -18,6 +18,7 @@ export interface FlexProps {
18
18
  align?: "stretch" | "start" | "center" | "end" | "baseline"
19
19
  gap?: "none" | "sm" | "md" | "lg" | "xl"
20
20
  padding?: "none" | "sm" | "md" | "lg" | "xl"
21
+ overflow?: "visible" | "hidden" | "clip" | "scroll" | "auto"
21
22
  isInline?: boolean
22
23
  isFullWidth?: boolean
23
24
  children?: Array<TemplateResult | string | number>