@inglorious/ui 0.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 (83) hide show
  1. package/LICENSE +9 -0
  2. package/package.json +142 -0
  3. package/src/controls/button/button.stories.js +139 -0
  4. package/src/controls/button/index.js +5 -0
  5. package/src/controls/button/style.css +148 -0
  6. package/src/controls/button/template.js +74 -0
  7. package/src/controls/button-group/button-group.stories.js +108 -0
  8. package/src/controls/button-group/index.js +4 -0
  9. package/src/controls/button-group/style.css +58 -0
  10. package/src/controls/button-group/template.js +117 -0
  11. package/src/controls/checkbox/checkbox.stories.js +47 -0
  12. package/src/controls/checkbox/index.js +4 -0
  13. package/src/controls/checkbox/style.css +33 -0
  14. package/src/controls/checkbox/template.js +54 -0
  15. package/src/controls/fab/fab.stories.js +52 -0
  16. package/src/controls/fab/index.js +4 -0
  17. package/src/controls/fab/style.css +76 -0
  18. package/src/controls/fab/template.js +33 -0
  19. package/src/controls/icon-button/icon-button.stories.js +70 -0
  20. package/src/controls/icon-button/index.js +4 -0
  21. package/src/controls/icon-button/style.css +14 -0
  22. package/src/controls/icon-button/template.js +47 -0
  23. package/src/controls/input/index.js +4 -0
  24. package/src/controls/input/input.stories.js +180 -0
  25. package/src/controls/input/style.css +121 -0
  26. package/src/controls/input/template.js +111 -0
  27. package/src/controls/radio-group/index.js +4 -0
  28. package/src/controls/radio-group/radio-group.stories.js +51 -0
  29. package/src/controls/radio-group/style.css +41 -0
  30. package/src/controls/radio-group/template.js +71 -0
  31. package/src/controls/rating/index.js +4 -0
  32. package/src/controls/rating/rating.stories.js +47 -0
  33. package/src/controls/rating/style.css +29 -0
  34. package/src/controls/rating/template.js +53 -0
  35. package/src/controls/select/handlers.js +133 -0
  36. package/src/controls/select/helpers.js +91 -0
  37. package/src/controls/select/index.js +15 -0
  38. package/src/controls/select/select.stories.js +90 -0
  39. package/src/controls/select/style.css +211 -0
  40. package/src/controls/select/template.js +423 -0
  41. package/src/controls/slider/index.js +4 -0
  42. package/src/controls/slider/slider.stories.js +43 -0
  43. package/src/controls/slider/style.css +28 -0
  44. package/src/controls/slider/template.js +66 -0
  45. package/src/controls/switch/index.js +4 -0
  46. package/src/controls/switch/style.css +82 -0
  47. package/src/controls/switch/switch.stories.js +45 -0
  48. package/src/controls/switch/template.js +55 -0
  49. package/src/index.js +14 -0
  50. package/src/layout/flex/flex.stories.js +103 -0
  51. package/src/layout/flex/index.js +4 -0
  52. package/src/layout/flex/style.css +103 -0
  53. package/src/layout/flex/template.js +41 -0
  54. package/src/layout/grid/grid.stories.js +82 -0
  55. package/src/layout/grid/index.js +4 -0
  56. package/src/layout/grid/style.css +51 -0
  57. package/src/layout/grid/template.js +47 -0
  58. package/src/shared/applyElementProps.js +79 -0
  59. package/src/shared/withRenderValidation.js +19 -0
  60. package/src/stories/notifyStory.js +43 -0
  61. package/src/themes/bootstrap.css +118 -0
  62. package/src/themes/inglorious.css +106 -0
  63. package/src/themes/material.css +124 -0
  64. package/src/tokens/index.css +133 -0
  65. package/src/widgets/card/card.stories.js +135 -0
  66. package/src/widgets/card/index.js +4 -0
  67. package/src/widgets/card/style.css +53 -0
  68. package/src/widgets/card/template.js +97 -0
  69. package/types/controls/button-group.d.ts +35 -0
  70. package/types/controls/button.d.ts +23 -0
  71. package/types/controls/checkbox.d.ts +18 -0
  72. package/types/controls/fab.d.ts +19 -0
  73. package/types/controls/icon-button.d.ts +23 -0
  74. package/types/controls/input.d.ts +29 -0
  75. package/types/controls/radio-group.d.ts +25 -0
  76. package/types/controls/rating.d.ts +18 -0
  77. package/types/controls/select.d.ts +56 -0
  78. package/types/controls/slider.d.ts +21 -0
  79. package/types/controls/switch.d.ts +17 -0
  80. package/types/index.d.ts +14 -0
  81. package/types/layout/flex.d.ts +20 -0
  82. package/types/layout/grid.d.ts +19 -0
  83. package/types/widgets/card.d.ts +21 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @typedef {import('../../../types/controls/button-group').ButtonGroupEntity} ButtonGroupEntity
