@bookklik/senangstart-css 0.1.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.
@@ -0,0 +1,125 @@
1
+ /**
2
+ * SenangStart CSS - TypeScript Definition Generator
3
+ * Generates type definitions for React/Vue/Svelte
4
+ */
5
+
6
+ /**
7
+ * Generate TypeScript definitions
8
+ * @param {Object} config - Configuration object
9
+ * @returns {string} - TypeScript definition content
10
+ */
11
+ export function generateTypeScript(config) {
12
+ const { theme } = config;
13
+
14
+ // Generate spacing unions
15
+ const spacingKeys = Object.keys(theme.spacing);
16
+ const paddingUnions = spacingKeys.map(k => `'p:${k}'`).join(' | ');
17
+ const marginUnions = spacingKeys.map(k => `'m:${k}'`).join(' | ');
18
+ const gapUnions = spacingKeys.map(k => `'g:${k}'`).join(' | ');
19
+
20
+ // Generate radius unions
21
+ const radiusKeys = Object.keys(theme.radius);
22
+ const roundedUnions = radiusKeys.map(k => `'rounded:${k}'`).join(' | ');
23
+
24
+ // Generate shadow unions
25
+ const shadowKeys = Object.keys(theme.shadow);
26
+ const shadowUnions = shadowKeys.map(k => `'shadow:${k}'`).join(' | ');
27
+
28
+ // Generate color unions
29
+ const colorKeys = Object.keys(theme.colors);
30
+ const bgUnions = colorKeys.map(k => `'bg:${k}'`).join(' | ');
31
+ const textColorUnions = colorKeys.map(k => `'text:${k}'`).join(' | ');
32
+
33
+ // Generate font size unions
34
+ const fontSizeKeys = Object.keys(theme.fontSize);
35
+ const textSizeUnions = fontSizeKeys.map(k => `'text-size:${k}'`).join(' | ');
36
+
37
+ // Generate font weight unions
38
+ const fontWeightKeys = Object.keys(theme.fontWeight);
39
+ const fontUnions = fontWeightKeys.map(k => `'font:${k}'`).join(' | ');
40
+
41
+ return `/**
42
+ * SenangStart CSS - TypeScript Definitions
43
+ * Auto-generated from configuration
44
+ */
45
+
46
+ // Layout attribute values
47
+ type LayoutDisplay = 'flex' | 'grid' | 'block' | 'inline' | 'hidden';
48
+ type LayoutDirection = 'row' | 'col' | 'row-reverse' | 'col-reverse';
49
+ type LayoutAlignment = 'center' | 'start' | 'end' | 'between' | 'around' | 'evenly';
50
+ type LayoutWrap = 'wrap' | 'nowrap';
51
+ type LayoutPosition = 'absolute' | 'relative' | 'fixed' | 'sticky';
52
+ type LayoutZIndex = 'z:base' | 'z:low' | 'z:mid' | 'z:high' | 'z:top';
53
+
54
+ type LayoutValue =
55
+ | LayoutDisplay
56
+ | LayoutDirection
57
+ | LayoutAlignment
58
+ | LayoutWrap
59
+ | LayoutPosition
60
+ | LayoutZIndex
61
+ | \`\${LayoutDisplay} \${LayoutDirection}\`
62
+ | \`\${LayoutDisplay} \${LayoutAlignment}\`
63
+ | \`\${LayoutDisplay} \${LayoutDirection} \${LayoutAlignment}\`
64
+ | string;
65
+
66
+ // Space attribute values
67
+ type SpacingScale = ${spacingKeys.map(k => `'${k}'`).join(' | ')};
68
+ type Breakpoint = 'mob' | 'tab' | 'lap' | 'desk';
69
+
70
+ type PaddingValue = ${paddingUnions} | \`p-t:\${SpacingScale}\` | \`p-r:\${SpacingScale}\` | \`p-b:\${SpacingScale}\` | \`p-l:\${SpacingScale}\` | \`p-x:\${SpacingScale}\` | \`p-y:\${SpacingScale}\`;
71
+ type MarginValue = ${marginUnions} | \`m-t:\${SpacingScale}\` | \`m-r:\${SpacingScale}\` | \`m-b:\${SpacingScale}\` | \`m-l:\${SpacingScale}\` | \`m-x:\${SpacingScale}\` | \`m-y:\${SpacingScale}\` | 'm-x:auto';
72
+ type GapValue = ${gapUnions} | \`g-x:\${SpacingScale}\` | \`g-y:\${SpacingScale}\`;
73
+ type SizeValue = \`w:[\${string}]\` | \`h:[\${string}]\` | \`min-w:[\${string}]\` | \`max-w:[\${string}]\` | \`min-h:[\${string}]\` | \`max-h:[\${string}]\`;
74
+
75
+ type SpaceValue = PaddingValue | MarginValue | GapValue | SizeValue | \`\${Breakpoint}:\${PaddingValue}\` | string;
76
+
77
+ // Visual attribute values
78
+ type ColorKey = ${colorKeys.map(k => `'${k}'`).join(' | ')};
79
+ type RadiusKey = ${radiusKeys.map(k => `'${k}'`).join(' | ')};
80
+ type ShadowKey = ${shadowKeys.map(k => `'${k}'`).join(' | ')};
81
+ type FontSizeKey = ${fontSizeKeys.map(k => `'${k}'`).join(' | ')};
82
+ type FontWeightKey = ${fontWeightKeys.map(k => `'${k}'`).join(' | ')};
83
+
84
+ type BgValue = ${bgUnions} | \`bg:[\${string}]\`;
85
+ type TextColorValue = ${textColorUnions};
86
+ type TextSizeValue = ${textSizeUnions};
87
+ type FontValue = ${fontUnions};
88
+ type RoundedValue = ${roundedUnions};
89
+ type ShadowValue = ${shadowUnions};
90
+ type TextAlignValue = 'text:left' | 'text:center' | 'text:right';
91
+
92
+ type VisualValue = BgValue | TextColorValue | TextSizeValue | FontValue | RoundedValue | ShadowValue | TextAlignValue | string;
93
+
94
+ // React JSX attribute extensions
95
+ declare module 'react' {
96
+ interface HTMLAttributes<T> {
97
+ layout?: LayoutValue;
98
+ space?: SpaceValue;
99
+ visual?: VisualValue;
100
+ }
101
+ }
102
+
103
+ // Vue attribute extensions
104
+ declare module 'vue' {
105
+ interface HTMLAttributes {
106
+ layout?: string;
107
+ space?: string;
108
+ visual?: string;
109
+ }
110
+ }
111
+
112
+ // Svelte attribute extensions
113
+ declare namespace svelteHTML {
114
+ interface HTMLAttributes<T> {
115
+ 'layout'?: string;
116
+ 'space'?: string;
117
+ 'visual'?: string;
118
+ }
119
+ }
120
+
121
+ export {};
122
+ `;
123
+ }
124
+
125
+ export default { generateTypeScript };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * SenangStart CSS - Main Compiler Orchestrator
3
+ * Coordinates parsing, tokenizing, and generating output
4
+ */
5
+
6
+ import { parseSource, parseMultipleSources } from './parser.js';
7
+ import { tokenizeAll } from './tokenizer.js';
8
+ import { generateCSS, minifyCSS } from './generators/css.js';
9
+ import { generateAIContext } from './generators/ai-context.js';
10
+ import { generateTypeScript } from './generators/typescript.js';
11
+
12
+ /**
13
+ * Compile a single source string
14
+ * @param {string} content - Source content
15
+ * @param {Object} config - Configuration
16
+ * @returns {Object} - Compilation results
17
+ */
18
+ export function compileSource(content, config) {
19
+ const parsed = parseSource(content);
20
+ const tokens = tokenizeAll(parsed);
21
+ const css = generateCSS(tokens, config);
22
+
23
+ return {
24
+ tokens,
25
+ css,
26
+ minifiedCSS: config.output?.minify ? minifyCSS(css) : null
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Compile multiple source files
32
+ * @param {Array<{path: string, content: string}>} files - Source files
33
+ * @param {Object} config - Configuration
34
+ * @returns {Object} - Compilation results
35
+ */
36
+ export function compileMultiple(files, config) {
37
+ const parsed = parseMultipleSources(files);
38
+ const tokens = tokenizeAll(parsed);
39
+ const css = generateCSS(tokens, config);
40
+
41
+ return {
42
+ tokens,
43
+ css,
44
+ minifiedCSS: config.output?.minify ? minifyCSS(css) : null,
45
+ aiContext: generateAIContext(config),
46
+ typescript: generateTypeScript(config)
47
+ };
48
+ }
49
+
50
+ export default { compileSource, compileMultiple };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * SenangStart CSS - HTML/JSX Parser
3
+ * Extracts layout, space, and visual attributes from source files
4
+ */
5
+
6
+ // Regex patterns for attribute extraction
7
+ const ATTRIBUTE_PATTERNS = {
8
+ layout: /layout\s*=\s*["']([^"']+)["']/g,
9
+ space: /space\s*=\s*["']([^"']+)["']/g,
10
+ visual: /visual\s*=\s*["']([^"']+)["']/g
11
+ };
12
+
13
+ /**
14
+ * Parse a source file and extract all SenangStart attributes
15
+ * @param {string} content - File content to parse
16
+ * @returns {Object} - Extracted attributes by type
17
+ */
18
+ export function parseSource(content) {
19
+ const results = {
20
+ layout: new Set(),
21
+ space: new Set(),
22
+ visual: new Set()
23
+ };
24
+
25
+ for (const [attr, pattern] of Object.entries(ATTRIBUTE_PATTERNS)) {
26
+ // Reset regex lastIndex for each new content
27
+ pattern.lastIndex = 0;
28
+
29
+ let match;
30
+ while ((match = pattern.exec(content)) !== null) {
31
+ const value = match[1].trim();
32
+ // Split by whitespace to get individual tokens
33
+ value.split(/\s+/).forEach(token => {
34
+ if (token) {
35
+ results[attr].add(token);
36
+ }
37
+ });
38
+ }
39
+ }
40
+
41
+ return results;
42
+ }
43
+
44
+ /**
45
+ * Parse multiple source files
46
+ * @param {Array<{path: string, content: string}>} files - Array of file objects
47
+ * @returns {Object} - Combined extracted attributes
48
+ */
49
+ export function parseMultipleSources(files) {
50
+ const combined = {
51
+ layout: new Set(),
52
+ space: new Set(),
53
+ visual: new Set()
54
+ };
55
+
56
+ for (const file of files) {
57
+ const parsed = parseSource(file.content);
58
+
59
+ parsed.layout.forEach(token => combined.layout.add(token));
60
+ parsed.space.forEach(token => combined.space.add(token));
61
+ parsed.visual.forEach(token => combined.visual.add(token));
62
+ }
63
+
64
+ return combined;
65
+ }
66
+
67
+ export default { parseSource, parseMultipleSources };
@@ -0,0 +1,142 @@
1
+ /**
2
+ * SenangStart CSS - Tokenizer
3
+ * Parses attribute values into structured tokens
4
+ *
5
+ * Syntax: [breakpoint]:[property]:[value]
6
+ * Examples:
7
+ * - p:medium → { property: 'p', value: 'medium' }
8
+ * - tab:p:big → { breakpoint: 'tab', property: 'p', value: 'big' }
9
+ * - w:[350px] → { property: 'w', value: '350px', isArbitrary: true }
10
+ * - hover:bg:primary → { state: 'hover', property: 'bg', value: 'primary' }
11
+ */
12
+
13
+ // Breakpoint prefixes
14
+ const BREAKPOINTS = ['mob', 'tab', 'lap', 'desk'];
15
+
16
+ // State prefixes
17
+ const STATES = ['hover', 'focus', 'active', 'disabled'];
18
+
19
+ // Layout keywords (no colon syntax)
20
+ const LAYOUT_KEYWORDS = [
21
+ 'flex', 'grid', 'block', 'inline', 'hidden',
22
+ 'row', 'col', 'row-reverse', 'col-reverse',
23
+ 'center', 'start', 'end', 'between', 'around', 'evenly',
24
+ 'wrap', 'nowrap',
25
+ 'absolute', 'relative', 'fixed', 'sticky'
26
+ ];
27
+
28
+ /**
29
+ * Tokenize a single attribute value string
30
+ * @param {string} raw - Raw token string (e.g., "tab:p:big")
31
+ * @param {string} attrType - Attribute type: 'layout', 'space', or 'visual'
32
+ * @returns {Object} - Parsed token object
33
+ */
34
+ export function tokenize(raw, attrType) {
35
+ const token = {
36
+ raw,
37
+ breakpoint: null,
38
+ state: null,
39
+ property: null,
40
+ value: null,
41
+ isArbitrary: false,
42
+ attrType
43
+ };
44
+
45
+ // Handle layout keywords (simple words like 'flex', 'center')
46
+ if (attrType === 'layout') {
47
+ // Check for z-index syntax (z:top, z:base)
48
+ if (raw.startsWith('z:')) {
49
+ token.property = 'z';
50
+ token.value = raw.substring(2);
51
+ return token;
52
+ }
53
+
54
+ // Check for overflow syntax
55
+ if (raw.startsWith('overflow:')) {
56
+ token.property = 'overflow';
57
+ token.value = raw.substring(9);
58
+ return token;
59
+ }
60
+
61
+ // Simple layout keyword
62
+ if (LAYOUT_KEYWORDS.includes(raw)) {
63
+ token.property = raw;
64
+ token.value = raw;
65
+ return token;
66
+ }
67
+
68
+ // Check for responsive layout (e.g., tab:row)
69
+ const parts = raw.split(':');
70
+ if (parts.length === 2 && BREAKPOINTS.includes(parts[0])) {
71
+ token.breakpoint = parts[0];
72
+ token.property = parts[1];
73
+ token.value = parts[1];
74
+ return token;
75
+ }
76
+ }
77
+
78
+ // Handle space and visual attributes with colon syntax
79
+ const parts = raw.split(':');
80
+
81
+ if (parts.length === 1) {
82
+ // Single value (shouldn't happen for space/visual, but handle it)
83
+ token.property = raw;
84
+ token.value = raw;
85
+ return token;
86
+ }
87
+
88
+ let idx = 0;
89
+
90
+ // Check for breakpoint prefix
91
+ if (BREAKPOINTS.includes(parts[0])) {
92
+ token.breakpoint = parts[0];
93
+ idx++;
94
+ }
95
+
96
+ // Check for state prefix
97
+ if (STATES.includes(parts[idx])) {
98
+ token.state = parts[idx];
99
+ idx++;
100
+ }
101
+
102
+ // Property
103
+ if (idx < parts.length) {
104
+ token.property = parts[idx];
105
+ idx++;
106
+ }
107
+
108
+ // Value
109
+ if (idx < parts.length) {
110
+ let value = parts.slice(idx).join(':');
111
+
112
+ // Check for arbitrary value in brackets
113
+ const arbitraryMatch = value.match(/^\[(.+)\]$/);
114
+ if (arbitraryMatch) {
115
+ token.value = arbitraryMatch[1].replace(/_/g, ' ');
116
+ token.isArbitrary = true;
117
+ } else {
118
+ token.value = value;
119
+ }
120
+ }
121
+
122
+ return token;
123
+ }
124
+
125
+ /**
126
+ * Tokenize all values from parsed attributes
127
+ * @param {Object} parsed - Output from parser { layout: Set, space: Set, visual: Set }
128
+ * @returns {Array} - Array of token objects
129
+ */
130
+ export function tokenizeAll(parsed) {
131
+ const tokens = [];
132
+
133
+ for (const [attrType, values] of Object.entries(parsed)) {
134
+ for (const raw of values) {
135
+ tokens.push(tokenize(raw, attrType));
136
+ }
137
+ }
138
+
139
+ return tokens;
140
+ }
141
+
142
+ export default { tokenize, tokenizeAll };
@@ -0,0 +1,137 @@
1
+ /**
2
+ * SenangStart CSS - Default Configuration
3
+ * The "Natural Object" Scale using intuitive adjectives
4
+ */
5
+
6
+ export const defaultConfig = {
7
+ // Input files to scan for attributes
8
+ content: [
9
+ './**/*.html',
10
+ './src/**/*.{html,jsx,tsx,vue,svelte}',
11
+ './pages/**/*.{html,jsx,tsx}',
12
+ './components/**/*.{html,jsx,tsx}'
13
+ ],
14
+
15
+ // Output configuration
16
+ output: {
17
+ css: './public/senangstart.css',
18
+ minify: false,
19
+ aiContext: './.cursorrules',
20
+ typescript: './types/senang.d.ts'
21
+ },
22
+
23
+ theme: {
24
+ // 1. SPACING: The "Natural Object" Scale
25
+ // Logic: How big is the object/gap physically?
26
+ spacing: {
27
+ 'none': '0px', // No space
28
+ 'tiny': '4px', // Pebble (Borders, offsets)
29
+ 'small': '8px', // Matchbox (Grouping inside components)
30
+ 'medium': '16px', // Smartphone (Standard default)
31
+ 'big': '32px', // Laptop (Separation between groups)
32
+ 'giant': '64px', // Door (Layout sections)
33
+ 'vast': '128px' // House (Hero sections)
34
+ },
35
+
36
+ // 2. RADIUS: Tactile Feel
37
+ radius: {
38
+ 'none': '0px', // Sharp corners
39
+ 'small': '4px', // Subtle nudge
40
+ 'medium': '8px', // Soft corner
41
+ 'big': '16px', // Distinct curve
42
+ 'round': '9999px' // Pill/Circle
43
+ },
44
+
45
+ // 3. SHADOWS: Depth Perception
46
+ shadow: {
47
+ 'none': 'none',
48
+ 'small': '0 1px 2px rgba(0,0,0,0.05)',
49
+ 'medium': '0 4px 6px rgba(0,0,0,0.1)',
50
+ 'big': '0 10px 15px rgba(0,0,0,0.15)',
51
+ 'giant': '0 25px 50px rgba(0,0,0,0.25)'
52
+ },
53
+
54
+ // 4. FONT SIZES: Reading Scale
55
+ fontSize: {
56
+ 'tiny': '12px',
57
+ 'small': '14px',
58
+ 'medium': '16px',
59
+ 'big': '20px',
60
+ 'giant': '32px',
61
+ 'vast': '48px'
62
+ },
63
+
64
+ // 5. FONT WEIGHTS
65
+ fontWeight: {
66
+ 'normal': '400',
67
+ 'medium': '500',
68
+ 'bold': '700'
69
+ },
70
+
71
+ // 6. BREAKPOINTS: Device Intent
72
+ screens: {
73
+ 'mob': '480px', // Mobile
74
+ 'tab': '768px', // Tablet
75
+ 'lap': '1024px', // Laptop
76
+ 'desk': '1280px' // Desktop
77
+ },
78
+
79
+ // 7. COLORS: SenangStart Brand Palette
80
+ colors: {
81
+ 'white': '#FFFFFF',
82
+ 'black': '#000000',
83
+ 'grey': '#6B7280',
84
+ 'dark': '#3E4A5D', // Brand dark
85
+ 'light': '#DBEAFE', // Brand light/secondary
86
+ 'primary': '#2563EB', // Brand primary
87
+ 'secondary': '#DBEAFE', // Brand secondary
88
+ 'success': '#10B981',
89
+ 'warning': '#F59E0B',
90
+ 'danger': '#EF4444'
91
+ },
92
+
93
+ // 8. Z-INDEX: Stacking Order
94
+ zIndex: {
95
+ 'base': '0',
96
+ 'low': '10',
97
+ 'mid': '50',
98
+ 'high': '100',
99
+ 'top': '9999'
100
+ }
101
+ },
102
+
103
+ // Extend or override defaults
104
+ extend: {}
105
+ };
106
+
107
+ /**
108
+ * Merge user config with defaults
109
+ */
110
+ export function mergeConfig(userConfig = {}) {
111
+ const merged = { ...defaultConfig };
112
+
113
+ if (userConfig.content) {
114
+ merged.content = userConfig.content;
115
+ }
116
+
117
+ if (userConfig.output) {
118
+ merged.output = { ...merged.output, ...userConfig.output };
119
+ }
120
+
121
+ if (userConfig.theme) {
122
+ merged.theme = {
123
+ spacing: { ...merged.theme.spacing, ...userConfig.theme?.spacing },
124
+ radius: { ...merged.theme.radius, ...userConfig.theme?.radius },
125
+ shadow: { ...merged.theme.shadow, ...userConfig.theme?.shadow },
126
+ fontSize: { ...merged.theme.fontSize, ...userConfig.theme?.fontSize },
127
+ fontWeight: { ...merged.theme.fontWeight, ...userConfig.theme?.fontWeight },
128
+ screens: { ...merged.theme.screens, ...userConfig.theme?.screens },
129
+ colors: { ...merged.theme.colors, ...userConfig.theme?.colors },
130
+ zIndex: { ...merged.theme.zIndex, ...userConfig.theme?.zIndex }
131
+ };
132
+ }
133
+
134
+ return merged;
135
+ }
136
+
137
+ export default defaultConfig;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * SenangStart CSS - Console Logger with Colors
3
+ */
4
+
5
+ const colors = {
6
+ reset: '\x1b[0m',
7
+ bright: '\x1b[1m',
8
+ dim: '\x1b[2m',
9
+
10
+ // Foreground colors
11
+ red: '\x1b[31m',
12
+ green: '\x1b[32m',
13
+ yellow: '\x1b[33m',
14
+ blue: '\x1b[34m',
15
+ magenta: '\x1b[35m',
16
+ cyan: '\x1b[36m',
17
+ white: '\x1b[37m'
18
+ };
19
+
20
+ const prefix = `${colors.magenta}${colors.bright}[senang]${colors.reset}`;
21
+
22
+ export const logger = {
23
+ info: (msg) => {
24
+ console.log(`${prefix} ${colors.blue}ℹ${colors.reset} ${msg}`);
25
+ },
26
+
27
+ success: (msg) => {
28
+ console.log(`${prefix} ${colors.green}✓${colors.reset} ${msg}`);
29
+ },
30
+
31
+ warn: (msg) => {
32
+ console.log(`${prefix} ${colors.yellow}⚠${colors.reset} ${msg}`);
33
+ },
34
+
35
+ error: (msg) => {
36
+ console.log(`${prefix} ${colors.red}✗${colors.reset} ${msg}`);
37
+ },
38
+
39
+ build: (msg) => {
40
+ console.log(`${prefix} ${colors.cyan}⚡${colors.reset} ${msg}`);
41
+ },
42
+
43
+ watch: (msg) => {
44
+ console.log(`${prefix} ${colors.green}👁${colors.reset} ${msg}`);
45
+ }
46
+ };
47
+
48
+ export default logger;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * SenangStart CSS Configuration
3
+ * @see https://senangstart.dev/docs/configuration
4
+ */
5
+
6
+ export default {
7
+ // Files to scan for SenangStart attributes
8
+ content: [
9
+ './**/*.html',
10
+ './src/**/*.{html,jsx,tsx,vue,svelte}',
11
+ './components/**/*.{html,jsx,tsx}'
12
+ ],
13
+
14
+ // Output configuration
15
+ output: {
16
+ css: './public/senangstart.css',
17
+ minify: true,
18
+ aiContext: './.cursorrules',
19
+ typescript: './types/senang.d.ts'
20
+ },
21
+
22
+ // Theme customization
23
+ theme: {
24
+ // Override or extend the default spacing scale
25
+ // spacing: {
26
+ // 'huge': '256px' // Add custom scale
27
+ // },
28
+
29
+ // Add custom colors
30
+ // colors: {
31
+ // 'brand': '#8B5CF6',
32
+ // 'accent': '#EC4899'
33
+ // }
34
+ }
35
+ }