@nons-dev/uikit 0.1.7 → 0.1.8

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.
@@ -0,0 +1,76 @@
1
+ import type { NonsConfig, RawConfig } from './types'
2
+ import { validateConfig } from './validator'
3
+ import type { ValidationResult } from './validator'
4
+
5
+ export type { NonsConfig, RawConfig, ValidationResult }
6
+ export { generateFullCSS } from './parser'
7
+
8
+ let _config: NonsConfig | null = null
9
+ let _rawConfig: RawConfig | null = null
10
+
11
+ export function createConfig(overrides?: Partial<NonsConfig>): NonsConfig {
12
+ return {
13
+ theme: { preset: 'nons' },
14
+ brand: { name: 'Nons' },
15
+ direction: { default: 'rtl' },
16
+ locale: { default: 'fa', supported: ['fa', 'en'] },
17
+ font: { fa: { family: 'IRANSansX' }, en: { family: 'Satoshi' } },
18
+ ...overrides,
19
+ }
20
+ }
21
+
22
+ export function loadConfig(config: NonsConfig) {
23
+ _config = config
24
+ }
25
+
26
+ export function loadRawConfig(config: RawConfig) {
27
+ _rawConfig = config
28
+ _config = {
29
+ theme: config.theme,
30
+ brand: config.brand,
31
+ direction: config.direction,
32
+ locale: config.locale,
33
+ font: config.font,
34
+ }
35
+ }
36
+
37
+ export function getConfig(): NonsConfig {
38
+ if (!_config) {
39
+ throw new Error('Config not loaded. Call loadConfig() first.')
40
+ }
41
+ return _config
42
+ }
43
+
44
+ export function getRawConfig(): RawConfig {
45
+ if (!_rawConfig) {
46
+ throw new Error('Raw config not loaded. Call loadRawConfig() first.')
47
+ }
48
+ return _rawConfig
49
+ }
50
+
51
+ export function validateNonsConfig(config: unknown): ValidationResult {
52
+ return validateConfig(config)
53
+ }
54
+
55
+ export function getThemePreset(): string {
56
+ return getConfig().theme.preset
57
+ }
58
+
59
+ export function getDirection(): 'rtl' | 'ltr' {
60
+ return getConfig().direction.default as 'rtl' | 'ltr'
61
+ }
62
+
63
+ export function getDefaultLocale(): string {
64
+ return getConfig().locale.default
65
+ }
66
+
67
+ export function getSupportedLocales(): string[] {
68
+ return getConfig().locale.supported
69
+ }
70
+
71
+ export function getFontFamily(locale: string): string {
72
+ const fonts = getConfig().font
73
+ if (locale === 'fa' && fonts.fa?.family) return fonts.fa.family
74
+ if (fonts.en?.family) return fonts.en.family
75
+ return 'IRANSansX'
76
+ }
@@ -0,0 +1,112 @@
1
+ import type { RawConfig } from './types'
2
+
3
+ function writeTokenBlock(tokens: Record<string, string>, indent: string): string {
4
+ return Object.entries(tokens || {})
5
+ .map(([k, v]) => `${indent}${k}: ${v};`)
6
+ .join('\n')
7
+ }
8
+
9
+ function generateFontCSS(config: RawConfig): string {
10
+ const faces = config.font?.faces
11
+ if (!faces || !faces.length) return ''
12
+ return faces.map(face =>
13
+ `@font-face {
14
+ font-family: '${face.family}';
15
+ src: url('./font/${face.src.split('/').pop()}') format('${face.format}');
16
+ font-weight: ${face.weight};
17
+ font-style: normal;
18
+ font-display: swap;
19
+ }`
20
+ ).join('\n\n') + '\n\n'
21
+ }
22
+
23
+ export function generateTokensCSS(config: RawConfig): string {
24
+ const lines: string[] = []
25
+ const fonts = generateFontCSS(config)
26
+ if (fonts) lines.push(fonts)
27
+
28
+ const t = config.tokens
29
+ const hasLight = t.colors?.light && Object.keys(t.colors.light).length
30
+ const hasDark = t.colors?.dark && Object.keys(t.colors.dark).length
31
+
32
+ if (hasLight) {
33
+ lines.push(':root {')
34
+ lines.push(' color-scheme: light;')
35
+ lines.push(writeTokenBlock(t.colors.light, ' '))
36
+ lines.push('}')
37
+ lines.push('')
38
+ }
39
+
40
+ if (hasDark) {
41
+ lines.push('[data-theme="dark"] {')
42
+ lines.push(' color-scheme: dark;')
43
+ lines.push(writeTokenBlock(t.colors.dark, ' '))
44
+ lines.push('}')
45
+ lines.push('')
46
+ }
47
+
48
+ const flatSections: [string, string][] = [
49
+ ['spacing', 'Spacing'],
50
+ ['typography', 'Typography'],
51
+ ['radius', 'Border Radius'],
52
+ ['z-index', 'Z-Index'],
53
+ ['opacity', 'Opacity'],
54
+ ['button', 'Button'],
55
+ ['alert', 'Alert'],
56
+ ]
57
+ for (const [section, label] of flatSections) {
58
+ const tokens = (t as any)[section] as Record<string, string> | undefined
59
+ if (!tokens || !Object.keys(tokens).length) continue
60
+ lines.push(`/* ${label} */`)
61
+ lines.push(':root {')
62
+ lines.push(writeTokenBlock(tokens, ' '))
63
+ lines.push('}')
64
+ lines.push('')
65
+ }
66
+
67
+ const hasShadowLight = t.shadows?.light && Object.keys(t.shadows.light).length
68
+ const hasShadowDark = t.shadows?.dark && Object.keys(t.shadows.dark).length
69
+ if (hasShadowLight) {
70
+ lines.push(':root {')
71
+ lines.push(' color-scheme: light;')
72
+ lines.push(writeTokenBlock(t.shadows.light, ' '))
73
+ lines.push('}')
74
+ lines.push('')
75
+ }
76
+ if (hasShadowDark) {
77
+ lines.push('[data-theme="dark"] {')
78
+ lines.push(' color-scheme: dark;')
79
+ lines.push(writeTokenBlock(t.shadows.dark, ' '))
80
+ lines.push('}')
81
+ lines.push('')
82
+ }
83
+
84
+ return lines.join('\n')
85
+ }
86
+
87
+ export function generateThemeCSS(config: RawConfig): string {
88
+ const m = config['theme-mappings']
89
+ if (!m) return ''
90
+
91
+ const lines: string[] = []
92
+ lines.push(':root {')
93
+ lines.push(' color-scheme: light;')
94
+ if (m.light) lines.push(writeTokenBlock(m.light, ' '))
95
+ lines.push('}')
96
+ lines.push('')
97
+ lines.push('[data-theme="dark"] {')
98
+ lines.push(' color-scheme: dark;')
99
+ if (m.dark) lines.push(writeTokenBlock(m.dark, ' '))
100
+ lines.push('}')
101
+
102
+ return lines.join('\n')
103
+ }
104
+
105
+ export function generateFullCSS(config: RawConfig): string {
106
+ const parts: string[] = []
107
+ const tokens = generateTokensCSS(config)
108
+ if (tokens) parts.push(tokens)
109
+ const theme = generateThemeCSS(config)
110
+ if (theme) parts.push(theme)
111
+ return parts.join('\n')
112
+ }
@@ -0,0 +1,41 @@
1
+ export interface FontFace {
2
+ family: string
3
+ weight: number
4
+ src: string
5
+ format: string
6
+ }
7
+
8
+ export interface NonsConfig {
9
+ theme: { preset: string }
10
+ brand: { name: string }
11
+ direction: { default: string }
12
+ locale: { default: string; supported: string[] }
13
+ font: {
14
+ fa?: { family: string }
15
+ en?: { family: string }
16
+ }
17
+ }
18
+
19
+ export interface RawConfig {
20
+ theme: { preset: string }
21
+ brand: { name: string }
22
+ direction: { default: string }
23
+ locale: { default: string; supported: string[] }
24
+ font: {
25
+ fa?: { family: string }
26
+ en?: { family: string }
27
+ faces?: FontFace[]
28
+ }
29
+ tokens: {
30
+ colors: { light: Record<string, string>; dark: Record<string, string> }
31
+ spacing: Record<string, string>
32
+ typography: Record<string, string>
33
+ radius: Record<string, string>
34
+ shadows: { light: Record<string, string>; dark: Record<string, string> }
35
+ 'z-index': Record<string, string>
36
+ opacity: Record<string, string>
37
+ button: Record<string, string>
38
+ alert: Record<string, string>
39
+ }
40
+ 'theme-mappings': { light: Record<string, string>; dark: Record<string, string> }
41
+ }
@@ -0,0 +1,68 @@
1
+ export interface ValidationResult {
2
+ valid: boolean
3
+ errors: string[]
4
+ warnings: string[]
5
+ }
6
+
7
+ export function validateConfig(config: unknown): ValidationResult {
8
+ const errors: string[] = []
9
+ const warnings: string[] = []
10
+
11
+ if (!config || typeof config !== 'object') {
12
+ errors.push('Config must be a non-null object')
13
+ return { valid: false, errors, warnings }
14
+ }
15
+
16
+ const c = config as Record<string, unknown>
17
+
18
+ if (!c.theme || typeof c.theme !== 'object') {
19
+ errors.push('Missing or invalid "theme" section')
20
+ } else {
21
+ const theme = c.theme as Record<string, unknown>
22
+ if (!theme.preset || typeof theme.preset !== 'string') {
23
+ errors.push('theme.preset must be a non-empty string')
24
+ }
25
+ }
26
+
27
+ if (!c.brand || typeof c.brand !== 'object') {
28
+ errors.push('Missing or invalid "brand" section')
29
+ } else {
30
+ const brand = c.brand as Record<string, unknown>
31
+ if (!brand.name || typeof brand.name !== 'string') {
32
+ errors.push('brand.name must be a non-empty string')
33
+ }
34
+ }
35
+
36
+ if (!c.direction || typeof c.direction !== 'object') {
37
+ errors.push('Missing or invalid "direction" section')
38
+ } else {
39
+ const dir = c.direction as Record<string, unknown>
40
+ if (dir.default !== 'rtl' && dir.default !== 'ltr') {
41
+ errors.push('direction.default must be "rtl" or "ltr"')
42
+ }
43
+ }
44
+
45
+ if (!c.locale || typeof c.locale !== 'object') {
46
+ errors.push('Missing or invalid "locale" section')
47
+ } else {
48
+ const loc = c.locale as Record<string, unknown>
49
+ if (!loc.default || typeof loc.default !== 'string') {
50
+ errors.push('locale.default must be a non-empty string')
51
+ }
52
+ if (!Array.isArray(loc.supported) || (loc.supported as unknown[]).length === 0) {
53
+ errors.push('locale.supported must be a non-empty array')
54
+ } else if (typeof loc.default === 'string' && !(loc.supported as string[]).includes(loc.default)) {
55
+ warnings.push('locale.default is not in locale.supported')
56
+ }
57
+ }
58
+
59
+ if (!c.tokens || typeof c.tokens !== 'object') {
60
+ errors.push('Missing or invalid "tokens" section')
61
+ }
62
+
63
+ return {
64
+ valid: errors.length === 0,
65
+ errors,
66
+ warnings,
67
+ }
68
+ }