3
+ * @typedef {import('@inglorious/web').Api} Api
4
+ * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
5
+ */
6
+
7
+ import { classMap, html, ref } from "@inglorious/web"
8
+ import { applyElementProps } from "@/shared/applyElementProps.js"
9
+
10
+ import { buttonPrimitive as button } from "../button/index.js"
11
+
12
+ /**
13
+ * Button group control for grouped actions.
14
+ *
15
+ * @param {ButtonGroupEntity} entity
16
+ * @param {Api} api
17
+ * @returns {TemplateResult}
18
+ */
19
+ export function render(entity, api) {
20
+ const {
21
+ buttons = [],
22
+ value,
23
+ multiple = false,
24
+ direction = "row",
25
+ attached = true,
26
+ size = "md",
27
+ variant = "default",
28
+ color = "primary",
29
+ disabled = false,
30
+ ...rest
31
+ } = entity
32
+
33
+ const groupClasses = {
34
+ "iw-button-group": true,
35
+ "iw-button-group-selection": value != null || multiple,
36
+ [`iw-button-group-${direction}`]: true,
37
+ "iw-button-group-attached": attached,
38
+ }
39
+
40
+ const selectedValues = multiple
41
+ ? new Set(Array.isArray(value) ? value : [])
42
+ : null
43
+
44
+ return html`
45
+ <div
46
+ class=${classMap(groupClasses)}
47
+ role="group"
48
+ ${ref((element) => applyElementProps(element, rest))}
49
+ >
50
+ ${buttons.map((item, index) => {
51
+ const {
52
+ id: itemId,
53
+ value: explicitValue,
54
+ label,
55
+ children,
56
+ disabled: itemDisabled,
57
+ variant: itemVariant,
58
+ color: itemColor,
59
+ size: itemSize,
60
+ icon,
61
+ iconAfter,
62
+ ...itemRest
63
+ } = item
64
+
65
+ const childId = `${entity.id}-btn-${itemId ?? index}`
66
+ const itemValue = explicitValue ?? itemId
67
+ const pressed = multiple
68
+ ? selectedValues.has(itemValue)
69
+ : value != null
70
+ ? value === itemValue
71
+ : false
72
+
73
+ const childApi = {
74
+ ...api,
75
+ notify(type, payload) {
76
+ if (type === `#${childId}:click`) {
77
+ api.notify(`#${entity.id}:click`, itemValue)
78
+ if (value != null || multiple) {
79
+ if (multiple) {
80
+ const next = new Set(selectedValues)
81
+ if (next.has(itemValue)) next.delete(itemValue)
82
+ else next.add(itemValue)
83
+ return api.notify(`#${entity.id}:change`, [...next])
84
+ }
85
+ return api.notify(`#${entity.id}:change`, itemValue)
86
+ }
87
+ return
88
+ }
89
+ return api.notify(type, payload)
90
+ },
91
+ }
92
+
93
+ return button.render(
94
+ {
95
+ id: childId,
96
+ children: html`
97
+ ${icon ? html`<span class="iw-button-icon">${icon}</span>` : null}
98
+ ${children ?? label}
99
+ ${iconAfter
100
+ ? html`<span class="iw-button-icon">${iconAfter}</span>`
101
+ : null}
102
+ `,
103
+ variant: itemVariant ?? variant,
104
+ color: itemColor ?? color,
105
+ size: itemSize ?? size,
106
+ disabled: disabled || !!itemDisabled,
107
+ ariaPressed: pressed ? true : undefined,
108
+ className: pressed ? "iw-button-pressed" : "",
109
+ type: "button",
110
+ ...itemRest,
111
+ },
112
+ childApi,
113
+ )
114
+ })}
115
+ </div>
116
+ `
117
+ }
@@ -0,0 +1,47 @@
1
+ import {
2
+ makeStoryRender,
3
+ notifyActionArgType,
4
+ } from "../../stories/notifyStory.js"
5
+ import { checkbox } from "."
6
+
7
+ export default {
8
+ title: "Controls/Checkbox",
9
+ tags: ["autodocs"],
10
+ render: makeStoryRender(checkbox, "story-checkbox"),
11
+ argTypes: {
12
+ ...notifyActionArgType,
13
+ label: { control: "text", description: "Label text." },
14
+ name: { control: "text", description: "Native HTML name attribute." },
15
+ checked: { control: "boolean", description: "Current checked state." },
16
+ disabled: { control: "boolean", description: "Disables interaction." },
17
+ required: { control: "boolean", description: "Marks input as required." },
18
+ size: {
19
+ control: "select",
20
+ options: ["sm", "md", "lg"],
21
+ description: "Label size scale.",
22
+ },
23
+ },
24
+ parameters: {
25
+ docs: {
26
+ description: {
27
+ component: "Boolean control with a native checkbox input.",
28
+ },
29
+ },
30
+ },
31
+ }
32
+
33
+ export const Default = {}
34
+ Default.args = {
35
+ label: "Accept terms",
36
+ name: "terms",
37
+ checked: false,
38
+ disabled: false,
39
+ required: false,
40
+ size: "md",
41
+ }
42
+
43
+ export const Checked = {}
44
+ Checked.args = {
45
+ ...Default.args,
46
+ checked: true,
47
+ }
@@ -0,0 +1,4 @@
1
+ import { withRenderValidation } from "../../shared/withRenderValidation.js"
2
+ import * as renderers from "./template.js"
3
+
4
+ export const checkbox = [{ ...renderers }, withRenderValidation]
@@ -0,0 +1,33 @@
1
+ .iw-checkbox-field {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: var(--iw-space-2);
5
+ font-family: var(--iw-font-family-sans);
6
+ color: var(--iw-color-text);
7
+ cursor: pointer;
8
+
9
+ .iw-checkbox {
10
+ width: 1rem;
11
+ height: 1rem;
12
+ margin: 0;
13
+ accent-color: var(--iw-color-primary);
14
+ }
15
+
16
+ .iw-checkbox-label {
17
+ font-size: var(--iw-font-size-base);
18
+ line-height: var(--iw-line-height-normal);
19
+ }
20
+
21
+ &.iw-checkbox-sm .iw-checkbox-label {
22
+ font-size: var(--iw-font-size-sm);
23
+ }
24
+
25
+ &.iw-checkbox-lg .iw-checkbox-label {
26
+ font-size: var(--iw-font-size-lg);
27
+ }
28
+
29
+ &.iw-checkbox-disabled {
30
+ opacity: 0.6;
31
+ cursor: not-allowed;
32
+ }
33
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @typedef {import('../../../types/controls/checkbox').CheckboxEntity} CheckboxEntity
3
+ * @typedef {import('@inglorious/web').Api} Api
4
+ * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
5
+ */
6
+
7
+ import { classMap, html, ref } from "@inglorious/web"
8
+
9
+ import { applyElementProps } from "@/shared/applyElementProps.js"
10
+
11
+ /**
12
+ * Checkbox control for boolean values.
13
+ *
14
+ * @param {CheckboxEntity} entity
15
+ * @param {Api} api
16
+ * @returns {TemplateResult}
17
+ */
18
+ export function render(entity, api) {
19
+ const {
20
+ name = "",
21
+ label = "",
22
+ checked = false,
23
+ disabled = false,
24
+ required = false,
25
+ size = "md",
26
+ ...rest
27
+ } = entity
28
+
29
+ const inputId = entity.id || name
30
+
31
+ const classes = {
32
+ "iw-checkbox-field": true,
33
+ "iw-checkbox-disabled": disabled,
34
+ [`iw-checkbox-${size}`]: size !== "md",
35
+ }
36
+
37
+ return html`
38
+ <label class=${classMap(classes)} for=${inputId}>
39
+ <input
40
+ id=${inputId}
41
+ class="iw-checkbox"
42
+ type="checkbox"
43
+ name=${name}
44
+ .checked=${checked}
45
+ ?disabled=${disabled}
46
+ ?required=${required}
47
+ @change=${(event) =>
48
+ api.notify(`#${entity.id}:change`, event.target.checked)}
49
+ ${ref((element) => applyElementProps(element, rest))}
50
+ />
51
+ <span class="iw-checkbox-label">${label}</span>
52
+ </label>
53
+ `
54
+ }
@@ -0,0 +1,52 @@
1
+ import { html } from "@inglorious/web"
2
+
3
+ import {
4
+ makeStoryRender,
5
+ notifyActionArgType,
6
+ } from "../../stories/notifyStory.js"
7
+ import { fab } from "."
8
+
9
+ export default {
10
+ title: "Controls/Fab",
11
+ tags: ["autodocs"],
12
+ render: makeStoryRender(fab, "story-fab"),
13
+ argTypes: {
14
+ ...notifyActionArgType,
15
+ children: { control: "text", description: "Content of the FAB." },
16
+ color: {
17
+ control: "select",
18
+ options: ["primary", "secondary", "success", "warning", "error", "info"],
19
+ description: "Semantic color intent.",
20
+ },
21
+ size: {
22
+ control: "select",
23
+ options: ["sm", "md", "lg"],
24
+ description: "Button size.",
25
+ },
26
+ extended: { control: "boolean", description: "Use extended pill shape." },
27
+ disabled: { control: "boolean", description: "Disables interaction." },
28
+ },
29
+ parameters: {
30
+ docs: {
31
+ description: {
32
+ component: "Floating action button for prominent primary actions.",
33
+ },
34
+ },
35
+ },
36
+ }
37
+
38
+ export const Default = {}
39
+ Default.args = {
40
+ children: "+",
41
+ color: "primary",
42
+ size: "md",
43
+ extended: false,
44
+ disabled: false,
45
+ }
46
+
47
+ export const Extended = {}
48
+ Extended.args = {
49
+ ...Default.args,
50
+ extended: true,
51
+ children: html`<span>+</span><span>Create</span>`,
52
+ }
@@ -0,0 +1,4 @@
1
+ import { withRenderValidation } from "../../shared/withRenderValidation.js"
2
+ import * as renderers from "./template.js"
3
+
4
+ export const fab = [{ ...renderers }, withRenderValidation]
@@ -0,0 +1,76 @@
1
+ .iw-button.iw-fab {
2
+ box-shadow: var(--iw-shadow-lg);
3
+ border: 0;
4
+ width: 3.5rem;
5
+
6
+ &:hover:not(:disabled) {
7
+ transform: translateY(-1px);
8
+ box-shadow: var(--iw-shadow-xl);
9
+ }
10
+
11
+ .iw-fab-content {
12
+ display: inline-flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ font-size: 1.65em;
16
+ line-height: 1;
17
+ font-weight: var(--iw-font-weight-normal);
18
+ transform: translateY(-0.04em);
19
+ }
20
+
21
+ &.iw-button-sm {
22
+ width: 2.75rem;
23
+
24
+ .iw-fab-content {
25
+ font-size: 1.55em;
26
+ }
27
+ }
28
+
29
+ &.iw-button-lg {
30
+ width: 4.25rem;
31
+
32
+ .iw-fab-content {
33
+ font-size: 1.75em;
34
+ }
35
+ }
36
+
37
+ &.iw-fab-extended {
38
+ width: auto;
39
+ min-height: 2.5rem;
40
+ padding: var(--iw-space-2) var(--iw-space-4);
41
+ gap: var(--iw-space-2);
42
+
43
+ .iw-fab-content {
44
+ font-size: inherit;
45
+ transform: none;
46
+ }
47
+ }
48
+
49
+ &.iw-fab-extended.iw-button-sm {
50
+ min-height: 2rem;
51
+ padding: var(--iw-space-1) var(--iw-space-3);
52
+ }
53
+
54
+ &.iw-fab-extended.iw-button-lg {
55
+ min-height: 3rem;
56
+ padding: var(--iw-space-3) var(--iw-space-5);
57
+ }
58
+
59
+ &.iw-fab-extended .iw-fab-content {
60
+ transform: none;
61
+ }
62
+
63
+ &.iw-fab-extended .iw-button-icon {
64
+ width: 1.1em;
65
+ height: 1.1em;
66
+ transform: translateY(-0.02em);
67
+ }
68
+
69
+ &.iw-fab-extended.iw-button-sm .iw-button-icon {
70
+ transform: none;
71
+ }
72
+
73
+ &.iw-fab-extended.iw-button-lg .iw-button-icon {
74
+ transform: translateY(-0.03em);
75
+ }
76
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @typedef {import('../../../types/controls/fab').FabEntity} FabEntity
3
+ * @typedef {import('@inglorious/web').Api} Api
4
+ * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
5
+ */
6
+
7
+ import { html } from "@inglorious/web"
8
+
9
+ import { buttonPrimitive as button } from "../button/index.js"
10
+
11
+ /**
12
+ * Floating action button for primary contextual actions.
13
+ *
14
+ * @param {FabEntity} entity
15
+ * @param {Api} api
16
+ * @returns {TemplateResult}
17
+ */
18
+ export function render(entity, api) {
19
+ const { children, extended = false, className = "", ...rest } = entity
20
+
21
+ return button.render(
22
+ {
23
+ ...rest,
24
+ shape: extended ? "pill" : "round",
25
+ className:
26
+ `iw-fab ${extended ? "iw-fab-extended" : ""} ${className}`.trim(),
27
+ children: extended
28
+ ? children
29
+ : html`<span class="iw-fab-content">${children}</span>`,
30
+ },
31
+ api,
32
+ )
33
+ }
@@ -0,0 +1,70 @@
1
+ import {
2
+ makeStoryRender,
3
+ notifyActionArgType,
4
+ } from "../../stories/notifyStory.js"
5
+ import { iconButton } from "."
6
+
7
+ export default {
8
+ title: "Controls/IconButton",
9
+ tags: ["autodocs"],
10
+ render: makeStoryRender(iconButton, "story-icon-button"),
11
+ argTypes: {
12
+ ...notifyActionArgType,
13
+ icon: { control: "text", description: "Leading icon content." },
14
+ label: { control: "text", description: "Optional button label." },
15
+ iconAfter: { control: "text", description: "Trailing icon content." },
16
+ direction: {
17
+ control: "select",
18
+ options: ["row", "column"],
19
+ description: "Layout direction for icon and label.",
20
+ },
21
+ iconOnly: {
22
+ control: "boolean",
23
+ description: "Renders icon-only content.",
24
+ },
25
+ ariaLabel: {
26
+ control: "text",
27
+ description: "Accessible name when used as icon-only button.",
28
+ },
29
+ variant: {
30
+ control: "select",
31
+ options: ["default", "outline", "ghost"],
32
+ description: "Visual style variant inherited from button.",
33
+ },
34
+ color: {
35
+ control: "select",
36
+ options: ["primary", "secondary", "success", "warning", "error", "info"],
37
+ description: "Semantic color inherited from button.",
38
+ },
39
+ },
40
+ parameters: {
41
+ docs: {
42
+ description: {
43
+ component:
44
+ "Wrapper around button for icon-oriented content and compact icon-only actions.",
45
+ },
46
+ },
47
+ },
48
+ }
49
+
50
+ export const Default = {}
51
+ Default.args = {
52
+ icon: "★",
53
+ label: "Favorite",
54
+ iconAfter: "",
55
+ direction: "row",
56
+ iconOnly: false,
57
+ ariaLabel: "Favorite",
58
+ variant: "default",
59
+ color: "primary",
60
+ }
61
+
62
+ export const IconOnly = {}
63
+ IconOnly.args = {
64
+ ...Default.args,
65
+ icon: "⚙",
66
+ label: "",
67
+ iconOnly: true,
68
+ variant: "ghost",
69
+ ariaLabel: "Settings",
70
+ }
@@ -0,0 +1,4 @@
1
+ import { withRenderValidation } from "../../shared/withRenderValidation.js"
2
+ import * as renderers from "./template.js"
3
+
4
+ export const iconButton = [{ ...renderers }, withRenderValidation]
@@ -0,0 +1,14 @@
1
+ .iw-icon-button-content {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ gap: var(--iw-space-2);
6
+
7
+ &.iw-icon-button-content-column {
8
+ flex-direction: column;
9
+ }
10
+
11
+ &.iw-icon-button-content-row {
12
+ flex-direction: row;
13
+ }
14
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @typedef {import('../../../types/controls/icon-button').IconButtonEntity} IconButtonEntity
3
+ * @typedef {import('@inglorious/web').Api} Api
4
+ * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
5
+ */
6
+
7
+ import { html } from "@inglorious/web"
8
+
9
+ import { render as renderButton } from "../button/template.js"
10
+
11
+ /**
12
+ * Icon button wrapper built on top of the base button renderer.
13
+ *
14
+ * @param {IconButtonEntity} entity
15
+ * @param {Api} api
16
+ * @returns {TemplateResult}
17
+ */
18
+ export function render(entity, api) {
19
+ const {
20
+ icon,
21
+ label,
22
+ iconAfter,
23
+ direction = "row",
24
+ iconOnly = false,
25
+ ariaLabel,
26
+ } = entity
27
+
28
+ const children = html`
29
+ <span class="iw-icon-button-content iw-icon-button-content-${direction}">
30
+ ${icon ? html`<span class="iw-button-icon">${icon}</span>` : null}
31
+ ${iconOnly ? null : label}
32
+ ${iconAfter
33
+ ? html`<span class="iw-button-icon">${iconAfter}</span>`
34
+ : null}
35
+ </span>
36
+ `
37
+
38
+ return renderButton(
39
+ {
40
+ ...entity,
41
+ label: iconOnly ? "" : label,
42
+ children,
43
+ ariaLabel,
44
+ },
45
+ api,
46
+ )
47
+ }
@@ -0,0 +1,4 @@
1
+ import { withRenderValidation } from "../../shared/withRenderValidation.js"
2
+ import * as renderers from "./template.js"
3
+
4
+ export const input = [{ ...renderers }, withRenderValidation]