@inglorious/ui 7.6.1 → 7.8.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
@@ -44,6 +44,27 @@ The public CSS exports are:
44
44
  - `@inglorious/ui/themes/bootstrap.css`
45
45
  - `@inglorious/ui/all.css`
46
46
 
47
+ ## Right-to-left
48
+
49
+ The components mirror in a right-to-left page (`<html dir="rtl">`, which `@inglorious/i18n` sets for Arabic, Hebrew and the like): their spacing, borders and corners use logical properties, and a `left` drawer sits at the start of the line.
50
+
51
+ For your own styles, use logical properties too (`margin-inline-start`, `inset-inline-end`, `text-align: start`). For anything that moves sideways, multiply by `--iw-dir-sign`, which is `1` in left-to-right and `-1` in right-to-left:
52
+
53
+ ```css
54
+ .slide-in {
55
+ transform: translateX(calc(100% * var(--iw-dir-sign)));
56
+ }
57
+ ```
58
+
59
+ Icons that point along the line, such as a back arrow or a chevron, take the `direction` of the text they sit in and are mirrored with `"rtl"`. With `@inglorious/i18n`, that is the i18n entity's `dir`:
60
+
61
+ ```javascript
62
+ MaterialIcon.render({
63
+ name: "arrow_back",
64
+ direction: api.getEntity("i18n").dir,
65
+ })
66
+ ```
67
+
47
68
  ## Safe-area utilities
48
69
 
49
70
  The base styles also expose CSS variables and utility classes for the system bars' insets: the notch, rounded corners, the home indicator and Android's status and navigation bars. They are in `base.css`, or on their own in `@inglorious/ui/safe-area.css`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/ui",
3
- "version": "7.6.1",
3
+ "version": "7.8.0",
4
4
  "description": "A design system for Inglorious Web with render-function components and customizable themes",
5
5
  "author": "Matteo Antony Mistretta <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
6
6
  "license": "MIT",
@@ -14,17 +14,17 @@
14
14
  }
15
15
 
16
16
  &.iw-button-group-attached.iw-button-group-row .iw-button:first-child {
17
- border-top-left-radius: var(--iw-button-radius);
18
- border-bottom-left-radius: var(--iw-button-radius);
17
+ border-start-start-radius: var(--iw-button-radius);
18
+ border-end-start-radius: var(--iw-button-radius);
19
19
  }
20
20
 
21
21
  &.iw-button-group-attached.iw-button-group-row .iw-button:last-child {
22
- border-top-right-radius: var(--iw-button-radius);
23
- border-bottom-right-radius: var(--iw-button-radius);
22
+ border-start-end-radius: var(--iw-button-radius);
23
+ border-end-end-radius: var(--iw-button-radius);
24
24
  }
25
25
 
26
26
  &.iw-button-group-attached.iw-button-group-row .iw-button + .iw-button {
27
- margin-left: -1px;
27
+ margin-inline-start: -1px;
28
28
  }
29
29
 
30
30
  &.iw-button-group-attached.iw-button-group-column .iw-button {
@@ -32,13 +32,13 @@
32
32
  }
33
33
 
34
34
  &.iw-button-group-attached.iw-button-group-column .iw-button:first-child {
35
- border-top-left-radius: var(--iw-button-radius);
36
- border-top-right-radius: var(--iw-button-radius);
35
+ border-start-start-radius: var(--iw-button-radius);
36
+ border-start-end-radius: var(--iw-button-radius);
37
37
  }
38
38
 
39
39
  &.iw-button-group-attached.iw-button-group-column .iw-button:last-child {
40
- border-bottom-left-radius: var(--iw-button-radius);
41
- border-bottom-right-radius: var(--iw-button-radius);
40
+ border-end-start-radius: var(--iw-button-radius);
41
+ border-end-end-radius: var(--iw-button-radius);
42
42
  }
43
43
 
