@heliosgraphics/ui 2.0.1-alpha.8 → 2.0.1-beta.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/components/Button/Button.module.css +0 -8
- package/components/Flex/Flex.utils.ts +3 -3
- package/components/Heading/Heading.tsx +1 -3
- package/components/Icon/Icon.module.css +10 -11
- package/components/Icon/Icon.spec.tsx +40 -0
- package/components/Icon/Icon.tsx +8 -2
- package/components/Icon/Icon.utils.ts +11 -0
- package/components/Layout/components/LayoutAside/components/LayoutAsideToggle/LayoutAsideToggle.tsx +2 -2
- package/components/Layout/components/LayoutMain/components/LayoutMainContent/LayoutMainContent.utils.ts +4 -3
- package/components/Markdown/Markdown.meta.ts +1 -1
- package/components/Markdown/Markdown.module.css +0 -10
- package/components/Markdown/Markdown.tsx +2 -2
- package/components/Markdown/Markdown.types.ts +1 -1
- package/components/Setup/Setup.spec.tsx +53 -0
- package/components/Setup/Setup.utils.ts +4 -10
- package/components/Setup/css/feat.atomics.css +0 -7
- package/components/Tabs/Tabs.tsx +3 -9
- package/components/Tabs/Tabs.utils.ts +9 -0
- package/components/Text/Text.tsx +1 -1
- package/components/Tile/Tile.module.css +0 -1
- package/components/Tile/Tile.tsx +2 -2
- package/constants/scope.ts +2 -2
- package/contexts/LayoutContext/LayoutContext.tsx +0 -1
- package/hooks/useTheme.tsx +2 -2
- package/package.json +11 -11
|
@@ -49,7 +49,6 @@
|
|
|
49
49
|
box-shadow: inset 0 0 0 1px var(--border-active);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/* Reset */
|
|
53
52
|
.button .button__baseElement {
|
|
54
53
|
display: inline-flex;
|
|
55
54
|
|
|
@@ -68,7 +67,6 @@
|
|
|
68
67
|
pointer-events: none;
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
/* Sizes */
|
|
72
70
|
.buttonSizeNormal {
|
|
73
71
|
--button-badge-size: 24px;
|
|
74
72
|
--button-badge-margin: 8px;
|
|
@@ -136,7 +134,6 @@
|
|
|
136
134
|
padding-inline: 7px;
|
|
137
135
|
}
|
|
138
136
|
|
|
139
|
-
/* Loading */
|
|
140
137
|
.buttonLoading,
|
|
141
138
|
.buttonDisabled {
|
|
142
139
|
pointer-events: none;
|
|
@@ -174,7 +171,6 @@
|
|
|
174
171
|
right: 6px;
|
|
175
172
|
}
|
|
176
173
|
|
|
177
|
-
/* Badge */
|
|
178
174
|
.button__badge {
|
|
179
175
|
z-index: 2;
|
|
180
176
|
|
|
@@ -187,7 +183,6 @@
|
|
|
187
183
|
border-radius: var(--button-badge-radius);
|
|
188
184
|
}
|
|
189
185
|
|
|
190
|
-
/* Flair */
|
|
191
186
|
.button__flair {
|
|
192
187
|
position: absolute;
|
|
193
188
|
top: var(--button-flair-offset);
|
|
@@ -203,7 +198,6 @@
|
|
|
203
198
|
color: var(--text);
|
|
204
199
|
}
|
|
205
200
|
|
|
206
|
-
/* Icon */
|
|
207
201
|
.buttonSizeNormal .button__iconLeft,
|
|
208
202
|
.buttonSizeNormal .button__iconRight {
|
|
209
203
|
padding-left: 8px;
|
|
@@ -235,7 +229,6 @@
|
|
|
235
229
|
padding-right: 5px;
|
|
236
230
|
}
|
|
237
231
|
|
|
238
|
-
/* IconRight */
|
|
239
232
|
.buttonSizeNormal:not(.buttonIconOnly) .button__iconRight {
|
|
240
233
|
margin-left: -12px;
|
|
241
234
|
}
|
|
@@ -253,7 +246,6 @@
|
|
|
253
246
|
opacity: 0;
|
|
254
247
|
}
|
|
255
248
|
|
|
256
|
-
/* Icon:focus */
|
|
257
249
|
.buttonSizeNormal.buttonIconOnly:not(.buttonIconOnlyLoading) .button__baseElement {
|
|
258
250
|
margin-left: -40px;
|
|
259
251
|
width: 40px;
|
|
@@ -73,7 +73,7 @@ export const getFlexUtility = (props?: FlexProps): string => {
|
|
|
73
73
|
export const getResponsiveScale = (value?: ResponsiveScaleType, prefix: string = "p"): string => {
|
|
74
74
|
if (!value) return ""
|
|
75
75
|
|
|
76
|
-
const isArray: boolean =
|
|
76
|
+
const isArray: boolean = Array.isArray(value)
|
|
77
77
|
const classes = new Set<string>()
|
|
78
78
|
|
|
79
79
|
if (!isArray) return `${prefix}-${value}`
|
|
@@ -92,7 +92,7 @@ export const getResponsiveScale = (value?: ResponsiveScaleType, prefix: string =
|
|
|
92
92
|
export const getPadding = (paddingValue?: ResponsiveScaleType): string => {
|
|
93
93
|
if (!paddingValue) return ""
|
|
94
94
|
|
|
95
|
-
const isArray: boolean =
|
|
95
|
+
const isArray: boolean = Array.isArray(paddingValue)
|
|
96
96
|
const paddingClasses = new Set<string>()
|
|
97
97
|
|
|
98
98
|
if (!isArray) return `p-${paddingValue}`
|
|
@@ -111,7 +111,7 @@ export const getPadding = (paddingValue?: ResponsiveScaleType): string => {
|
|
|
111
111
|
export const getRadius = (radiusValue?: ResponsiveRadiusType): string => {
|
|
112
112
|
if (!radiusValue) return ""
|
|
113
113
|
|
|
114
|
-
const isArray: boolean =
|
|
114
|
+
const isArray: boolean = Array.isArray(radiusValue)
|
|
115
115
|
const radiusClasses = new Set<string>()
|
|
116
116
|
|
|
117
117
|
if (!isArray) return `radius-${radiusValue}`
|
|
@@ -32,9 +32,7 @@ export const Heading: FC<HeadingProps> = (props) => {
|
|
|
32
32
|
const lineHeightStyle: CSSProperties | undefined = lineHeight !== undefined ? { lineHeight } : undefined
|
|
33
33
|
|
|
34
34
|
const mergedStyle: CSSProperties | undefined =
|
|
35
|
-
style || lineClampStyle || lineHeightStyle
|
|
36
|
-
? { ...(style || {}), ...(lineClampStyle || {}), ...(lineHeightStyle || {}) }
|
|
37
|
-
: undefined
|
|
35
|
+
style || lineClampStyle || lineHeightStyle ? { ...style, ...lineClampStyle, ...lineHeightStyle } : undefined
|
|
38
36
|
const safeHeadingProps: Omit<HeadingProps, "level" | "lineClamp" | "lineHeight" | "className" | "style"> =
|
|
39
37
|
stripTypographyProps(rest)
|
|
40
38
|
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
.icon {
|
|
2
|
+
color: currentColor;
|
|
2
3
|
transition: all var(--speed-sm) var(--ease-in-out-sine);
|
|
3
4
|
}
|
|
4
5
|
|
|
5
6
|
.icon svg {
|
|
6
7
|
height: 100%;
|
|
7
8
|
width: 100%;
|
|
9
|
+
}
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
.iconPrimary {
|
|
12
|
+
color: var(--ui-text-primary);
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
.
|
|
14
|
-
|
|
15
|
-
stroke: var(--ui-text-primary);
|
|
15
|
+
.iconSecondary {
|
|
16
|
+
color: var(--ui-text-secondary);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
.
|
|
19
|
-
|
|
20
|
-
stroke: var(--ui-text-secondary);
|
|
19
|
+
.iconTertiary {
|
|
20
|
+
color: var(--ui-text-tertiary);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
.
|
|
24
|
-
|
|
25
|
-
stroke: var(--ui-text-tertiary);
|
|
23
|
+
.iconInherit {
|
|
24
|
+
color: inherit;
|
|
26
25
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test"
|
|
2
|
+
import { icons } from "@heliosgraphics/icons"
|
|
3
|
+
import { readFileSync } from "fs"
|
|
4
|
+
import { dirname, join } from "path"
|
|
5
|
+
import { fileURLToPath } from "url"
|
|
6
|
+
import { getIconIdScope, getScopedIconSvg } from "./Icon.utils"
|
|
7
|
+
|
|
8
|
+
const __filename: string = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname: string = dirname(__filename)
|
|
10
|
+
|
|
11
|
+
describe("Icon", () => {
|
|
12
|
+
it("should preserve fill-only, stroke-only, and mixed icon paint modes", () => {
|
|
13
|
+
expect(icons["triangle"]).toContain('fill="currentColor"')
|
|
14
|
+
expect(icons["triangle"]).not.toContain('stroke="currentColor"')
|
|
15
|
+
|
|
16
|
+
expect(icons["arrow-right"]).toContain('stroke="currentColor"')
|
|
17
|
+
expect(icons["arrow-right"]).not.toContain('fill="currentColor"')
|
|
18
|
+
|
|
19
|
+
expect(icons["tag"]).toContain('fill="currentColor"')
|
|
20
|
+
expect(icons["tag"]).toContain('stroke="currentColor"')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it("should not force fill or stroke on every svg", () => {
|
|
24
|
+
const iconStyles: string = readFileSync(join(__dirname, "Icon.module.css"), "utf8")
|
|
25
|
+
|
|
26
|
+
expect(iconStyles).toContain(".icon svg")
|
|
27
|
+
expect(iconStyles).not.toContain(".icon svg {\n\theight: 100%;\n\twidth: 100%;\n\n\tfill: currentcolor;")
|
|
28
|
+
expect(iconStyles).not.toContain("stroke: currentcolor;")
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("should scope svg definitions per icon instance", () => {
|
|
32
|
+
const scope: string = getIconIdScope(":r1:")
|
|
33
|
+
const actual: string = getScopedIconSvg(icons["layout-aside-right"], scope)
|
|
34
|
+
|
|
35
|
+
expect(actual).toContain(`id="${scope}-a"`)
|
|
36
|
+
expect(actual).toContain(`mask="url(#${scope}-a)"`)
|
|
37
|
+
expect(actual).not.toContain('id="a"')
|
|
38
|
+
expect(actual).not.toContain("url(#a)")
|
|
39
|
+
})
|
|
40
|
+
})
|
package/components/Icon/Icon.tsx
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
1
3
|
import { getClasses } from "@heliosgraphics/utils"
|
|
2
4
|
import { icons } from "@heliosgraphics/icons"
|
|
3
5
|
import styles from "./Icon.module.css"
|
|
4
|
-
import type
|
|
6
|
+
import { useId, type FC } from "react"
|
|
7
|
+
import { getIconIdScope, getScopedIconSvg } from "./Icon.utils"
|
|
5
8
|
import type { IconProps } from "./Icon.types"
|
|
6
9
|
|
|
7
10
|
export const Icon: FC<IconProps> = ({ icon, className, emphasis, size }) => {
|
|
11
|
+
const iconId: string = getIconIdScope(useId())
|
|
12
|
+
const iconSvg: string = getScopedIconSvg(icons[icon] || "", iconId)
|
|
8
13
|
const iconSizeStyle = {
|
|
9
14
|
height: size + "px",
|
|
10
15
|
minHeight: size + "px",
|
|
@@ -16,6 +21,7 @@ export const Icon: FC<IconProps> = ({ icon, className, emphasis, size }) => {
|
|
|
16
21
|
[styles.iconPrimary]: emphasis === "primary",
|
|
17
22
|
[styles.iconSecondary]: emphasis === "secondary",
|
|
18
23
|
[styles.iconTertiary]: emphasis === "tertiary",
|
|
24
|
+
[styles.iconInherit]: emphasis === "inherit",
|
|
19
25
|
})
|
|
20
26
|
|
|
21
27
|
return (
|
|
@@ -25,7 +31,7 @@ export const Icon: FC<IconProps> = ({ icon, className, emphasis, size }) => {
|
|
|
25
31
|
aria-hidden={true}
|
|
26
32
|
data-ui-component="Icon"
|
|
27
33
|
data-ui-icon={icon}
|
|
28
|
-
dangerouslySetInnerHTML={{ __html:
|
|
34
|
+
dangerouslySetInnerHTML={{ __html: iconSvg }}
|
|
29
35
|
/>
|
|
30
36
|
)
|
|
31
37
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const HREF_REFERENCE_REGEX = /\b(href|xlink:href)="#([^"]+)"/g
|
|
2
|
+
const ID_ATTRIBUTE_REGEX = /\bid="([^"]+)"/g
|
|
3
|
+
const URL_REFERENCE_REGEX = /url\(#([^\)]+)\)/g
|
|
4
|
+
|
|
5
|
+
export const getIconIdScope = (id: string): string => `icon-${id.replace(/[^a-zA-Z0-9_-]/g, "")}`
|
|
6
|
+
|
|
7
|
+
export const getScopedIconSvg = (svg: string, scope: string): string =>
|
|
8
|
+
svg
|
|
9
|
+
.replace(ID_ATTRIBUTE_REGEX, `id="${scope}-$1"`)
|
|
10
|
+
.replace(URL_REFERENCE_REGEX, `url(#${scope}-$1)`)
|
|
11
|
+
.replace(HREF_REFERENCE_REGEX, `$1="#${scope}-$2"`)
|
package/components/Layout/components/LayoutAside/components/LayoutAsideToggle/LayoutAsideToggle.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import type { FC } from "react"
|
|
4
|
-
import { Button } from "../../../../../Button
|
|
5
|
-
import { ButtonGroup } from "../../../../../ButtonGroup
|
|
4
|
+
import { Button } from "../../../../../Button"
|
|
5
|
+
import { ButtonGroup } from "../../../../../ButtonGroup"
|
|
6
6
|
import { useLayoutContext } from "../../../../../../hooks/useLayoutContext"
|
|
7
7
|
import type { LayoutAsideToggleProps } from "./LayoutAsideToggle.types"
|
|
8
8
|
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
let lastLocation: string | null = null
|
|
4
4
|
let isHistoryPatched: boolean = false
|
|
5
5
|
|
|
6
|
+
const dispatchLocationChange = (): void => {
|
|
7
|
+
globalThis.dispatchEvent(new Event("locationchange"))
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
const getLocationKey = (): string | null => {
|
|
7
11
|
if (!globalThis?.location) return null
|
|
8
12
|
|
|
@@ -15,9 +19,6 @@ const patchHistory = (): void => {
|
|
|
15
19
|
isHistoryPatched = true
|
|
16
20
|
|
|
17
21
|
const { pushState, replaceState } = globalThis.history
|
|
18
|
-
const dispatchLocationChange = (): void => {
|
|
19
|
-
globalThis.dispatchEvent(new Event("locationchange"))
|
|
20
|
-
}
|
|
21
22
|
|
|
22
23
|
const pushStateWrapper = (...args: Parameters<History["pushState"]>): ReturnType<History["pushState"]> => {
|
|
23
24
|
const result = pushState.apply(globalThis.history, args as Parameters<History["pushState"]>)
|
|
@@ -16,7 +16,7 @@ export const meta: HeliosAttributeMeta<MarkdownProps> = {
|
|
|
16
16
|
isOptional: true,
|
|
17
17
|
description: "Children to render inside the markdown wrapper",
|
|
18
18
|
},
|
|
19
|
-
|
|
19
|
+
isLinksAllowed: {
|
|
20
20
|
type: "boolean",
|
|
21
21
|
isOptional: true,
|
|
22
22
|
description: "Preserves markdown links and auto-linked URLs in sanitized output",
|
|
@@ -67,7 +67,6 @@
|
|
|
67
67
|
line-height: normal;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
/* Element: Blockquote */
|
|
71
70
|
.markdown blockquote {
|
|
72
71
|
position: relative;
|
|
73
72
|
|
|
@@ -110,7 +109,6 @@
|
|
|
110
109
|
line-height: inherit;
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
/* Element: Link */
|
|
114
112
|
.markdown a {
|
|
115
113
|
color: oklch(var(--ui-text-soft-l) var(--ui-chroma) var(--ui-blue));
|
|
116
114
|
|
|
@@ -118,7 +116,6 @@
|
|
|
118
116
|
text-decoration: underline;
|
|
119
117
|
}
|
|
120
118
|
|
|
121
|
-
/* Element: List */
|
|
122
119
|
.markdown ul li {
|
|
123
120
|
list-style-type: disc;
|
|
124
121
|
}
|
|
@@ -140,13 +137,11 @@
|
|
|
140
137
|
list-style-type: circle;
|
|
141
138
|
}
|
|
142
139
|
|
|
143
|
-
/* Element: Separator */
|
|
144
140
|
.markdown hr {
|
|
145
141
|
border: 0;
|
|
146
142
|
border-top: 1px solid var(--ui-border-secondary);
|
|
147
143
|
}
|
|
148
144
|
|
|
149
|
-
/* Element: Table */
|
|
150
145
|
.markdown table {
|
|
151
146
|
max-width: 100%;
|
|
152
147
|
|
|
@@ -181,7 +176,6 @@
|
|
|
181
176
|
border-radius: var(--radius-md);
|
|
182
177
|
}
|
|
183
178
|
|
|
184
|
-
/* Element: Headings */
|
|
185
179
|
.markdown h1 {
|
|
186
180
|
font-size: var(--font-size-h1);
|
|
187
181
|
line-height: var(--line-height-h1);
|
|
@@ -222,24 +216,20 @@
|
|
|
222
216
|
line-height: var(--line-height-p);
|
|
223
217
|
}
|
|
224
218
|
|
|
225
|
-
/* Element: strong, bold: 700 */
|
|
226
219
|
.markdown b,
|
|
227
220
|
.markdown strong {
|
|
228
221
|
font-weight: var(--font-weight-bold);
|
|
229
222
|
}
|
|
230
223
|
|
|
231
|
-
/* Element: Delete */
|
|
232
224
|
.markdown del {
|
|
233
225
|
text-decoration: line-through;
|
|
234
226
|
}
|
|
235
227
|
|
|
236
|
-
/* Element: Italic */
|
|
237
228
|
.markdown i,
|
|
238
229
|
.markdown em {
|
|
239
230
|
font-style: italic;
|
|
240
231
|
}
|
|
241
232
|
|
|
242
|
-
/* Modifier: Code */
|
|
243
233
|
.markdown *:not(pre) code {
|
|
244
234
|
display: inline-block;
|
|
245
235
|
padding: 0 4px;
|
|
@@ -4,7 +4,7 @@ import styles from "./Markdown.module.css"
|
|
|
4
4
|
import type { FC } from "react"
|
|
5
5
|
import type { MarkdownProps } from "./Markdown.types"
|
|
6
6
|
|
|
7
|
-
export const Markdown: FC<MarkdownProps> = ({ text, children, isNonSelectable,
|
|
7
|
+
export const Markdown: FC<MarkdownProps> = ({ text, children, isNonSelectable, isLinksAllowed }) => {
|
|
8
8
|
if (!text && !children) return null
|
|
9
9
|
|
|
10
10
|
const markdownClasses: string = getClasses(styles.markdown, {
|
|
@@ -12,7 +12,7 @@ export const Markdown: FC<MarkdownProps> = ({ text, children, isNonSelectable, a
|
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
if (text) {
|
|
15
|
-
const innerHTML = { __html: renderMarkdown(text, { allowLinks }) }
|
|
15
|
+
const innerHTML = { __html: renderMarkdown(text, { allowLinks: !!isLinksAllowed }) }
|
|
16
16
|
|
|
17
17
|
return <div className={markdownClasses} dangerouslySetInnerHTML={innerHTML} data-ui-component="Markdown"></div>
|
|
18
18
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test"
|
|
2
|
+
import type { HeliosFixedThemeType } from "../../types/themes"
|
|
3
|
+
import { code } from "./Setup.utils"
|
|
4
|
+
|
|
5
|
+
describe("Setup", () => {
|
|
6
|
+
it("should execute when serialized into an inline script", () => {
|
|
7
|
+
const rootDataset: Record<string, string> = {}
|
|
8
|
+
const localStorageValues = new Map<string, string>()
|
|
9
|
+
const listeners: Record<string, (event: { matches: boolean }) => void> = {}
|
|
10
|
+
|
|
11
|
+
globalThis.__theme = "light" as HeliosFixedThemeType
|
|
12
|
+
globalThis.__onThemeChange = undefined
|
|
13
|
+
globalThis.__setPreferredTheme = (): void => {}
|
|
14
|
+
globalThis.document = {
|
|
15
|
+
documentElement: {
|
|
16
|
+
dataset: rootDataset,
|
|
17
|
+
},
|
|
18
|
+
} as Document
|
|
19
|
+
globalThis.localStorage = {
|
|
20
|
+
getItem: (key: string): string | null => localStorageValues.get(key) ?? null,
|
|
21
|
+
setItem: (key: string, value: string): void => {
|
|
22
|
+
localStorageValues.set(key, value)
|
|
23
|
+
},
|
|
24
|
+
} as Storage
|
|
25
|
+
globalThis.matchMedia = ((query: string): MediaQueryList => {
|
|
26
|
+
expect(query).toBe("(prefers-color-scheme: dark)")
|
|
27
|
+
return {
|
|
28
|
+
matches: true,
|
|
29
|
+
addEventListener: (type: string, listener: (event: { matches: boolean }) => void): void => {
|
|
30
|
+
listeners[type] = listener
|
|
31
|
+
},
|
|
32
|
+
} as MediaQueryList
|
|
33
|
+
}) as typeof globalThis.matchMedia
|
|
34
|
+
|
|
35
|
+
new Function(`return (${code.toString()})("system", undefined)`)()
|
|
36
|
+
|
|
37
|
+
expect(globalThis.__theme).toBe("dark")
|
|
38
|
+
expect(rootDataset["theme"]).toBe("dark")
|
|
39
|
+
expect(typeof listeners["change"]).toBe("function")
|
|
40
|
+
|
|
41
|
+
const changeListener = listeners["change"]
|
|
42
|
+
expect(changeListener).toBeDefined()
|
|
43
|
+
if (!changeListener) {
|
|
44
|
+
throw new Error("expected change listener to be registered")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
changeListener({ matches: false })
|
|
48
|
+
|
|
49
|
+
expect(globalThis.__theme).toBe("light")
|
|
50
|
+
expect(rootDataset["theme"]).toBe("light")
|
|
51
|
+
expect(localStorageValues.get("theme")).toBe("light")
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -3,20 +3,18 @@ import type { HeliosFixedThemeType, HeliosThemeType } from "../../types/themes"
|
|
|
3
3
|
export const code = (theme: HeliosThemeType = "system", fixedTheme?: HeliosFixedThemeType): void => {
|
|
4
4
|
globalThis.__onThemeChange = function (_theme: HeliosFixedThemeType): void {}
|
|
5
5
|
|
|
6
|
+
/* eslint-disable-next-line unicorn/consistent-function-scoping */
|
|
6
7
|
const setTheme = (newTheme: HeliosFixedThemeType): void => {
|
|
7
8
|
globalThis.__theme = newTheme
|
|
8
9
|
document.documentElement.dataset["theme"] = newTheme
|
|
9
10
|
globalThis.__onThemeChange?.(newTheme)
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
13
|
+
/* eslint-disable-next-line unicorn/consistent-function-scoping */
|
|
14
|
+
const handleDarkModeChange = (event: MediaQueryListEvent): void => {
|
|
15
|
+
globalThis.__setPreferredTheme(event.matches ? "dark" : "light")
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
document.addEventListener("click", handleDocumentClick)
|
|
19
|
-
|
|
20
18
|
const isLocked: boolean = fixedTheme !== undefined || theme !== "system"
|
|
21
19
|
|
|
22
20
|
if (fixedTheme !== undefined && theme !== "system") {
|
|
@@ -51,10 +49,6 @@ export const code = (theme: HeliosThemeType = "system", fixedTheme?: HeliosFixed
|
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
const handleDarkModeChange = (event: MediaQueryListEvent): void => {
|
|
55
|
-
globalThis.__setPreferredTheme(event.matches ? "dark" : "light")
|
|
56
|
-
}
|
|
57
|
-
|
|
58
52
|
darkQuery.addEventListener("change", handleDarkModeChange)
|
|
59
53
|
|
|
60
54
|
setTheme(preferredTheme ?? (darkQuery.matches ? "dark" : "light"))
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
@layer helios {
|
|
2
|
-
/* TODO sync with css */
|
|
3
|
-
|
|
4
|
-
/* background */
|
|
5
2
|
.ui-bg,
|
|
6
3
|
.ui-bg-primary {
|
|
7
4
|
background-color: var(--ui-bg-primary);
|
|
@@ -20,7 +17,6 @@
|
|
|
20
17
|
backdrop-filter: blur(3px);
|
|
21
18
|
}
|
|
22
19
|
|
|
23
|
-
/* radius utilities */
|
|
24
20
|
.radius-none {
|
|
25
21
|
border-radius: 0;
|
|
26
22
|
}
|
|
@@ -53,7 +49,6 @@
|
|
|
53
49
|
border-radius: 9999px;
|
|
54
50
|
}
|
|
55
51
|
|
|
56
|
-
/* scrollbar */
|
|
57
52
|
.ui-scrollbar::-webkit-scrollbar {
|
|
58
53
|
height: 8px;
|
|
59
54
|
width: 8px;
|
|
@@ -84,7 +79,6 @@
|
|
|
84
79
|
background-color: var(--ui-bg-soft-disabled-gray);
|
|
85
80
|
}
|
|
86
81
|
|
|
87
|
-
/* elevation */
|
|
88
82
|
.elevation-sm {
|
|
89
83
|
box-shadow: var(--ui-elevation-sm);
|
|
90
84
|
}
|
|
@@ -97,7 +91,6 @@
|
|
|
97
91
|
box-shadow: var(--ui-elevation-lg);
|
|
98
92
|
}
|
|
99
93
|
|
|
100
|
-
/* debug */
|
|
101
94
|
.debug * {
|
|
102
95
|
box-shadow: inset 0 0 0 1px var(--ui-bg-red);
|
|
103
96
|
}
|
package/components/Tabs/Tabs.tsx
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import { getClasses } from "@heliosgraphics/utils"
|
|
2
2
|
import { Text } from "../Text"
|
|
3
|
+
import { onTabKeyDown } from "./Tabs.utils"
|
|
3
4
|
import styles from "./Tabs.module.css"
|
|
4
|
-
import type { FC
|
|
5
|
+
import type { FC } from "react"
|
|
5
6
|
import type { TabItem, TabsProps } from "./Tabs.types"
|
|
6
7
|
|
|
7
8
|
export const Tabs: FC<TabsProps> = ({ items, children }) => {
|
|
8
9
|
if (!items?.length) return null
|
|
9
10
|
|
|
10
|
-
const onKeyDown = (event: KeyboardEvent<HTMLElement>, item: TabItem): void => {
|
|
11
|
-
if (event.key === " " || event.key === "Enter") {
|
|
12
|
-
event.preventDefault()
|
|
13
|
-
item.onClick?.()
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
11
|
return (
|
|
18
12
|
<div data-ui-component="Tabs">
|
|
19
13
|
<div role="tablist" className={styles.tabs__list}>
|
|
@@ -46,7 +40,7 @@ export const Tabs: FC<TabsProps> = ({ items, children }) => {
|
|
|
46
40
|
<div
|
|
47
41
|
{...commonProps}
|
|
48
42
|
onClick={item.isDisabled ? undefined : item.onClick}
|
|
49
|
-
onKeyDown={(
|
|
43
|
+
onKeyDown={(event) => onTabKeyDown(event, item)}
|
|
50
44
|
>
|
|
51
45
|
<Text type="small" fontWeight="medium">
|
|
52
46
|
{item.name}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { KeyboardEvent } from "react"
|
|
2
|
+
import type { TabItem } from "./Tabs.types"
|
|
3
|
+
|
|
4
|
+
export const onTabKeyDown = (event: KeyboardEvent<HTMLElement>, item: TabItem): void => {
|
|
5
|
+
if (event.key === " " || event.key === "Enter") {
|
|
6
|
+
event.preventDefault()
|
|
7
|
+
item.onClick?.()
|
|
8
|
+
}
|
|
9
|
+
}
|
package/components/Text/Text.tsx
CHANGED
|
@@ -28,7 +28,7 @@ export const Text: FC<TextProps> = (props) => {
|
|
|
28
28
|
: undefined
|
|
29
29
|
|
|
30
30
|
const mergedStyle: object | undefined =
|
|
31
|
-
props.style || lineClampStyle ? { ...
|
|
31
|
+
props.style || lineClampStyle ? { ...props.style, ...lineClampStyle } : undefined
|
|
32
32
|
|
|
33
33
|
const baseTextProps: Omit<TextProps, "type"> = {
|
|
34
34
|
onClick: props.onClick,
|
package/components/Tile/Tile.tsx
CHANGED
|
@@ -34,8 +34,8 @@ export const Tile: FC<TileProps> = ({
|
|
|
34
34
|
const tileColorClasses: Array<string> = getColorClasses(resolvedColor, appearance)
|
|
35
35
|
const tileClasses = getClasses(styles.tile, ...tileColorClasses, {
|
|
36
36
|
[styles.tileLarge]: isLarge,
|
|
37
|
-
["radius-
|
|
38
|
-
["radius-
|
|
37
|
+
["radius-md"]: isRounded,
|
|
38
|
+
["radius-100"]: isRound,
|
|
39
39
|
})
|
|
40
40
|
|
|
41
41
|
return (
|
package/constants/scope.ts
CHANGED
|
@@ -32,9 +32,9 @@ const RESULT_ITEMS: Array<ResultItem> = [
|
|
|
32
32
|
onClick: () => null,
|
|
33
33
|
description: "Nulla ultricies ultrices mauris, sed posuere justo ultrices in.",
|
|
34
34
|
},
|
|
35
|
-
{ name: "Random Item", icon: "
|
|
35
|
+
{ name: "Random Item", icon: "user" },
|
|
36
36
|
{ name: "Active Item", icon: "bell", isActive: true },
|
|
37
|
-
{ name: "Lorem Ipsum Dolor Sit Ameta", icon: "
|
|
37
|
+
{ name: "Lorem Ipsum Dolor Sit Ameta", icon: "brand-x" },
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
const MARKDOWN: string = `# Donec vestibulum
|
package/hooks/useTheme.tsx
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { useCallback, useEffect, useState } from "react"
|
|
4
4
|
import type { HeliosFixedThemeType } from "../types/themes"
|
|
5
5
|
|
|
6
|
+
const getTheme = (): HeliosFixedThemeType => (typeof globalThis !== "undefined" ? globalThis.__theme : "light")
|
|
7
|
+
|
|
6
8
|
export const useTheme = (): { theme: HeliosFixedThemeType; isDark: boolean; toggleTheme: () => void } => {
|
|
7
9
|
const [theme, setTheme] = useState<HeliosFixedThemeType | null>(null)
|
|
8
10
|
|
|
@@ -10,8 +12,6 @@ export const useTheme = (): { theme: HeliosFixedThemeType; isDark: boolean; togg
|
|
|
10
12
|
setTheme(getTheme())
|
|
11
13
|
}, [])
|
|
12
14
|
|
|
13
|
-
const getTheme = (): HeliosFixedThemeType => (typeof globalThis !== "undefined" ? globalThis.__theme : "light")
|
|
14
|
-
|
|
15
15
|
const toggleTheme = useCallback(() => {
|
|
16
16
|
const currentIsDark: boolean = getTheme() === "dark"
|
|
17
17
|
globalThis.__setPreferredTheme?.(currentIsDark ? "light" : "dark")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heliosgraphics/ui",
|
|
3
|
-
"version": "2.0.1-
|
|
3
|
+
"version": "2.0.1-beta.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"*.css",
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"ts:watch": "tsc --noEmit --incremental --watch"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@heliosgraphics/css": "^1.0.0-alpha.
|
|
44
|
-
"@heliosgraphics/icons": "^1.0.0-
|
|
45
|
-
"@heliosgraphics/utils": "^6.0.0-alpha.
|
|
46
|
-
"marked": "^
|
|
47
|
-
"marked-linkify-it": "^3.1.
|
|
48
|
-
"marked-xhtml": "^1.0.
|
|
43
|
+
"@heliosgraphics/css": "^1.0.0-alpha.10",
|
|
44
|
+
"@heliosgraphics/icons": "^1.0.0-beta.0",
|
|
45
|
+
"@heliosgraphics/utils": "^6.0.0-alpha.17",
|
|
46
|
+
"marked": "^18.0.3",
|
|
47
|
+
"marked-linkify-it": "^3.1.15",
|
|
48
|
+
"marked-xhtml": "^1.0.15",
|
|
49
49
|
"react-plock": "^3.6.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"esbuild": "^0.28.0",
|
|
55
55
|
"esbuild-css-modules-plugin": "^3.1.5",
|
|
56
56
|
"glob": "^13.0.6",
|
|
57
|
-
"jsdom": "^29.
|
|
58
|
-
"next": "^16.2.
|
|
59
|
-
"prettier": "^3.8.
|
|
60
|
-
"typescript": "^6.0.
|
|
57
|
+
"jsdom": "^29.1.1",
|
|
58
|
+
"next": "^16.2.6",
|
|
59
|
+
"prettier": "^3.8.3",
|
|
60
|
+
"typescript": "^6.0.3"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"@types/react": "^19",
|