@dezkareid/components 0.0.0 → 1.0.1
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 +213 -5
- package/dist/_virtual/_commonjsHelpers.js +6 -0
- package/dist/_virtual/_commonjsHelpers.js.map +1 -0
- package/dist/_virtual/index.js +8 -0
- package/dist/_virtual/index.js.map +1 -0
- package/dist/_virtual/index2.js +4 -0
- package/dist/_virtual/index2.js.map +1 -0
- package/dist/astro/index.d.ts +5 -0
- package/dist/astro/index.d.ts.map +1 -0
- package/dist/components.min.css +1 -0
- package/dist/css/button.module.css.js +4 -0
- package/dist/css/button.module.css.js.map +1 -0
- package/dist/css/card.module.css.js +4 -0
- package/dist/css/card.module.css.js.map +1 -0
- package/dist/css/index.d.ts +5 -0
- package/dist/css/index.d.ts.map +1 -0
- package/dist/css/tag.module.css.js +4 -0
- package/dist/css/tag.module.css.js.map +1 -0
- package/dist/css/theme-toggle.module.css.js +4 -0
- package/dist/css/theme-toggle.module.css.js.map +1 -0
- package/dist/node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js +86 -0
- package/dist/node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js.map +1 -0
- package/dist/react/Button/index.d.ts +6 -0
- package/dist/react/Button/index.d.ts.map +1 -0
- package/dist/react/Button/index.js +10 -0
- package/dist/react/Button/index.js.map +1 -0
- package/dist/react/Card/index.d.ts +6 -0
- package/dist/react/Card/index.d.ts.map +1 -0
- package/dist/react/Card/index.js +10 -0
- package/dist/react/Card/index.js.map +1 -0
- package/dist/react/Tag/index.d.ts +6 -0
- package/dist/react/Tag/index.d.ts.map +1 -0
- package/dist/react/Tag/index.js +10 -0
- package/dist/react/Tag/index.js.map +1 -0
- package/dist/react/ThemeToggle/index.d.ts +2 -0
- package/dist/react/ThemeToggle/index.d.ts.map +1 -0
- package/dist/react/ThemeToggle/index.js +25 -0
- package/dist/react/ThemeToggle/index.js.map +1 -0
- package/dist/react/index.d.ts +5 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +5 -0
- package/dist/react/index.js.map +1 -0
- package/dist/shared/js/theme.d.ts +5 -0
- package/dist/shared/js/theme.d.ts.map +1 -0
- package/dist/shared/js/theme.js +22 -0
- package/dist/shared/js/theme.js.map +1 -0
- package/dist/shared/types/button.d.ts +8 -0
- package/dist/shared/types/button.d.ts.map +1 -0
- package/dist/shared/types/card.d.ts +5 -0
- package/dist/shared/types/card.d.ts.map +1 -0
- package/dist/shared/types/tag.d.ts +5 -0
- package/dist/shared/types/tag.d.ts.map +1 -0
- package/dist/shared/types/theme-toggle.d.ts +4 -0
- package/dist/shared/types/theme-toggle.d.ts.map +1 -0
- package/dist/vue/index.d.ts +5 -0
- package/dist/vue/index.d.ts.map +1 -0
- package/package.json +81 -6
- package/src/astro/Button/index.astro +35 -0
- package/src/astro/Card/index.astro +23 -0
- package/src/astro/Tag/index.astro +23 -0
- package/src/astro/ThemeToggle/index.astro +63 -0
- package/src/astro/index.ts +4 -0
- package/src/css/button.module.css +90 -0
- package/src/css/card.module.css +30 -0
- package/src/css/index.ts +4 -0
- package/src/css/tag.module.css +33 -0
- package/src/css/theme-toggle.module.css +38 -0
- package/src/declarations.d.ts +19 -0
- package/src/react/Button/index.test.tsx +59 -0
- package/src/react/Button/index.tsx +31 -0
- package/src/react/Card/index.test.tsx +38 -0
- package/src/react/Card/index.tsx +14 -0
- package/src/react/Tag/index.test.tsx +35 -0
- package/src/react/Tag/index.tsx +14 -0
- package/src/react/ThemeToggle/index.test.tsx +84 -0
- package/src/react/ThemeToggle/index.tsx +36 -0
- package/src/react/index.ts +4 -0
- package/src/shared/js/theme.ts +22 -0
- package/src/shared/types/button.ts +8 -0
- package/src/shared/types/card.ts +5 -0
- package/src/shared/types/tag.ts +5 -0
- package/src/shared/types/theme-toggle.ts +5 -0
- package/src/vue/Button/index.vue +27 -0
- package/src/vue/Card/index.vue +18 -0
- package/src/vue/Tag/index.vue +18 -0
- package/src/vue/ThemeToggle/index.vue +39 -0
- package/src/vue/index.ts +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tag.d.ts","sourceRoot":"","sources":["../../../src/shared/types/tag.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE1D,MAAM,WAAW,QAAQ;IACvB,OAAO,CAAC,EAAE,UAAU,CAAC;CACtB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-toggle.d.ts","sourceRoot":"","sources":["../../../src/shared/types/theme-toggle.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC;AAErC,MAAM,WAAW,gBAAgB;CAEhC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vue/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,yBAAyB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dezkareid/components",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "A package to export UI components in formats like React, Astro, Vue, etc.",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"src",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
"./react": {
|
|
13
|
+
"types": "./dist/react/index.d.ts",
|
|
14
|
+
"import": "./dist/react.js",
|
|
15
|
+
"default": "./dist/react.js"
|
|
16
|
+
},
|
|
17
|
+
"./astro": "./src/astro/index.ts",
|
|
18
|
+
"./vue": "./src/vue/index.ts",
|
|
19
|
+
"./css": {
|
|
20
|
+
"import": "./dist/components.min.css",
|
|
21
|
+
"default": "./dist/components.min.css"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"typesVersions": {
|
|
25
|
+
"*": {
|
|
26
|
+
"react": [
|
|
27
|
+
"./dist/react/index.d.ts"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
5
31
|
"repository": {
|
|
6
32
|
"type": "git",
|
|
7
33
|
"url": "https://github.com/dezkareid/dezkareid"
|
|
@@ -15,8 +41,57 @@
|
|
|
15
41
|
"access": "public"
|
|
16
42
|
},
|
|
17
43
|
"keywords": [
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
44
|
+
"design-system",
|
|
45
|
+
"components",
|
|
46
|
+
"react",
|
|
47
|
+
"astro",
|
|
48
|
+
"vue"
|
|
49
|
+
],
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"astro": ">=4.0.0",
|
|
52
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
53
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
54
|
+
"vue": "^3.0.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"astro": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"vue": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"react": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"react-dom": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"classnames": "2.5.1",
|
|
72
|
+
"@dezkareid/design-tokens": "0.0.0"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@rollup/plugin-commonjs": "29.0.0",
|
|
76
|
+
"@rollup/plugin-node-resolve": "16.0.3",
|
|
77
|
+
"@rollup/plugin-typescript": "12.3.0",
|
|
78
|
+
"@testing-library/jest-dom": "6.9.1",
|
|
79
|
+
"@testing-library/react": "16.3.2",
|
|
80
|
+
"@testing-library/user-event": "14.5.2",
|
|
81
|
+
"@types/classnames": "^2.3.4",
|
|
82
|
+
"@types/react": "19.2.9",
|
|
83
|
+
"@vitejs/plugin-react": "5.1.4",
|
|
84
|
+
"jsdom": "27.4.0",
|
|
85
|
+
"react": "19.2.4",
|
|
86
|
+
"react-dom": "19.2.4",
|
|
87
|
+
"rollup": "4.56.0",
|
|
88
|
+
"rollup-plugin-postcss": "4.0.2",
|
|
89
|
+
"typescript": "5.9.3",
|
|
90
|
+
"vite": "7.3.1",
|
|
91
|
+
"vitest": "4.0.18"
|
|
92
|
+
},
|
|
93
|
+
"scripts": {
|
|
94
|
+
"build": "rollup -c rollup.config.mjs",
|
|
95
|
+
"test": "vitest --run"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { ButtonProps } from '../../shared/types/button';
|
|
3
|
+
import styles from '../../css/button.module.css';
|
|
4
|
+
|
|
5
|
+
type Props = ButtonProps & {
|
|
6
|
+
href?: string;
|
|
7
|
+
class?: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
variant = 'primary',
|
|
13
|
+
size = 'md',
|
|
14
|
+
disabled = false,
|
|
15
|
+
href,
|
|
16
|
+
class: className,
|
|
17
|
+
...rest
|
|
18
|
+
} = Astro.props;
|
|
19
|
+
|
|
20
|
+
const Tag = href ? 'a' : 'button';
|
|
21
|
+
|
|
22
|
+
const classes = [
|
|
23
|
+
styles.button,
|
|
24
|
+
styles[`button--${variant}`],
|
|
25
|
+
styles[`button--${size}`],
|
|
26
|
+
disabled ? styles['button--disabled'] : '',
|
|
27
|
+
className ?? '',
|
|
28
|
+
]
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
.join(' ');
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
<Tag class={classes} href={href} disabled={!href && disabled} {...rest}>
|
|
34
|
+
<slot />
|
|
35
|
+
</Tag>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { CardProps } from '../../shared/types/card';
|
|
3
|
+
import styles from '../../css/card.module.css';
|
|
4
|
+
|
|
5
|
+
type Props = CardProps & {
|
|
6
|
+
class?: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const { elevation = 'raised', class: className, ...rest } = Astro.props;
|
|
11
|
+
|
|
12
|
+
const classes = [
|
|
13
|
+
styles.card,
|
|
14
|
+
styles[`card--${elevation}`],
|
|
15
|
+
className ?? '',
|
|
16
|
+
]
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.join(' ');
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<div class={classes} {...rest}>
|
|
22
|
+
<slot />
|
|
23
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { TagProps } from '../../shared/types/tag';
|
|
3
|
+
import styles from '../../css/tag.module.css';
|
|
4
|
+
|
|
5
|
+
type Props = TagProps & {
|
|
6
|
+
class?: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const { variant = 'default', class: className, ...rest } = Astro.props;
|
|
11
|
+
|
|
12
|
+
const classes = [
|
|
13
|
+
styles.tag,
|
|
14
|
+
styles[`tag--${variant}`],
|
|
15
|
+
className ?? '',
|
|
16
|
+
]
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.join(' ');
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<span class={classes} {...rest}>
|
|
22
|
+
<slot />
|
|
23
|
+
</span>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
import styles from '../../css/theme-toggle.module.css';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
class?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const { class: className } = Astro.props;
|
|
9
|
+
|
|
10
|
+
const classes = [styles['theme-toggle'], className ?? ''].filter(Boolean).join(' ');
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<!--
|
|
14
|
+
The inline script runs before first paint to apply the stored theme,
|
|
15
|
+
preventing a flash of unstyled/wrong-theme content (FOUC).
|
|
16
|
+
-->
|
|
17
|
+
<script is:inline>
|
|
18
|
+
(function () {
|
|
19
|
+
var stored = localStorage.getItem('color-scheme');
|
|
20
|
+
var theme =
|
|
21
|
+
stored === 'light' || stored === 'dark'
|
|
22
|
+
? stored
|
|
23
|
+
: window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
24
|
+
? 'dark'
|
|
25
|
+
: 'light';
|
|
26
|
+
document.documentElement.setAttribute('color-scheme', theme);
|
|
27
|
+
})();
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
class={classes}
|
|
33
|
+
data-theme-toggle
|
|
34
|
+
aria-label="Toggle colour scheme"
|
|
35
|
+
>
|
|
36
|
+
<span data-theme-label>Light</span>
|
|
37
|
+
</button>
|
|
38
|
+
|
|
39
|
+
<script>
|
|
40
|
+
import { getInitialTheme, applyTheme, persistTheme } from '../../shared/js/theme';
|
|
41
|
+
|
|
42
|
+
const btn = document.querySelector('[data-theme-toggle]') as HTMLButtonElement | null;
|
|
43
|
+
const label = document.querySelector('[data-theme-label]') as HTMLElement | null;
|
|
44
|
+
|
|
45
|
+
if (btn && label) {
|
|
46
|
+
let current = getInitialTheme();
|
|
47
|
+
|
|
48
|
+
function update(theme: 'light' | 'dark') {
|
|
49
|
+
label!.textContent = theme === 'dark' ? 'Dark' : 'Light';
|
|
50
|
+
btn!.setAttribute('aria-pressed', String(theme === 'dark'));
|
|
51
|
+
btn!.classList.toggle('theme-toggle--dark', theme === 'dark');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
update(current);
|
|
55
|
+
|
|
56
|
+
btn.addEventListener('click', () => {
|
|
57
|
+
current = current === 'light' ? 'dark' : 'light';
|
|
58
|
+
applyTheme(current);
|
|
59
|
+
persistTheme(current);
|
|
60
|
+
update(current);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* =============================================================================
|
|
2
|
+
BUTTON — BEM + OOCSS
|
|
3
|
+
Structure classes: layout, sizing, spacing
|
|
4
|
+
Skin classes: colour, border, typography
|
|
5
|
+
============================================================================= */
|
|
6
|
+
|
|
7
|
+
/* --- Structure: block --- */
|
|
8
|
+
.button {
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
cursor: pointer;
|
|
13
|
+
border: none;
|
|
14
|
+
border-radius: var(--spacing-4);
|
|
15
|
+
font-family: var(--font-family-base);
|
|
16
|
+
font-weight: var(--font-weight-medium);
|
|
17
|
+
line-height: var(--font-line-height-none);
|
|
18
|
+
text-decoration: none;
|
|
19
|
+
white-space: nowrap;
|
|
20
|
+
transition: opacity 0.15s ease;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* --- Structure: size modifiers --- */
|
|
24
|
+
.button--sm,
|
|
25
|
+
.button--small {
|
|
26
|
+
padding: var(--spacing-4) var(--spacing-12);
|
|
27
|
+
font-size: var(--font-size-200);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.button--md,
|
|
31
|
+
.button--medium {
|
|
32
|
+
padding: var(--spacing-8) var(--spacing-24);
|
|
33
|
+
font-size: var(--font-size-300);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.button--lg,
|
|
37
|
+
.button--large {
|
|
38
|
+
padding: var(--spacing-12) var(--spacing-32);
|
|
39
|
+
font-size: var(--font-size-400);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* --- Skin: variant modifiers --- */
|
|
43
|
+
.button--primary {
|
|
44
|
+
background-color: var(--color-primary);
|
|
45
|
+
color: var(--color-text-inverse);
|
|
46
|
+
border: 1px solid transparent;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.button--primary:hover:not(.button--disabled) {
|
|
50
|
+
opacity: 0.88;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.button--secondary,
|
|
54
|
+
.button--outline {
|
|
55
|
+
background-color: transparent;
|
|
56
|
+
color: var(--color-primary);
|
|
57
|
+
border: 1px solid var(--color-primary);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.button--secondary:hover:not(.button--disabled),
|
|
61
|
+
.button--outline:hover:not(.button--disabled) {
|
|
62
|
+
background-color: var(--color-background-secondary);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.button--success {
|
|
66
|
+
background-color: var(--color-success);
|
|
67
|
+
color: var(--color-text-inverse);
|
|
68
|
+
border: 1px solid transparent;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.button--success:hover:not(.button--disabled) {
|
|
72
|
+
opacity: 0.88;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.button--ghost {
|
|
76
|
+
background-color: transparent;
|
|
77
|
+
color: var(--color-text-primary);
|
|
78
|
+
border: 1px solid transparent;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.button--ghost:hover:not(.button--disabled) {
|
|
82
|
+
background-color: var(--color-background-secondary);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* --- Skin: state modifier --- */
|
|
86
|
+
.button--disabled {
|
|
87
|
+
opacity: 0.4;
|
|
88
|
+
cursor: not-allowed;
|
|
89
|
+
pointer-events: none;
|
|
90
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* =============================================================================
|
|
2
|
+
CARD — BEM + OOCSS
|
|
3
|
+
Structure classes: layout, sizing, spacing
|
|
4
|
+
Skin classes: colour, shadow
|
|
5
|
+
============================================================================= */
|
|
6
|
+
|
|
7
|
+
/* --- Structure: block --- */
|
|
8
|
+
.card {
|
|
9
|
+
display: block;
|
|
10
|
+
width: 100%;
|
|
11
|
+
border-radius: var(--spacing-8);
|
|
12
|
+
padding: var(--spacing-24);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* --- Skin: base --- */
|
|
16
|
+
.card {
|
|
17
|
+
background-color: var(--color-background-secondary);
|
|
18
|
+
color: var(--color-text-primary);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* --- Skin: elevation modifiers --- */
|
|
22
|
+
|
|
23
|
+
/* TODO: Propose --shadow-raised token to the design-tokens package. */
|
|
24
|
+
.card--raised {
|
|
25
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.card--flat {
|
|
29
|
+
box-shadow: none;
|
|
30
|
+
}
|
package/src/css/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* =============================================================================
|
|
2
|
+
TAG — BEM + OOCSS
|
|
3
|
+
Structure classes: layout, sizing, spacing
|
|
4
|
+
Skin classes: colour, border, typography
|
|
5
|
+
============================================================================= */
|
|
6
|
+
|
|
7
|
+
/* --- Structure: block --- */
|
|
8
|
+
.tag {
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
border-radius: var(--spacing-4);
|
|
12
|
+
padding: var(--spacing-4) var(--spacing-8);
|
|
13
|
+
font-family: var(--font-family-base);
|
|
14
|
+
font-size: var(--font-size-100);
|
|
15
|
+
font-weight: var(--font-weight-medium);
|
|
16
|
+
line-height: var(--font-line-height-none);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* --- Skin: variant modifiers --- */
|
|
20
|
+
.tag--default {
|
|
21
|
+
background-color: var(--color-background-secondary);
|
|
22
|
+
color: var(--color-text-primary);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.tag--success {
|
|
26
|
+
background-color: var(--color-success);
|
|
27
|
+
color: var(--color-text-inverse);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.tag--danger {
|
|
31
|
+
background-color: var(--color-danger);
|
|
32
|
+
color: var(--color-text-inverse);
|
|
33
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* =============================================================================
|
|
2
|
+
THEME TOGGLE — BEM + OOCSS
|
|
3
|
+
Structure classes: layout, sizing, spacing
|
|
4
|
+
Skin classes: colour, state
|
|
5
|
+
============================================================================= */
|
|
6
|
+
|
|
7
|
+
/* --- Structure: block --- */
|
|
8
|
+
.theme-toggle {
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
gap: var(--spacing-8);
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
padding: var(--spacing-8) var(--spacing-12);
|
|
15
|
+
border: none;
|
|
16
|
+
border-radius: var(--spacing-4);
|
|
17
|
+
font-family: var(--font-family-base);
|
|
18
|
+
font-size: var(--font-size-200);
|
|
19
|
+
font-weight: var(--font-weight-medium);
|
|
20
|
+
line-height: var(--font-line-height-none);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* --- Skin: base --- */
|
|
24
|
+
.theme-toggle {
|
|
25
|
+
background-color: transparent;
|
|
26
|
+
color: var(--color-text-primary);
|
|
27
|
+
border: 1px solid var(--color-background-secondary);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.theme-toggle:hover {
|
|
31
|
+
background-color: var(--color-background-secondary);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* --- Skin: state modifier (dark mode active) --- */
|
|
35
|
+
.theme-toggle--dark {
|
|
36
|
+
color: var(--color-primary);
|
|
37
|
+
border-color: var(--color-primary);
|
|
38
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// CSS Modules
|
|
2
|
+
declare module '*.module.css' {
|
|
3
|
+
const classes: Record<string, string>;
|
|
4
|
+
export default classes;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Astro components
|
|
8
|
+
declare module '*.astro' {
|
|
9
|
+
import type { AstroComponentFactory } from 'astro/runtime/server/index.js';
|
|
10
|
+
const Component: AstroComponentFactory;
|
|
11
|
+
export default Component;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Vue SFCs
|
|
15
|
+
declare module '*.vue' {
|
|
16
|
+
import type { DefineComponent } from 'vue';
|
|
17
|
+
const Component: DefineComponent;
|
|
18
|
+
export default Component;
|
|
19
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
4
|
+
import { Button } from './index';
|
|
5
|
+
|
|
6
|
+
describe('Button', () => {
|
|
7
|
+
it('renders with default variant and size', () => {
|
|
8
|
+
render(<Button>Click me</Button>);
|
|
9
|
+
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('renders primary variant', () => {
|
|
13
|
+
render(<Button variant="primary">Primary</Button>);
|
|
14
|
+
const btn = screen.getByRole('button');
|
|
15
|
+
expect(btn.className).toMatch(/button--primary/);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('renders secondary variant', () => {
|
|
19
|
+
render(<Button variant="secondary">Secondary</Button>);
|
|
20
|
+
const btn = screen.getByRole('button');
|
|
21
|
+
expect(btn.className).toMatch(/button--secondary/);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('renders sm size', () => {
|
|
25
|
+
render(<Button size="sm">Small</Button>);
|
|
26
|
+
expect(screen.getByRole('button').className).toMatch(/button--sm/);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders md size', () => {
|
|
30
|
+
render(<Button size="md">Medium</Button>);
|
|
31
|
+
expect(screen.getByRole('button').className).toMatch(/button--md/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders lg size', () => {
|
|
35
|
+
render(<Button size="lg">Large</Button>);
|
|
36
|
+
expect(screen.getByRole('button').className).toMatch(/button--lg/);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('is disabled when disabled prop is set', () => {
|
|
40
|
+
render(<Button disabled>Disabled</Button>);
|
|
41
|
+
const btn = screen.getByRole('button');
|
|
42
|
+
expect(btn).toBeDisabled();
|
|
43
|
+
expect(btn.className).toMatch(/button--disabled/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('does not fire onClick when disabled', async () => {
|
|
47
|
+
const onClick = vi.fn();
|
|
48
|
+
render(<Button disabled onClick={onClick}>Disabled</Button>);
|
|
49
|
+
await userEvent.click(screen.getByRole('button'));
|
|
50
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('fires onClick when enabled', async () => {
|
|
54
|
+
const onClick = vi.fn();
|
|
55
|
+
render(<Button onClick={onClick}>Click</Button>);
|
|
56
|
+
await userEvent.click(screen.getByRole('button'));
|
|
57
|
+
expect(onClick).toHaveBeenCalledOnce();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes } from 'react';
|
|
2
|
+
import cx from 'classnames';
|
|
3
|
+
import type { ButtonProps } from '../../shared/types/button';
|
|
4
|
+
import styles from '../../css/button.module.css';
|
|
5
|
+
|
|
6
|
+
type Props = ButtonProps & ButtonHTMLAttributes<HTMLButtonElement>;
|
|
7
|
+
|
|
8
|
+
export function Button({
|
|
9
|
+
variant = 'primary',
|
|
10
|
+
size = 'md',
|
|
11
|
+
disabled = false,
|
|
12
|
+
children,
|
|
13
|
+
className,
|
|
14
|
+
...rest
|
|
15
|
+
}: Props) {
|
|
16
|
+
return (
|
|
17
|
+
<button
|
|
18
|
+
className={cx(
|
|
19
|
+
styles.button,
|
|
20
|
+
styles[`button--${variant}`],
|
|
21
|
+
styles[`button--${size}`],
|
|
22
|
+
disabled && styles['button--disabled'],
|
|
23
|
+
className,
|
|
24
|
+
)}
|
|
25
|
+
disabled={disabled}
|
|
26
|
+
{...rest}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</button>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { Card } from './index';
|
|
4
|
+
|
|
5
|
+
describe('Card', () => {
|
|
6
|
+
it('renders children', () => {
|
|
7
|
+
render(<Card>Card content</Card>);
|
|
8
|
+
expect(screen.getByText('Card content')).toBeInTheDocument();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('renders arbitrary children', () => {
|
|
12
|
+
render(
|
|
13
|
+
<Card>
|
|
14
|
+
<h2>Title</h2>
|
|
15
|
+
<p>Body</p>
|
|
16
|
+
</Card>
|
|
17
|
+
);
|
|
18
|
+
expect(screen.getByText('Title')).toBeInTheDocument();
|
|
19
|
+
expect(screen.getByText('Body')).toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('applies raised elevation by default', () => {
|
|
23
|
+
const { container } = render(<Card>Content</Card>);
|
|
24
|
+
expect((container.firstChild as HTMLElement).className).toMatch(/card--raised/);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('applies flat elevation when set', () => {
|
|
28
|
+
const { container } = render(<Card elevation="flat">Content</Card>);
|
|
29
|
+
const el = container.firstChild as HTMLElement;
|
|
30
|
+
expect(el.className).toMatch(/card--flat/);
|
|
31
|
+
expect(el.className).not.toMatch(/card--raised/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('applies raised elevation when explicitly set', () => {
|
|
35
|
+
const { container } = render(<Card elevation="raised">Content</Card>);
|
|
36
|
+
expect((container.firstChild as HTMLElement).className).toMatch(/card--raised/);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
import cx from 'classnames';
|
|
3
|
+
import type { CardProps } from '../../shared/types/card';
|
|
4
|
+
import styles from '../../css/card.module.css';
|
|
5
|
+
|
|
6
|
+
type Props = CardProps & HTMLAttributes<HTMLDivElement>;
|
|
7
|
+
|
|
8
|
+
export function Card({ elevation = 'raised', children, className, ...rest }: Props) {
|
|
9
|
+
return (
|
|
10
|
+
<div className={cx(styles.card, styles[`card--${elevation}`], className)} {...rest}>
|
|
11
|
+
{children}
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|