44
44
  &.iw-button-group-attached.iw-button-group-column .iw-button + .iw-button {
@@ -33,6 +33,10 @@ export default {
33
33
  isLoading: { control: "boolean", description: "Show loading state." },
34
34
  searchTerm: { control: "text", description: "Current search filter." },
35
35
  placeholder: { control: "text", description: "Placeholder text." },
36
+ searchPlaceholder: {
37
+ control: "text",
38
+ description: "Placeholder of the search box in the panel.",
39
+ },
36
40
  noOptionsMessage: { control: "text", description: "No results message." },
37
41
  loadingMessage: { control: "text", description: "Loading message." },
38
42
  isFullWidth: {
@@ -20,7 +20,7 @@
20
20
 
21
21
  .iw-combobox-required {
22
22
  color: var(--iw-color-error);
23
- margin-left: var(--iw-space-1);
23
+ margin-inline-start: var(--iw-space-1);
24
24
  }
25
25
 
26
26
  .iw-combobox-hint,
@@ -91,8 +91,8 @@
91
91
  border-color: var(--iw-input-border-focus);
92
92
  box-shadow: var(--iw-input-shadow-focus);
93
93
  background: var(--iw-input-bg-focus);
94
- border-bottom-left-radius: 0;
95
- border-bottom-right-radius: 0;
94
+ border-end-start-radius: 0;
95
+ border-end-end-radius: 0;
96
96
  }
97
97
 
98
98
  &.iw-combobox-frame-disabled {
@@ -151,7 +151,7 @@
151
151
  align-items: center;
152
152
  justify-content: center;
153
153
  cursor: pointer;
154
- right: var(--iw-space-7);
154
+ inset-inline-end: var(--iw-space-7);
155
155
 
156
156
  &:hover {
157
157
  color: var(--iw-color-text);
@@ -194,8 +194,8 @@
194
194
  overflow: hidden;
195
195
  border: 1px solid var(--iw-color-border);
196
196
  border-radius: var(--iw-radius-md);
197
- border-top-left-radius: 0;
198
- border-top-right-radius: 0;
197
+ border-start-start-radius: 0;
198
+ border-start-end-radius: 0;
199
199
  background: var(--iw-color-bg);
200
200
  box-shadow: var(--iw-shadow-lg);
201
201
  margin-top: -1px;
@@ -291,12 +291,17 @@ export const Combobox = {
291
291
  * @returns {TemplateResult}
292
292
  */
293
293
  renderSearchInput(props) {
294
- const { searchTerm, isOpen, onSearchChange } = props
294
+ const {
295
+ searchTerm,
296
+ searchPlaceholder = "Search...",
297
+ isOpen,
298
+ onSearchChange,
299
+ } = props
295
300
 
296
301
  return html`<input
297
302
  class="iw-combobox-dropdown-search"
298
303
  type="text"
299
- placeholder="Search..."
304
+ placeholder=${searchPlaceholder}
300
305
  .value=${searchTerm ?? ""}
301
306
  @input=${(event) => onSearchChange?.(event.target.value)}
302
307
  @keydown=${(event) => handleKeyDown(props, event)}
@@ -17,7 +17,7 @@
17
17
 
18
18
  .iw-input-required {
19
19
  color: var(--iw-color-error);
20
- margin-left: var(--iw-space-1);
20
+ margin-inline-start: var(--iw-space-1);
21
21
  }
22
22
 
23
23
  .iw-input-frame {
@@ -74,7 +74,7 @@
74
74
  }
75
75
 
76
76
  &.iw-input-number {
77
- text-align: right;
77
+ text-align: end;
78
78
  }
79
79
 
80
80
  &.iw-input-sm {
@@ -17,7 +17,7 @@
17
17
 
18
18
  .iw-select-required {
19
19
  color: var(--iw-color-error);
20
- margin-left: var(--iw-space-1);
20
+ margin-inline-start: var(--iw-space-1);
21
21
  }
22
22
 
23
23
  .iw-select-hint,
@@ -40,7 +40,7 @@
40
40
  }
41
41
 
42
42
  .iw-switch-input:checked + .iw-switch-track .iw-switch-thumb {
43
- transform: translateX(1rem);
43
+ transform: translateX(calc(1rem * var(--iw-dir-sign, 1)));
44
44
  }
45
45
 
46
46
  .iw-switch-label {
@@ -59,7 +59,7 @@
59
59
  }
60
60
 
61
61
  &.iw-switch-sm .iw-switch-input:checked + .iw-switch-track .iw-switch-thumb {
62
- transform: translateX(0.875rem);
62
+ transform: translateX(calc(0.875rem * var(--iw-dir-sign, 1)));
63
63
  }
64
64
 
65
65
  &.iw-switch-lg .iw-switch-track {
@@ -73,7 +73,7 @@
73
73
  }
74
74
 
75
75
  &.iw-switch-lg .iw-switch-input:checked + .iw-switch-track .iw-switch-thumb {
76
- transform: translateX(1.25rem);
76
+ transform: translateX(calc(1.25rem * var(--iw-dir-sign, 1)));
77
77
  }
78
78
 
79
79
  &.iw-switch-disabled {
@@ -16,7 +16,7 @@
16
16
 
17
17
  .iw-textarea-required {
18
18
  color: var(--iw-color-error);
19
- margin-left: var(--iw-space-1);
19
+ margin-inline-start: var(--iw-space-1);
20
20
  }
21
21
 
22
22
  .iw-textarea {
@@ -4,13 +4,13 @@
4
4
  margin: var(--iw-space-2) 0;
5
5
 
6
6
  &.iw-divider-inset {
7
- margin-left: var(--iw-space-4);
8
- margin-right: var(--iw-space-4);
7
+ margin-inline-start: var(--iw-space-4);
8
+ margin-inline-end: var(--iw-space-4);
9
9
  }
10
10
 
11
11
  &.iw-divider-vertical {
12
12
  border-top: 0;
13
- border-left: 1px solid var(--iw-color-border);
13
+ border-inline-start: 1px solid var(--iw-color-border);
14
14
  width: 0;
15
15
  height: 100%;
16
16
  margin: 0 var(--iw-space-2);
@@ -71,14 +71,14 @@ Use this page as a quick reference for supported icon strategies in `@inglorious
71
71
  <input id="iw-icon-tab-lucide" type="radio" name="iw-icon-tab" />
72
72
  <input id="iw-icon-tab-a11y" type="radio" name="iw-icon-tab" />
73
73
 
74
- <div className="iw-icon-tab-labels">
75
- <label htmlFor="iw-icon-tab-generic">Generic</label>
76
- <label htmlFor="iw-icon-tab-svg">Inline SVG</label>
77
- <label htmlFor="iw-icon-tab-material">Material</label>
78
- <label htmlFor="iw-icon-tab-fa">Font Awesome</label>
79
- <label htmlFor="iw-icon-tab-lucide">Lucide</label>
80
- <label htmlFor="iw-icon-tab-a11y">Accessibility</label>
81
- </div>
74
+ <div className="iw-icon-tab-labels">
75
+ <label htmlFor="iw-icon-tab-generic">Generic</label>
76
+ <label htmlFor="iw-icon-tab-svg">Inline SVG</label>
77
+ <label htmlFor="iw-icon-tab-material">Material</label>
78
+ <label htmlFor="iw-icon-tab-fa">Font Awesome</label>
79
+ <label htmlFor="iw-icon-tab-lucide">Lucide</label>
80
+ <label htmlFor="iw-icon-tab-a11y">Accessibility</label>
81
+ </div>
82
82
 
83
83
  <div className="iw-icon-tab-panels">
84
84
  <section className="iw-icon-tab-panel iw-icon-tab-panel-generic">
@@ -92,6 +92,7 @@ Use this page as a quick reference for supported icon strategies in `@inglorious
92
92
  <pre><code>{`Icon.render({
93
93
  children: html\`<svg viewBox="0 0 24 24" ...>...</svg>\`,
94
94
  size: "md",
95
+
95
96
  })`}</code></pre>
96
97
 
97
98
  <div className="iw-icon-example">
@@ -121,6 +122,7 @@ Use this page as a quick reference for supported icon strategies in `@inglorious
121
122
  <p>Load Font Awesome CSS globally, then pass an icon element as <code>children</code>. Reference: <a href="https://fontawesome.com/search" target="_blank">Font Awesome Icons</a></p>
122
123
  <pre><code>{`Icon.render({
123
124
  children: html\`<i class="fa-solid fa-user" aria-hidden="true"></i>\`,
125
+
124
126
  })`}</code></pre>
125
127
 
126
128
  <div className="iw-icon-example">
@@ -133,9 +135,10 @@ Use this page as a quick reference for supported icon strategies in `@inglorious
133
135
  <section className="iw-icon-tab-panel iw-icon-tab-panel-lucide">
134
136
  <p>Lucide requires runtime bootstrapping before icons render. Reference: <a href="https://lucide.dev/icons/?focus" target="_blank">Lucide Icons</a></p>
135
137
  <pre><code>{`import { createIcons, icons } from "lucide"
138
+
136
139
  createIcons({
137
- icons,
138
- attrs: { "stroke-width": 1.75 },
140
+ icons,
141
+ attrs: { "stroke-width": 1.75 },
139
142
  })`}</code></pre>
140
143
  <p>Then render nodes such as <code>{`<i data-lucide="user"></i>`}</code>.</p>
141
144
 
@@ -53,7 +53,7 @@
53
53
  /* An item without an icon lines its text up with the items that have one:
54
54
  the icon, the margin either side of it, and the row gap. */
55
55
  .iw-list-item-inset .iw-list-item-content {
56
- margin-left: calc(
56
+ margin-inline-start: calc(
57
57
  var(--iw-list-icon-size, var(--iw-space-6)) + 2 *
58
58
  var(--iw-list-icon-spacing, var(--iw-space-4)) + var(--iw-space-2)
59
59
  );
@@ -74,7 +74,7 @@
74
74
  }
75
75
 
76
76
  .iw-list-item-trailing {
77
- margin-left: auto;
77
+ margin-inline-start: auto;
78
78
  display: inline-flex;
79
79
  align-items: center;
80
80
  gap: var(--iw-space-2);
@@ -127,7 +127,7 @@
127
127
 
128
128
  .iw-list-nested {
129
129
  margin-top: var(--iw-space-1);
130
- padding-left: var(--iw-space-4);
130
+ padding-inline-start: var(--iw-space-4);
131
131
  }
132
132
 
133
133
  .iw-theme-light {
@@ -24,4 +24,10 @@
24
24
  &.iw-material-icon-lg {
25
25
  font-size: 1.5rem;
26
26
  }
27
+
28
+ /* `scale` rather than `transform`, so it adds to a rotation set elsewhere
29
+ instead of replacing it. */
30
+ &.iw-material-icon-rtl {
31
+ scale: -1 1;
32
+ }
27
33
  }
@@ -9,17 +9,27 @@ import { classMap } from "@inglorious/web/directives/class-map"
9
9
  /**
10
10
  * Renders a Material Symbols icon by name.
11
11
  * Requires the Material Symbols font and supports filled and size variants.
12
+ * `direction` is the direction of the text the icon sits in. An icon that
13
+ * points along the line, such as a back arrow or a chevron, is mirrored with
14
+ * `"rtl"`: in right-to-left text, back points right.
12
15
  * @param {MaterialIconProps} props
13
16
  * @returns {TemplateResult}
14
17
  */
15
18
  export function render(props) {
16
- const { name = "help", size = "md", isFilled = true, onClick } = props
19
+ const {
20
+ name = "help",
21
+ size = "md",
22
+ isFilled = true,
23
+ direction = "ltr",
24
+ onClick,
25
+ } = props
17
26
 
18
27
  return html`<span
19
28
  class=${classMap({
20
29
  "iw-material-icon": true,
21
30
  "material-symbols-outlined": true,
22
31
  "iw-material-icon-filled": isFilled,
32
+ "iw-material-icon-rtl": direction === "rtl",
23
33
  [`iw-material-icon-${size}`]: size !== "md",
24
34
  })}
25
35
  aria-hidden="true"
@@ -34,7 +34,7 @@
34
34
  }
35
35
 
36
36
  .iw-alert-trailing {
37
- margin-left: auto;
37
+ margin-inline-start: auto;
38
38
  display: inline-flex;
39
39
  align-items: center;
40
40
  gap: var(--iw-space-2);
@@ -18,7 +18,7 @@
18
18
  }
19
19
 
20
20
  .iw-container-padding-sm {
21
- padding-left: var(--iw-space-2);
21
+ padding-inline-start: var(--iw-space-2);
22
22
  }
23
23
 
24
24
  .iw-container-padding-md {
@@ -29,4 +29,11 @@
29
29
 
30
30
  .iw-bottom-navigation-label {
31
31
  font-size: var(--iw-font-size-sm);
32
+ /* One line, like its neighbours: a label too long for its tab is cut short
33
+ rather than wrapping, which in languages that break between any two
34
+ characters (Japanese, Chinese) splits a word and makes the bar taller. */
35
+ max-width: 100%;
36
+ overflow: hidden;
37
+ text-overflow: ellipsis;
38
+ white-space: nowrap;
32
39
  }
@@ -42,6 +42,12 @@
42
42
  transition: transform var(--iw-transition-fast);
43
43
  }
44
44
 
45
+ /* The toggle points at the edge the drawer sits on, which a right-to-left
46
+ page mirrors. `scale`, so the collapsed state's rotation still applies. */
47
+ .iw-drawer-toggler > span:dir(rtl) {
48
+ scale: -1 1;
49
+ }
50
+
45
51
  .iw-list-item-inset,
46
52
  .iw-list-item-content,
47
53
  .iw-list-item-trailing,
@@ -96,16 +102,18 @@
96
102
  }
97
103
  }
98
104
 
105
+ /* `left` and `right` are the start and end of the line: the left drawer
106
+ sits on the right in a right-to-left page, and slides off that way. */
99
107
  .iw-drawer-left {
100
- left: 0;
101
- border-right: 1px solid var(--iw-color-border);
102
- transform: translateX(-100%);
108
+ inset-inline-start: 0;
109
+ border-inline-end: 1px solid var(--iw-color-border);
110
+ transform: translateX(calc(-100% * var(--iw-dir-sign, 1)));
103
111
  }
104
112
 
105
113
  .iw-drawer-right {
106
- right: 0;
107
- border-left: 1px solid var(--iw-color-border);
108
- transform: translateX(100%);
114
+ inset-inline-end: 0;
115
+ border-inline-start: 1px solid var(--iw-color-border);
116
+ transform: translateX(calc(100% * var(--iw-dir-sign, 1)));
109
117
  }
110
118
 
111
119
  @media (min-width: 0rem) {
@@ -128,11 +136,11 @@
128
136
  box-shadow: none;
129
137
 
130
138
  &.iw-drawer-left.iw-drawer-hidden {
131
- transform: translateX(-100%);
139
+ transform: translateX(calc(-100% * var(--iw-dir-sign, 1)));
132
140
  }
133
141
 
134
142
  &.iw-drawer-right.iw-drawer-hidden {
135
- transform: translateX(100%);
143
+ transform: translateX(calc(100% * var(--iw-dir-sign, 1)));
136
144
  }
137
145
 
138
146
  & ~ .iw-drawer-backdrop {
@@ -19,7 +19,7 @@
19
19
  width: 100%;
20
20
  padding: var(--iw-space-2) var(--iw-space-3);
21
21
  border-radius: var(--iw-radius-sm);
22
- text-align: left;
22
+ text-align: start;
23
23
  cursor: pointer;
24
24
  }
25
25
 
@@ -29,7 +29,7 @@
29
29
  }
30
30
 
31
31
  .iw-menu-item-trailing {
32
- margin-left: auto;
32
+ margin-inline-start: auto;
33
33
  color: var(--iw-color-text-muted, var(--iw-color-text));
34
34
  }
35
35
 
@@ -89,25 +89,25 @@
89
89
 
90
90
  @keyframes iw-view-transition-out-left {
91
91
  to {
92
- transform: translateX(-100%);
92
+ transform: translateX(calc(-100% * var(--iw-dir-sign, 1)));
93
93
  }
94
94
  }
95
95
 
96
96
  @keyframes iw-view-transition-in-right {
97
97
  from {
98
- transform: translateX(100%);
98
+ transform: translateX(calc(100% * var(--iw-dir-sign, 1)));
99
99
  }
100
100
  }
101
101
 
102
102
  @keyframes iw-view-transition-out-right {
103
103
  to {
104
- transform: translateX(100%);
104
+ transform: translateX(calc(100% * var(--iw-dir-sign, 1)));
105
105
  }
106
106
  }
107
107
 
108
108
  @keyframes iw-view-transition-in-left {
109
109
  from {
110
- transform: translateX(-100%);
110
+ transform: translateX(calc(-100% * var(--iw-dir-sign, 1)));
111
111
  }
112
112
  }
113
113
 
@@ -1,3 +1,16 @@
1
+ /* The writing direction as a number, for what moves sideways: a slide that
2
+ goes `translateX(calc(-100% * var(--iw-dir-sign)))` leaves to the left in
3
+ English and to the right in Arabic. Set again on any element with a `dir`,
4
+ so a subtree in the other direction gets its own. */
5
+ :root,
6
+ [dir="ltr"] {
7
+ --iw-dir-sign: 1;
8
+ }
9
+
10
+ [dir="rtl"] {
11
+ --iw-dir-sign: -1;
12
+ }
13
+
1
14
  :root {
2
15
  /* ========================================
3
16
  COLOR PALETTE - Primitive Colors
@@ -105,5 +105,5 @@
105
105
  }
106
106
 
107
107
  .iw-app-bar-trailing {
108
- margin-left: auto;
108
+ margin-inline-start: auto;
109
109
  }
@@ -32,6 +32,8 @@ export interface ComboboxProps {
32
32
  isClearable?: boolean
33
33
 
34
34
  placeholder?: string
35
+ /** Placeholder of the search box in the panel. Defaults to "Search...". */
36
+ searchPlaceholder?: string
35
37
  noOptionsMessage?: string
36
38
  loadingMessage?: string
37
39
 
@@ -5,6 +5,11 @@ export interface MaterialIconProps {
5
5
  name?: string
6
6
  size?: "sm" | "md" | "lg"
7
7
  isFilled?: boolean
8
+ /**
9
+ * The direction of the text the icon sits in. An icon that points along the
10
+ * line (a back arrow, a chevron) is mirrored with `"rtl"`. Defaults to `"ltr"`.
11
+ */
12
+ direction?: "ltr" | "rtl"
8
13
  onClick?: () => void
9
14
  [key: string]: unknown
10
15
  }