@chipmo/design-system 1.0.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/CLAUDE.md +43 -0
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/custom-elements.json +5 -0
- package/dist/tokens.css +98 -0
- package/llms.txt +23 -0
- package/package.json +59 -0
- package/src/components/chp-badge.js +42 -0
- package/src/components/chp-button.js +78 -0
- package/src/components/chp-card.js +60 -0
- package/src/components/chp-input.js +93 -0
- package/src/index.js +4 -0
- package/src/react/index.js +32 -0
- package/src/vue/index.js +11 -0
- package/tokens/design-tokens.json +98 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Chipmo design system — instructions for AI coding agents
|
|
2
|
+
|
|
3
|
+
You are working in a repo that uses `@chipmo/design-system`, the shared UI library for all Chipmo ERP products. Follow these rules in every UI change.
|
|
4
|
+
|
|
5
|
+
## Hard rules
|
|
6
|
+
|
|
7
|
+
1. **Never hardcode colors, spacing, or fonts.** Always use the CSS variables from `@chipmo/design-system/tokens.css`. If you find yourself typing a hex value like `#E68524`, stop — use `var(--chp-primary)` instead. The full token list is in `tokens/design-tokens.json`.
|
|
8
|
+
2. **Never build a button, badge, input, or card from scratch.** Use the existing components: `<chp-button>`, `<chp-badge>`, `<chp-input>`, `<chp-card>`. Check `custom-elements.json` for the full API (attributes, events, slots) of every component before using it.
|
|
9
|
+
3. **Import once, per framework:**
|
|
10
|
+
- Plain HTML / any framework: `import '@chipmo/design-system'` registers all elements.
|
|
11
|
+
- React: `import { Button, Badge, Input, Card } from '@chipmo/design-system/react'`.
|
|
12
|
+
- Vue 3: `import '@chipmo/design-system/vue'` in main.js, then use `<chp-*>` tags directly (configure `isCustomElement: tag => tag.startsWith('chp-')`).
|
|
13
|
+
- Always import the tokens once at app root: `import '@chipmo/design-system/tokens.css'`.
|
|
14
|
+
4. **Dark mode is automatic.** Tokens flip via `prefers-color-scheme`, or explicitly with `data-theme="dark"` / `data-theme="light"` on `<html>`. Never write your own dark-mode color overrides.
|
|
15
|
+
5. **Events are prefixed.** Components emit `chp-click`, `chp-input`, `chp-change` — not native `click`/`input` semantics. In React wrappers these are `onChpClick`, `onChpInput`, `onChpChange`.
|
|
16
|
+
|
|
17
|
+
## Quick reference
|
|
18
|
+
|
|
19
|
+
| Need | Use |
|
|
20
|
+
|---|---|
|
|
21
|
+
| Primary action button | `<chp-button variant="primary">` |
|
|
22
|
+
| Destructive action | `<chp-button variant="danger">` |
|
|
23
|
+
| Status pill (paid, pending, cancelled...) | `<chp-badge tone="success|warning|danger|info|neutral|brand">` |
|
|
24
|
+
| Text field with label + validation | `<chp-input label="..." error="...">` |
|
|
25
|
+
| Record/summary container | `<chp-card heading="...">` with optional `slot="actions"` |
|
|
26
|
+
| Page background | `var(--chp-bg-page)` |
|
|
27
|
+
| Card/surface background | `var(--chp-bg-surface)` |
|
|
28
|
+
| Body text color | `var(--chp-text-primary)` |
|
|
29
|
+
| Secondary/muted text | `var(--chp-text-secondary)` / `var(--chp-text-muted)` |
|
|
30
|
+
| Hairline divider | `border: 0.5px solid var(--chp-border)` |
|
|
31
|
+
| Brand orange | `var(--chp-primary)` (never raw `#E68524`) |
|
|
32
|
+
|
|
33
|
+
## Design conventions
|
|
34
|
+
|
|
35
|
+
- Font is Inter (400 body, 500 medium, 600 headings/buttons). Type scale: 26/19/15px headings, 14px body, 13px secondary, 12px captions. Never go below 12px.
|
|
36
|
+
- Border radius: 8px for controls, 10px for cards.
|
|
37
|
+
- Text on the orange primary button is navy (`--chp-text-on-primary`), not white.
|
|
38
|
+
- Status badges are the standard way to show entity state in tables and cards.
|
|
39
|
+
- All text/background pairs in the tokens are WCAG AA checked — introducing new color combinations requires a contrast check (4.5:1 body, 3:1 large/UI).
|
|
40
|
+
|
|
41
|
+
## Before adding a new component
|
|
42
|
+
|
|
43
|
+
Search `src/components/` first. If nothing fits, propose the new component in the design-system repo (github.com/chipmo/design-system) rather than building a one-off inside an ERP app — one-off components break cross-product consistency.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chipmo (chipmo.mn)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Chipmo design system
|
|
2
|
+
|
|
3
|
+
Shared tokens + components for every Chipmo ERP. One repo, one npm package, every product looks like Chipmo.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @chipmo/design-system
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Public on npm — no registry setup needed.
|
|
12
|
+
|
|
13
|
+
## Use
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
// once, at app root
|
|
17
|
+
import '@chipmo/design-system/tokens.css';
|
|
18
|
+
import '@chipmo/design-system'; // registers <chp-*> elements
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<chp-card heading="Нэхэмжлэх #INV-2041">
|
|
23
|
+
<chp-badge slot="actions" tone="success">Төлөгдсөн</chp-badge>
|
|
24
|
+
<chp-input label="Харилцагч" value="Монгол Транс ХХК"></chp-input>
|
|
25
|
+
<chp-button variant="primary">Хадгалах</chp-button>
|
|
26
|
+
</chp-card>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
React: `import { Button, Badge, Input, Card } from '@chipmo/design-system/react'`
|
|
30
|
+
Vue 3: `import '@chipmo/design-system/vue'` — see `src/vue/index.js` for Vite config.
|
|
31
|
+
|
|
32
|
+
## Try it in 30 seconds
|
|
33
|
+
|
|
34
|
+
A full working demo lives at [`examples/index.html`](examples/index.html) — buttons, badges, inputs, a card, and a light/dark toggle. It loads everything from CDN, so you can test the design system with **zero install**: save this as `demo.html` and open it in a browser.
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<!doctype html>
|
|
38
|
+
<html lang="mn">
|
|
39
|
+
<head>
|
|
40
|
+
<link rel="stylesheet" href="https://unpkg.com/@chipmo/design-system/dist/tokens.css" />
|
|
41
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
42
|
+
<script type="module">
|
|
43
|
+
import 'https://esm.sh/@chipmo/design-system';
|
|
44
|
+
</script>
|
|
45
|
+
<style>
|
|
46
|
+
body { font-family: 'Inter', sans-serif; background: var(--chp-bg-page);
|
|
47
|
+
color: var(--chp-text-primary); padding: 2rem; }
|
|
48
|
+
</style>
|
|
49
|
+
</head>
|
|
50
|
+
<body>
|
|
51
|
+
<chp-card heading="Нэхэмжлэх #INV-2041" style="max-width: 420px">
|
|
52
|
+
<chp-badge slot="actions" tone="success">Төлөгдсөн</chp-badge>
|
|
53
|
+
<chp-input label="Харилцагч" value="Монгол Транс ХХК"></chp-input>
|
|
54
|
+
<br />
|
|
55
|
+
<chp-button variant="primary">Хадгалах</chp-button>
|
|
56
|
+
<chp-button variant="outline">Цуцлах</chp-button>
|
|
57
|
+
</chp-card>
|
|
58
|
+
|
|
59
|
+
<script>
|
|
60
|
+
// components emit chp-* events
|
|
61
|
+
document.querySelector('chp-button')
|
|
62
|
+
.addEventListener('chp-click', () => alert('Хадгалагдлаа'));
|
|
63
|
+
</script>
|
|
64
|
+
</body>
|
|
65
|
+
</html>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Force dark mode by adding `data-theme="dark"` to the `<html>` tag — or leave it off and the tokens follow the visitor's system preference automatically.
|
|
69
|
+
|
|
70
|
+
The demo also deploys automatically to GitHub Pages on every merge to `main`:
|
|
71
|
+
**https://YOUR-ORG.github.io/design-system/** — the always-current showcase of the latest components and tokens.
|
|
72
|
+
|
|
73
|
+
## Dark mode
|
|
74
|
+
|
|
75
|
+
Automatic via `prefers-color-scheme`. Force it with `<html data-theme="dark">`.
|
|
76
|
+
|
|
77
|
+
## Working with AI coding tools
|
|
78
|
+
|
|
79
|
+
This repo is built to be used with AI pair programming. `CLAUDE.md` contains the rules
|
|
80
|
+
agents must follow; `custom-elements.json` (generated on build) is the machine-readable
|
|
81
|
+
component API; `llms.txt` is the index. Copy `CLAUDE.md` into consumer ERP repos or
|
|
82
|
+
reference it from theirs.
|
|
83
|
+
|
|
84
|
+
## Developing
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install
|
|
88
|
+
npm run build # generates dist/tokens.css + custom-elements.json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Edit colors only in `tokens/design-tokens.json`, then rebuild. Never edit `dist/`.
|
|
92
|
+
|
|
93
|
+
## Releasing
|
|
94
|
+
|
|
95
|
+
Merging to `main` triggers CI to build and publish to npm. Consumer ERP
|
|
96
|
+
repos using Renovate pick up the new version automatically via auto-merged PRs.
|
package/dist/tokens.css
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* Generated by scripts/build-tokens.mjs — DO NOT EDIT BY HAND. Edit tokens/design-tokens.json instead. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--chp-navy-50: #F0F4F9;
|
|
5
|
+
--chp-navy-100: #D9E3F2;
|
|
6
|
+
--chp-navy-200: #ACC1E2;
|
|
7
|
+
--chp-navy-300: #7093CD;
|
|
8
|
+
--chp-navy-400: #3965AC;
|
|
9
|
+
--chp-navy-500: #28487B;
|
|
10
|
+
--chp-navy-600: #1E365C;
|
|
11
|
+
--chp-navy-700: #172B4A;
|
|
12
|
+
--chp-navy-800: #13223B;
|
|
13
|
+
--chp-navy-900: #0A1424;
|
|
14
|
+
--chp-orange-50: #FEF5EB;
|
|
15
|
+
--chp-orange-100: #FDE7D3;
|
|
16
|
+
--chp-orange-200: #F9CEA9;
|
|
17
|
+
--chp-orange-300: #F4B67B;
|
|
18
|
+
--chp-orange-400: #EE9B4F;
|
|
19
|
+
--chp-orange-500: #E68524;
|
|
20
|
+
--chp-orange-600: #C9621D;
|
|
21
|
+
--chp-orange-700: #A74A1B;
|
|
22
|
+
--chp-orange-800: #823717;
|
|
23
|
+
--chp-orange-900: #5E2612;
|
|
24
|
+
--chp-success-400: #59C084;
|
|
25
|
+
--chp-success-500: #27864F;
|
|
26
|
+
--chp-warning-400: #F0BF4C;
|
|
27
|
+
--chp-warning-500: #E6A50F;
|
|
28
|
+
--chp-warning-700text: #916808;
|
|
29
|
+
--chp-danger-400: #E2635A;
|
|
30
|
+
--chp-danger-500: #C6342A;
|
|
31
|
+
--chp-info-400: #5FA2DD;
|
|
32
|
+
--chp-info-500: #2278C3;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
:root, [data-theme="light"] {
|
|
36
|
+
--chp-bg-page: #FFFFFF;
|
|
37
|
+
--chp-bg-surface: #FFFFFF;
|
|
38
|
+
--chp-bg-subtle: #F0F4F9;
|
|
39
|
+
--chp-text-primary: #13223B;
|
|
40
|
+
--chp-text-secondary: #28487B;
|
|
41
|
+
--chp-text-muted: #7093CD;
|
|
42
|
+
--chp-border: #D9E3F2;
|
|
43
|
+
--chp-border-strong: #ACC1E2;
|
|
44
|
+
--chp-primary: #E68524;
|
|
45
|
+
--chp-primary-hover: #C9621D;
|
|
46
|
+
--chp-text-on-primary: #13223B;
|
|
47
|
+
--chp-accent-text: #A74A1B;
|
|
48
|
+
--chp-secondary: #13223B;
|
|
49
|
+
--chp-text-on-secondary: #FFFFFF;
|
|
50
|
+
--chp-success: #27864F;
|
|
51
|
+
--chp-warning: #916808;
|
|
52
|
+
--chp-danger: #C6342A;
|
|
53
|
+
--chp-info: #2278C3;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
[data-theme="dark"] {
|
|
57
|
+
--chp-bg-page: #0A1424;
|
|
58
|
+
--chp-bg-surface: #13223B;
|
|
59
|
+
--chp-bg-subtle: #1E365C;
|
|
60
|
+
--chp-text-primary: #F0F4F9;
|
|
61
|
+
--chp-text-secondary: #ACC1E2;
|
|
62
|
+
--chp-text-muted: #7093CD;
|
|
63
|
+
--chp-border: #1E365C;
|
|
64
|
+
--chp-border-strong: #28487B;
|
|
65
|
+
--chp-primary: #EE9B4F;
|
|
66
|
+
--chp-primary-hover: #F4B67B;
|
|
67
|
+
--chp-text-on-primary: #13223B;
|
|
68
|
+
--chp-accent-text: #EE9B4F;
|
|
69
|
+
--chp-secondary: #ACC1E2;
|
|
70
|
+
--chp-text-on-secondary: #0A1424;
|
|
71
|
+
--chp-success: #59C084;
|
|
72
|
+
--chp-warning: #F0BF4C;
|
|
73
|
+
--chp-danger: #E2635A;
|
|
74
|
+
--chp-info: #5FA2DD;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@media (prefers-color-scheme: dark) {
|
|
78
|
+
:root:not([data-theme="light"]) {
|
|
79
|
+
--chp-bg-page: #0A1424;
|
|
80
|
+
--chp-bg-surface: #13223B;
|
|
81
|
+
--chp-bg-subtle: #1E365C;
|
|
82
|
+
--chp-text-primary: #F0F4F9;
|
|
83
|
+
--chp-text-secondary: #ACC1E2;
|
|
84
|
+
--chp-text-muted: #7093CD;
|
|
85
|
+
--chp-border: #1E365C;
|
|
86
|
+
--chp-border-strong: #28487B;
|
|
87
|
+
--chp-primary: #EE9B4F;
|
|
88
|
+
--chp-primary-hover: #F4B67B;
|
|
89
|
+
--chp-text-on-primary: #13223B;
|
|
90
|
+
--chp-accent-text: #EE9B4F;
|
|
91
|
+
--chp-secondary: #ACC1E2;
|
|
92
|
+
--chp-text-on-secondary: #0A1424;
|
|
93
|
+
--chp-success: #59C084;
|
|
94
|
+
--chp-warning: #F0BF4C;
|
|
95
|
+
--chp-danger: #E2635A;
|
|
96
|
+
--chp-info: #5FA2DD;
|
|
97
|
+
}
|
|
98
|
+
}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @chipmo/design-system
|
|
2
|
+
|
|
3
|
+
> Shared design tokens and web components for all Chipmo (chipmo.mn) ERP products.
|
|
4
|
+
> Framework-agnostic core (Lit web components) with React and Vue adapters.
|
|
5
|
+
> Brand colors: navy #13223B and orange #E68524, extracted from the Chipmo logo.
|
|
6
|
+
|
|
7
|
+
## Key files
|
|
8
|
+
- CLAUDE.md: rules for AI coding agents working with this system (read this first)
|
|
9
|
+
- tokens/design-tokens.json: single source of truth for all colors, light + dark mode
|
|
10
|
+
- custom-elements.json: machine-readable manifest of every component API
|
|
11
|
+
- src/components/: Lit component sources with JSDoc
|
|
12
|
+
|
|
13
|
+
## Components
|
|
14
|
+
- chp-button: variants primary|secondary|outline|ghost|danger, sizes sm|md|lg, event chp-click
|
|
15
|
+
- chp-badge: tones neutral|success|warning|danger|info|brand
|
|
16
|
+
- chp-input: label, error, placeholder, events chp-input / chp-change
|
|
17
|
+
- chp-card: heading attr, default slot + actions slot
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
- import '@chipmo/design-system' registers all custom elements
|
|
21
|
+
- import '@chipmo/design-system/tokens.css' provides all --chp-* CSS variables
|
|
22
|
+
- React: import { Button } from '@chipmo/design-system/react'
|
|
23
|
+
- Dark mode: automatic via prefers-color-scheme, or data-theme="dark" on html
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chipmo/design-system",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Chipmo design system: tokens + web components shared across all Chipmo ERP products. Works in React, Vue, and plain HTML.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/chipmo/design-system.git"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src",
|
|
13
|
+
"dist",
|
|
14
|
+
"tokens",
|
|
15
|
+
"custom-elements.json",
|
|
16
|
+
"CLAUDE.md",
|
|
17
|
+
"llms.txt"
|
|
18
|
+
],
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./src/index.js",
|
|
21
|
+
"./react": "./src/react/index.js",
|
|
22
|
+
"./vue": "./src/vue/index.js",
|
|
23
|
+
"./tokens.css": "./dist/tokens.css",
|
|
24
|
+
"./tokens.json": "./tokens/design-tokens.json"
|
|
25
|
+
},
|
|
26
|
+
"customElements": "custom-elements.json",
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "node scripts/build-tokens.mjs && npm run manifest",
|
|
29
|
+
"manifest": "custom-elements-manifest analyze --litelement --globs 'src/components/*.js'",
|
|
30
|
+
"test": "node --test"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"lit": "^3.1.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@lit/react": "^1.0.0",
|
|
37
|
+
"react": ">=17"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"@lit/react": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"react": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@custom-elements-manifest/analyzer": "^0.10.0"
|
|
49
|
+
},
|
|
50
|
+
"keywords": [
|
|
51
|
+
"design-system",
|
|
52
|
+
"web-components",
|
|
53
|
+
"lit",
|
|
54
|
+
"chipmo",
|
|
55
|
+
"erp",
|
|
56
|
+
"design-tokens"
|
|
57
|
+
],
|
|
58
|
+
"homepage": "https://github.com/chipmo/design-system#readme"
|
|
59
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Chipmo status badge (pill). The ERP workhorse for invoice/order/stock states.
|
|
5
|
+
*
|
|
6
|
+
* @tag chp-badge
|
|
7
|
+
* @attr {("neutral"|"success"|"warning"|"danger"|"info"|"brand")} tone - Semantic color. Default "neutral".
|
|
8
|
+
* @slot - Badge label.
|
|
9
|
+
*
|
|
10
|
+
* @example <chp-badge tone="success">Төлөгдсөн</chp-badge>
|
|
11
|
+
* @example <chp-badge tone="warning">Хүлээгдэж буй</chp-badge>
|
|
12
|
+
*/
|
|
13
|
+
export class ChpBadge extends LitElement {
|
|
14
|
+
static properties = { tone: { type: String, reflect: true } };
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
this.tone = 'neutral';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static styles = css`
|
|
22
|
+
:host { display: inline-block; }
|
|
23
|
+
span {
|
|
24
|
+
font-family: var(--chp-font-sans, 'Inter', sans-serif);
|
|
25
|
+
font-size: 12px; font-weight: 500;
|
|
26
|
+
padding: 4px 10px; border-radius: 99px;
|
|
27
|
+
display: inline-block; line-height: 1.3;
|
|
28
|
+
}
|
|
29
|
+
:host([tone='neutral']) span { background: var(--chp-bg-subtle); color: var(--chp-text-secondary); }
|
|
30
|
+
:host([tone='brand']) span { background: var(--chp-orange-100); color: var(--chp-orange-800); }
|
|
31
|
+
:host([tone='success']) span { background: color-mix(in srgb, var(--chp-success) 14%, var(--chp-bg-surface)); color: var(--chp-success); }
|
|
32
|
+
:host([tone='warning']) span { background: color-mix(in srgb, var(--chp-warning) 14%, var(--chp-bg-surface)); color: var(--chp-warning); }
|
|
33
|
+
:host([tone='danger']) span { background: color-mix(in srgb, var(--chp-danger) 14%, var(--chp-bg-surface)); color: var(--chp-danger); }
|
|
34
|
+
:host([tone='info']) span { background: color-mix(in srgb, var(--chp-info) 14%, var(--chp-bg-surface)); color: var(--chp-info); }
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
render() {
|
|
38
|
+
return html`<span part="badge"><slot></slot></span>`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
customElements.define('chp-badge', ChpBadge);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Chipmo button.
|
|
5
|
+
*
|
|
6
|
+
* @tag chp-button
|
|
7
|
+
* @attr {("primary"|"secondary"|"outline"|"ghost"|"danger")} variant - Visual style. Default "primary".
|
|
8
|
+
* @attr {boolean} disabled - Disables the button.
|
|
9
|
+
* @attr {("sm"|"md"|"lg")} size - Control height. Default "md".
|
|
10
|
+
* @fires chp-click - Fired on activation (click or Enter/Space). Not fired when disabled.
|
|
11
|
+
* @slot - Button label.
|
|
12
|
+
*
|
|
13
|
+
* @example <chp-button variant="primary">Хадгалах</chp-button>
|
|
14
|
+
* @example <chp-button variant="danger" size="sm">Устгах</chp-button>
|
|
15
|
+
*/
|
|
16
|
+
export class ChpButton extends LitElement {
|
|
17
|
+
static properties = {
|
|
18
|
+
variant: { type: String, reflect: true },
|
|
19
|
+
size: { type: String, reflect: true },
|
|
20
|
+
disabled: { type: Boolean, reflect: true },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
super();
|
|
25
|
+
this.variant = 'primary';
|
|
26
|
+
this.size = 'md';
|
|
27
|
+
this.disabled = false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static styles = css`
|
|
31
|
+
:host { display: inline-block; }
|
|
32
|
+
button {
|
|
33
|
+
font-family: var(--chp-font-sans, 'Inter', sans-serif);
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
border: none;
|
|
36
|
+
border-radius: 8px;
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
transition: background-color 0.15s ease;
|
|
39
|
+
line-height: 1;
|
|
40
|
+
}
|
|
41
|
+
:host([size='sm']) button { padding: 6px 12px; font-size: 13px; }
|
|
42
|
+
button, :host([size='md']) button { padding: 9px 18px; font-size: 14px; }
|
|
43
|
+
:host([size='lg']) button { padding: 12px 24px; font-size: 15px; }
|
|
44
|
+
|
|
45
|
+
:host([variant='primary']) button { background: var(--chp-primary); color: var(--chp-text-on-primary); }
|
|
46
|
+
:host([variant='primary']) button:hover:not(:disabled) { background: var(--chp-primary-hover); }
|
|
47
|
+
|
|
48
|
+
:host([variant='secondary']) button { background: var(--chp-secondary); color: var(--chp-text-on-secondary); font-weight: 500; }
|
|
49
|
+
|
|
50
|
+
:host([variant='outline']) button {
|
|
51
|
+
background: transparent; color: var(--chp-text-primary);
|
|
52
|
+
border: 1px solid var(--chp-border-strong); font-weight: 500;
|
|
53
|
+
}
|
|
54
|
+
:host([variant='outline']) button:hover:not(:disabled) { background: var(--chp-bg-subtle); }
|
|
55
|
+
|
|
56
|
+
:host([variant='ghost']) button { background: transparent; color: var(--chp-accent-text); font-weight: 500; }
|
|
57
|
+
|
|
58
|
+
:host([variant='danger']) button { background: var(--chp-danger); color: #fff; font-weight: 500; }
|
|
59
|
+
|
|
60
|
+
button:disabled { background: var(--chp-bg-subtle); color: var(--chp-text-muted); cursor: not-allowed; }
|
|
61
|
+
button:focus-visible { outline: 2px solid var(--chp-primary); outline-offset: 2px; }
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
render() {
|
|
65
|
+
return html`<button
|
|
66
|
+
?disabled=${this.disabled}
|
|
67
|
+
@click=${this._onClick}
|
|
68
|
+
part="button"
|
|
69
|
+
><slot></slot></button>`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_onClick(e) {
|
|
73
|
+
if (this.disabled) return;
|
|
74
|
+
this.dispatchEvent(new CustomEvent('chp-click', { bubbles: true, composed: true, detail: { originalEvent: e } }));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
customElements.define('chp-button', ChpButton);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Chipmo card container. Standard surface for records, summaries, and panels.
|
|
5
|
+
*
|
|
6
|
+
* @tag chp-card
|
|
7
|
+
* @attr {string} heading - Optional card title rendered in the header row.
|
|
8
|
+
* @attr {boolean} subtle - Uses the subtle background instead of the raised surface.
|
|
9
|
+
* @slot - Card body content.
|
|
10
|
+
* @slot actions - Right side of the header row (badges, buttons).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* <chp-card heading="Нэхэмжлэх #INV-2041">
|
|
14
|
+
* <chp-badge slot="actions" tone="success">Төлөгдсөн</chp-badge>
|
|
15
|
+
* ...rows...
|
|
16
|
+
* </chp-card>
|
|
17
|
+
*/
|
|
18
|
+
export class ChpCard extends LitElement {
|
|
19
|
+
static properties = {
|
|
20
|
+
heading: { type: String },
|
|
21
|
+
subtle: { type: Boolean, reflect: true },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
this.heading = '';
|
|
27
|
+
this.subtle = false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static styles = css`
|
|
31
|
+
:host {
|
|
32
|
+
display: block;
|
|
33
|
+
font-family: var(--chp-font-sans, 'Inter', sans-serif);
|
|
34
|
+
background: var(--chp-bg-surface);
|
|
35
|
+
border: 0.5px solid var(--chp-border);
|
|
36
|
+
border-radius: 10px;
|
|
37
|
+
padding: 1rem 1.25rem;
|
|
38
|
+
}
|
|
39
|
+
:host([subtle]) { background: var(--chp-bg-subtle); }
|
|
40
|
+
header {
|
|
41
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
42
|
+
margin-bottom: 10px;
|
|
43
|
+
}
|
|
44
|
+
h3 { font-size: 14px; font-weight: 600; color: var(--chp-text-primary); margin: 0; }
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
render() {
|
|
48
|
+
return html`
|
|
49
|
+
${this.heading
|
|
50
|
+
? html`<header part="header">
|
|
51
|
+
<h3>${this.heading}</h3>
|
|
52
|
+
<slot name="actions"></slot>
|
|
53
|
+
</header>`
|
|
54
|
+
: ''}
|
|
55
|
+
<slot></slot>
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
customElements.define('chp-card', ChpCard);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Chipmo text input with optional label and error message.
|
|
5
|
+
*
|
|
6
|
+
* @tag chp-input
|
|
7
|
+
* @attr {string} label - Field label rendered above the input.
|
|
8
|
+
* @attr {string} value - Current value.
|
|
9
|
+
* @attr {string} placeholder - Placeholder text.
|
|
10
|
+
* @attr {string} error - Error message. When set, the input shows the danger border and the message below.
|
|
11
|
+
* @attr {string} type - Input type (text, email, number, password...). Default "text".
|
|
12
|
+
* @attr {boolean} disabled - Disables the input.
|
|
13
|
+
* @fires chp-input - Fired on every input, detail: { value }.
|
|
14
|
+
* @fires chp-change - Fired on change (blur after edit), detail: { value }.
|
|
15
|
+
*
|
|
16
|
+
* @example <chp-input label="Нэр" placeholder="Харилцагчийн нэр"></chp-input>
|
|
17
|
+
* @example <chp-input label="Имэйл" type="email" error="Имэйл буруу байна"></chp-input>
|
|
18
|
+
*/
|
|
19
|
+
export class ChpInput extends LitElement {
|
|
20
|
+
static properties = {
|
|
21
|
+
label: { type: String },
|
|
22
|
+
value: { type: String },
|
|
23
|
+
placeholder: { type: String },
|
|
24
|
+
error: { type: String },
|
|
25
|
+
type: { type: String },
|
|
26
|
+
disabled: { type: Boolean, reflect: true },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
super();
|
|
31
|
+
this.label = '';
|
|
32
|
+
this.value = '';
|
|
33
|
+
this.placeholder = '';
|
|
34
|
+
this.error = '';
|
|
35
|
+
this.type = 'text';
|
|
36
|
+
this.disabled = false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static styles = css`
|
|
40
|
+
:host { display: block; font-family: var(--chp-font-sans, 'Inter', sans-serif); }
|
|
41
|
+
label { display: block; font-size: 13px; font-weight: 500; color: var(--chp-text-primary); margin-bottom: 6px; }
|
|
42
|
+
input {
|
|
43
|
+
width: 100%; box-sizing: border-box;
|
|
44
|
+
font-family: inherit; font-size: 14px;
|
|
45
|
+
color: var(--chp-text-primary);
|
|
46
|
+
background: var(--chp-bg-surface);
|
|
47
|
+
border: 1px solid var(--chp-border-strong);
|
|
48
|
+
border-radius: 8px; padding: 9px 12px;
|
|
49
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
50
|
+
}
|
|
51
|
+
input::placeholder { color: var(--chp-text-muted); }
|
|
52
|
+
input:focus {
|
|
53
|
+
outline: none;
|
|
54
|
+
border-color: var(--chp-primary);
|
|
55
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--chp-primary) 22%, transparent);
|
|
56
|
+
}
|
|
57
|
+
input:disabled { background: var(--chp-bg-subtle); color: var(--chp-text-muted); cursor: not-allowed; }
|
|
58
|
+
:host([data-invalid]) input { border-color: var(--chp-danger); }
|
|
59
|
+
.error { font-size: 12px; color: var(--chp-danger); margin-top: 5px; }
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
updated() {
|
|
63
|
+
this.toggleAttribute('data-invalid', Boolean(this.error));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
render() {
|
|
67
|
+
return html`
|
|
68
|
+
${this.label ? html`<label for="field">${this.label}</label>` : ''}
|
|
69
|
+
<input
|
|
70
|
+
id="field"
|
|
71
|
+
part="input"
|
|
72
|
+
.type=${this.type}
|
|
73
|
+
.value=${this.value}
|
|
74
|
+
placeholder=${this.placeholder}
|
|
75
|
+
?disabled=${this.disabled}
|
|
76
|
+
@input=${this._onInput}
|
|
77
|
+
@change=${this._onChange}
|
|
78
|
+
/>
|
|
79
|
+
${this.error ? html`<div class="error" part="error">${this.error}</div>` : ''}
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_onInput(e) {
|
|
84
|
+
this.value = e.target.value;
|
|
85
|
+
this.dispatchEvent(new CustomEvent('chp-input', { bubbles: true, composed: true, detail: { value: this.value } }));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_onChange(e) {
|
|
89
|
+
this.dispatchEvent(new CustomEvent('chp-change', { bubbles: true, composed: true, detail: { value: e.target.value } }));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
customElements.define('chp-input', ChpInput);
|
package/src/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createComponent } from '@lit/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ChpButton } from '../components/chp-button.js';
|
|
4
|
+
import { ChpBadge } from '../components/chp-badge.js';
|
|
5
|
+
import { ChpInput } from '../components/chp-input.js';
|
|
6
|
+
import { ChpCard } from '../components/chp-card.js';
|
|
7
|
+
|
|
8
|
+
export const Button = createComponent({
|
|
9
|
+
tagName: 'chp-button',
|
|
10
|
+
elementClass: ChpButton,
|
|
11
|
+
react: React,
|
|
12
|
+
events: { onChpClick: 'chp-click' },
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const Badge = createComponent({
|
|
16
|
+
tagName: 'chp-badge',
|
|
17
|
+
elementClass: ChpBadge,
|
|
18
|
+
react: React,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const Input = createComponent({
|
|
22
|
+
tagName: 'chp-input',
|
|
23
|
+
elementClass: ChpInput,
|
|
24
|
+
react: React,
|
|
25
|
+
events: { onChpInput: 'chp-input', onChpChange: 'chp-change' },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const Card = createComponent({
|
|
29
|
+
tagName: 'chp-card',
|
|
30
|
+
elementClass: ChpCard,
|
|
31
|
+
react: React,
|
|
32
|
+
});
|
package/src/vue/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Vue 3 consumes web components natively. Import this module once in main.js
|
|
2
|
+
// to register all Chipmo elements, then use <chp-button>, <chp-input>, etc.
|
|
3
|
+
// directly in templates. Configure Vite to treat chp-* as custom elements:
|
|
4
|
+
//
|
|
5
|
+
// // vite.config.js
|
|
6
|
+
// vue({ template: { compilerOptions: {
|
|
7
|
+
// isCustomElement: (tag) => tag.startsWith('chp-')
|
|
8
|
+
// }}})
|
|
9
|
+
//
|
|
10
|
+
// Listen to events with @chp-click, @chp-input, @chp-change.
|
|
11
|
+
import '../index.js';
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://chipmo.mn/design-system/tokens.schema.json",
|
|
3
|
+
"$description": "Chipmo design system color tokens. Source of truth for all ERP products. Brand colors were extracted directly from the chipmo.mn logo. Do not hardcode hex values in application code — always reference these tokens by name.",
|
|
4
|
+
"$version": "1.0.0",
|
|
5
|
+
"$lastUpdated": "2026-07-06",
|
|
6
|
+
|
|
7
|
+
"brand": {
|
|
8
|
+
"navy": { "value": "#13223B", "description": "Primary brand color, extracted from logo wordmark and ring." },
|
|
9
|
+
"orange": { "value": "#E68524", "description": "Secondary brand color, extracted from logo mark center." }
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
"ramp": {
|
|
13
|
+
"navy": {
|
|
14
|
+
"50": "#F0F4F9",
|
|
15
|
+
"100": "#D9E3F2",
|
|
16
|
+
"200": "#ACC1E2",
|
|
17
|
+
"300": "#7093CD",
|
|
18
|
+
"400": "#3965AC",
|
|
19
|
+
"500": "#28487B",
|
|
20
|
+
"600": "#1E365C",
|
|
21
|
+
"700": "#172B4A",
|
|
22
|
+
"800": "#13223B",
|
|
23
|
+
"900": "#0A1424"
|
|
24
|
+
},
|
|
25
|
+
"orange": {
|
|
26
|
+
"50": "#FEF5EB",
|
|
27
|
+
"100": "#FDE7D3",
|
|
28
|
+
"200": "#F9CEA9",
|
|
29
|
+
"300": "#F4B67B",
|
|
30
|
+
"400": "#EE9B4F",
|
|
31
|
+
"500": "#E68524",
|
|
32
|
+
"600": "#C9621D",
|
|
33
|
+
"700": "#A74A1B",
|
|
34
|
+
"800": "#823717",
|
|
35
|
+
"900": "#5E2612"
|
|
36
|
+
},
|
|
37
|
+
"success": { "500": "#27864F", "400": "#59C084" },
|
|
38
|
+
"warning": { "700text": "#916808", "500": "#E6A50F", "400": "#F0BF4C" },
|
|
39
|
+
"danger": { "500": "#C6342A", "400": "#E2635A" },
|
|
40
|
+
"info": { "500": "#2278C3", "400": "#5FA2DD" }
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
"light": {
|
|
44
|
+
"$description": "Semantic tokens for light mode. Reference these, never the raw ramp values, in components.",
|
|
45
|
+
"bg-page": "#FFFFFF",
|
|
46
|
+
"bg-surface": "#FFFFFF",
|
|
47
|
+
"bg-subtle": "{ramp.navy.50}",
|
|
48
|
+
"text-primary": "{ramp.navy.800}",
|
|
49
|
+
"text-secondary": "{ramp.navy.500}",
|
|
50
|
+
"text-muted": "{ramp.navy.300}",
|
|
51
|
+
"border": "{ramp.navy.100}",
|
|
52
|
+
"border-strong": "{ramp.navy.200}",
|
|
53
|
+
"primary": "{ramp.orange.500}",
|
|
54
|
+
"primary-hover": "{ramp.orange.600}",
|
|
55
|
+
"text-on-primary": "{ramp.navy.800}",
|
|
56
|
+
"accent-text": "{ramp.orange.700}",
|
|
57
|
+
"secondary": "{ramp.navy.800}",
|
|
58
|
+
"text-on-secondary": "#FFFFFF",
|
|
59
|
+
"success": "{ramp.success.500}",
|
|
60
|
+
"warning": "{ramp.warning.700text}",
|
|
61
|
+
"danger": "{ramp.danger.500}",
|
|
62
|
+
"info": "{ramp.info.500}"
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
"dark": {
|
|
66
|
+
"$description": "Semantic tokens for dark mode. Reference these, never the raw ramp values, in components.",
|
|
67
|
+
"bg-page": "{ramp.navy.900}",
|
|
68
|
+
"bg-surface": "{ramp.navy.800}",
|
|
69
|
+
"bg-subtle": "{ramp.navy.600}",
|
|
70
|
+
"text-primary": "{ramp.navy.50}",
|
|
71
|
+
"text-secondary": "{ramp.navy.200}",
|
|
72
|
+
"text-muted": "{ramp.navy.300}",
|
|
73
|
+
"border": "{ramp.navy.600}",
|
|
74
|
+
"border-strong": "{ramp.navy.500}",
|
|
75
|
+
"primary": "{ramp.orange.400}",
|
|
76
|
+
"primary-hover": "{ramp.orange.300}",
|
|
77
|
+
"text-on-primary": "{ramp.navy.800}",
|
|
78
|
+
"accent-text": "{ramp.orange.400}",
|
|
79
|
+
"secondary": "{ramp.navy.200}",
|
|
80
|
+
"text-on-secondary": "{ramp.navy.900}",
|
|
81
|
+
"success": "{ramp.success.400}",
|
|
82
|
+
"warning": "{ramp.warning.400}",
|
|
83
|
+
"danger": "{ramp.danger.400}",
|
|
84
|
+
"info": "{ramp.info.400}"
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
"accessibility": {
|
|
88
|
+
"$description": "Contrast ratios verified against WCAG 2.1 AA (4.5:1 body text, 3:1 large text/UI components).",
|
|
89
|
+
"checked": [
|
|
90
|
+
{ "pair": "light.text-primary on light.bg-page", "ratio": 15.94, "pass": "AA" },
|
|
91
|
+
{ "pair": "light.text-secondary on light.bg-page", "ratio": 9.12, "pass": "AA" },
|
|
92
|
+
{ "pair": "light.accent-text on light.bg-page", "ratio": 5.77, "pass": "AA" },
|
|
93
|
+
{ "pair": "light.text-on-primary on light.primary", "ratio": 5.9, "pass": "AA" },
|
|
94
|
+
{ "pair": "dark.text-primary on dark.bg-page", "ratio": 18.45, "pass": "AA" },
|
|
95
|
+
{ "pair": "dark.text-on-primary on dark.primary", "ratio": 9.44, "pass": "AA" }
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
}
|