@pyreon/coolgrid 0.24.5 → 0.25.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.
@@ -1,181 +0,0 @@
1
- /** @jsxImportSource @pyreon/core */
2
- import { h } from '@pyreon/core'
3
- import { sheet } from '@pyreon/styler'
4
- import { mountInBrowser } from '@pyreon/test-utils/browser'
5
- import { PyreonUI } from '@pyreon/ui-core'
6
- import { afterEach, describe, expect, it } from 'vitest'
7
- import { Col, Container, Row } from '../index'
8
- import gridTheme from '../theme'
9
-
10
- // Real-Chromium smoke for @pyreon/coolgrid.
11
- //
12
- // Vitest browser mode runs at a small viewport (≈414px), so we assert
13
- // RATIOS against the actual rendered Row width rather than pegging
14
- // absolute pixels to a Container max-width that gets capped by the
15
- // viewport.
16
- //
17
- // Wraps in `PyreonUI` (the unified provider that replaces the
18
- // deprecated `<Provider>` from @pyreon/unistyle), matching production
19
- // usage.
20
-
21
- describe('@pyreon/coolgrid in real browser', () => {
22
- afterEach(() => {
23
- sheet.clearCache()
24
- })
25
-
26
- it('Container mounts and Chromium computes flex layout', () => {
27
- const { container, unmount } = mountInBrowser(
28
- h(PyreonUI, { theme: gridTheme }, h(Container, { id: 'c' }, 'hello')),
29
- )
30
- const el = container.querySelector<HTMLElement>('#c')!
31
- const cs = getComputedStyle(el)
32
- expect(cs.display).toBe('flex')
33
- expect(cs.flexDirection).toBe('column')
34
- expect(el.textContent).toContain('hello')
35
- unmount()
36
- })
37
-
38
- it('Col size=6 in a 12-col grid yields 50% width of its Row', () => {
39
- const { container, unmount } = mountInBrowser(
40
- h(
41
- PyreonUI,
42
- { theme: gridTheme },
43
- h(Container, null, h(Row, { id: 'row' }, h(Col, { id: 'half', size: 6 }, 'half'))),
44
- ),
45
- )
46
- const row = container.querySelector<HTMLElement>('#row')!
47
- const half = container.querySelector<HTMLElement>('#half')!
48
- const ratio = half.getBoundingClientRect().width / row.getBoundingClientRect().width
49
- expect(ratio).toBeGreaterThan(0.495)
50
- expect(ratio).toBeLessThan(0.505)
51
- unmount()
52
- })
53
-
54
- it('Col size=2 in a 6-col grid yields ~33% — grid columns aren\'t hardcoded to 12', () => {
55
- // Explicitly overrides `grid.columns` to 6 to prove the math is
56
- // `size / columns`, not `size / 12`.
57
- const theme = { ...gridTheme, grid: { ...gridTheme.grid, columns: 6 } }
58
- const { container, unmount } = mountInBrowser(
59
- h(
60
- PyreonUI,
61
- { theme },
62
- h(Container, null, h(Row, { id: 'row' }, h(Col, { id: 'third', size: 2 }, 't'))),
63
- ),
64
- )
65
- const row = container.querySelector<HTMLElement>('#row')!
66
- const third = container.querySelector<HTMLElement>('#third')!
67
- const ratio = third.getBoundingClientRect().width / row.getBoundingClientRect().width
68
- expect(ratio).toBeGreaterThan(0.33)
69
- expect(ratio).toBeLessThan(0.34)
70
- // flex-basis emitted from size/columns
71
- expect(getComputedStyle(third).flexBasis).toBe('33.3333%')
72
- unmount()
73
- })
74
-
75
- it('two Cols of size 6 each lay out side-by-side (sum ≈ 100% of Row)', () => {
76
- const { container, unmount } = mountInBrowser(
77
- h(
78
- PyreonUI,
79
- { theme: gridTheme },
80
- h(
81
- Container,
82
- null,
83
- h(
84
- Row,
85
- { id: 'row' },
86
- h(Col, { id: 'a', size: 6 }, 'A'),
87
- h(Col, { id: 'b', size: 6 }, 'B'),
88
- ),
89
- ),
90
- ),
91
- )
92
- const row = container.querySelector<HTMLElement>('#row')!
93
- const a = container.querySelector<HTMLElement>('#a')!
94
- const b = container.querySelector<HTMLElement>('#b')!
95
- const ar = a.getBoundingClientRect()
96
- const br = b.getBoundingClientRect()
97
- expect(br.left).toBeGreaterThanOrEqual(ar.right - 1)
98
- const sum = ar.width + br.width
99
- const rowW = row.getBoundingClientRect().width
100
- expect(sum / rowW).toBeGreaterThan(0.99)
101
- expect(sum / rowW).toBeLessThan(1.01)
102
- unmount()
103
- })
104
-
105
- it('flex-basis on Col is the literal percentage authored from size/columns', () => {
106
- const { container, unmount } = mountInBrowser(
107
- h(
108
- PyreonUI,
109
- { theme: gridTheme },
110
- h(Container, null, h(Row, null, h(Col, { id: 'c', size: 3 }, 'q'))),
111
- ),
112
- )
113
- const el = container.querySelector<HTMLElement>('#c')!
114
- expect(getComputedStyle(el).flexBasis).toBe('25%')
115
- unmount()
116
- })
117
-
118
- it('`gap` subtracts from Col flex-basis via calc() — different emit path', () => {
119
- // With gap set on Row, Col's widthStyles takes the
120
- // `calc(${width}% - ${g}px)` branch (see Col/styled.ts:37).
121
- // Chromium resolves the calc() and produces a width strictly less
122
- // than the gap-less 50% value, which is the load-bearing behavior.
123
- const { container, unmount } = mountInBrowser(
124
- h(
125
- PyreonUI,
126
- { theme: gridTheme },
127
- h(
128
- Container,
129
- null,
130
- h(
131
- Row,
132
- { id: 'row', gap: 24 },
133
- h(Col, { id: 'g', size: 6 }, 'G'),
134
- ),
135
- ),
136
- ),
137
- )
138
- const row = container.querySelector<HTMLElement>('#row')!
139
- const col = container.querySelector<HTMLElement>('#g')!
140
- const cs = getComputedStyle(col)
141
- // Chromium preserves the `calc(... - 24px)` literal in computed style.
142
- expect(cs.flexBasis).toMatch(/calc\([^)]+- 24px\)/)
143
- const colW = col.getBoundingClientRect().width
144
- const rowW = row.getBoundingClientRect().width
145
- // Gap subtraction: col width should be less than exactly 50%
146
- expect(colW).toBeLessThan(rowW * 0.5)
147
- // …but not dramatically so — confirms it's a subtraction, not a failure
148
- expect(colW).toBeGreaterThan(rowW * 0.4)
149
- unmount()
150
- })
151
-
152
- it('responsive `size` array applies a per-breakpoint width at the current viewport', () => {
153
- // The responsive-per-breakpoint feature: `size={[12, 6, 4]}`
154
- // means "size 12 on xs, 6 on sm, 4 on md+". At the vitest browser
155
- // viewport (~414px), xs is the only applicable breakpoint, so
156
- // size=12 should apply (100% width of Row).
157
- const { container, unmount } = mountInBrowser(
158
- h(
159
- PyreonUI,
160
- { theme: gridTheme },
161
- h(
162
- Container,
163
- null,
164
- h(
165
- Row,
166
- { id: 'row' },
167
- h(Col, { id: 'r', size: [12, 6, 4] } as any, 'x'),
168
- ),
169
- ),
170
- ),
171
- )
172
- const row = container.querySelector<HTMLElement>('#row')!
173
- const col = container.querySelector<HTMLElement>('#r')!
174
- expect(window.innerWidth).toBeLessThan(576) // below the `sm` breakpoint
175
- const ratio = col.getBoundingClientRect().width / row.getBoundingClientRect().width
176
- // xs size=12 on 12-column grid → 100% of Row
177
- expect(ratio).toBeGreaterThan(0.99)
178
- expect(ratio).toBeLessThan(1.01)
179
- unmount()
180
- })
181
- })
@@ -1,35 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- describe('index exports', () => {
4
- it('exports Container', async () => {
5
- const mod = await import('../index')
6
- expect(mod.Container).toBeDefined()
7
- expect(typeof mod.Container).toBe('function')
8
- })
9
-
10
- it('exports Row', async () => {
11
- const mod = await import('../index')
12
- expect(mod.Row).toBeDefined()
13
- expect(typeof mod.Row).toBe('function')
14
- })
15
-
16
- it('exports Col', async () => {
17
- const mod = await import('../index')
18
- expect(mod.Col).toBeDefined()
19
- expect(typeof mod.Col).toBe('function')
20
- })
21
-
22
- it('exports Provider', async () => {
23
- const mod = await import('../index')
24
- expect(mod.Provider).toBeDefined()
25
- expect(typeof mod.Provider).toBe('function')
26
- })
27
-
28
- it('exports theme', async () => {
29
- const mod = await import('../index')
30
- expect(mod.theme).toBeDefined()
31
- expect(mod.theme).toHaveProperty('rootSize')
32
- expect(mod.theme).toHaveProperty('breakpoints')
33
- expect(mod.theme).toHaveProperty('grid')
34
- })
35
- })
@@ -1,13 +0,0 @@
1
- import { isNativeCompat } from '@pyreon/core'
2
- import { describe, expect, it } from 'vitest'
3
- import Container from '../Container/component'
4
- import Row from '../Row/component'
5
-
6
- describe('native-compat markers — @pyreon/coolgrid', () => {
7
- it('Container is marked native', () => {
8
- expect(isNativeCompat(Container)).toBe(true)
9
- })
10
- it('Row is marked native', () => {
11
- expect(isNativeCompat(Row)).toBe(true)
12
- })
13
- })
@@ -1,92 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest'
2
-
3
- const mockUseContext = vi.fn()
4
-
5
- vi.mock('@pyreon/core', async (importOriginal) => {
6
- const original = await importOriginal<typeof import('@pyreon/core')>()
7
- return {
8
- ...original,
9
- useContext: mockUseContext,
10
- }
11
- })
12
-
13
- describe('useGridContext', () => {
14
- beforeEach(() => {
15
- vi.clearAllMocks()
16
- // Default: coreContext is ReactiveContext — mock returns () => value
17
- mockUseContext.mockReturnValue(() => ({ theme: {} }))
18
- })
19
-
20
- it('returns props merged with theme grid context', async () => {
21
- mockUseContext.mockReturnValue(() => ({
22
- theme: {
23
- grid: {
24
- columns: 12,
25
- container: { xs: '100%', md: 720 },
26
- },
27
- },
28
- }))
29
- const useGridContext = (await import('../useContext')).default
30
- const result = useGridContext({ gap: 16 })
31
- expect(result.gap).toBe(16)
32
- expect(result.columns).toBe(12)
33
- })
34
-
35
- it('props override theme values', async () => {
36
- mockUseContext.mockReturnValue(() => ({
37
- theme: {
38
- grid: { columns: 12 },
39
- },
40
- }))
41
- const useGridContext = (await import('../useContext')).default
42
- const result = useGridContext({ columns: 24 })
43
- expect(result.columns).toBe(24)
44
- })
45
-
46
- it('falls back to coolgrid namespace in theme', async () => {
47
- mockUseContext.mockReturnValue(() => ({
48
- theme: {
49
- coolgrid: {
50
- columns: 16,
51
- container: { xs: '100%' },
52
- },
53
- },
54
- }))
55
- const useGridContext = (await import('../useContext')).default
56
- const result = useGridContext({})
57
- expect(result.columns).toBe(16)
58
- })
59
-
60
- it('returns empty context when no theme or props', async () => {
61
- mockUseContext.mockReturnValue(() => ({ theme: {} }))
62
- const useGridContext = (await import('../useContext')).default
63
- const result = useGridContext({})
64
- expect(result).toBeDefined()
65
- })
66
- })
67
-
68
- describe('getGridContext', () => {
69
- it('resolves columns from props first', async () => {
70
- const { getGridContext } = await import('../useContext')
71
- const result = getGridContext({ columns: 24 }, { grid: { columns: 12 } })
72
- expect(result.columns).toBe(24)
73
- })
74
-
75
- it('resolves columns from theme.grid', async () => {
76
- const { getGridContext } = await import('../useContext')
77
- const result = getGridContext({}, { grid: { columns: 12 } })
78
- expect(result.columns).toBe(12)
79
- })
80
-
81
- it('resolves columns from theme.coolgrid', async () => {
82
- const { getGridContext } = await import('../useContext')
83
- const result = getGridContext({}, { coolgrid: { columns: 16 } })
84
- expect(result.columns).toBe(16)
85
- })
86
-
87
- it('resolves containerWidth from theme.grid.container', async () => {
88
- const { getGridContext } = await import('../useContext')
89
- const result = getGridContext({}, { grid: { container: { xs: '100%' } } })
90
- expect(result.containerWidth).toEqual({ xs: '100%' })
91
- })
92
- })
@@ -1,144 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { getContainerWidth } from '../Container/utils'
3
- import { hasValue, hasWidth, isNumber, isVisible, omitCtxKeys } from '../utils'
4
-
5
- describe('isNumber', () => {
6
- it('returns true for finite numbers', () => {
7
- expect(isNumber(0)).toBe(true)
8
- expect(isNumber(1)).toBe(true)
9
- expect(isNumber(-1)).toBe(true)
10
- expect(isNumber(3.14)).toBe(true)
11
- })
12
-
13
- it('returns false for non-finite values', () => {
14
- expect(isNumber(Infinity)).toBe(false)
15
- expect(isNumber(-Infinity)).toBe(false)
16
- expect(isNumber(NaN)).toBe(false)
17
- })
18
-
19
- it('returns false for non-number types', () => {
20
- expect(isNumber(null)).toBe(false)
21
- expect(isNumber(undefined)).toBe(false)
22
- expect(isNumber('5')).toBe(false)
23
- expect(isNumber(true)).toBe(false)
24
- })
25
- })
26
-
27
- describe('hasValue', () => {
28
- it('returns true for positive finite numbers', () => {
29
- expect(hasValue(1)).toBe(true)
30
- expect(hasValue(12)).toBe(true)
31
- expect(hasValue(0.5)).toBe(true)
32
- })
33
-
34
- it('returns false for zero', () => {
35
- expect(hasValue(0)).toBe(false)
36
- })
37
-
38
- it('returns false for negative numbers', () => {
39
- expect(hasValue(-1)).toBe(false)
40
- })
41
-
42
- it('returns false for non-numbers', () => {
43
- expect(hasValue(null)).toBe(false)
44
- expect(hasValue(undefined)).toBe(false)
45
- expect(hasValue('5')).toBe(false)
46
- })
47
- })
48
-
49
- describe('isVisible', () => {
50
- it('returns true for positive numbers', () => {
51
- expect(isVisible(1)).toBe(true)
52
- expect(isVisible(12)).toBe(true)
53
- })
54
-
55
- it('returns true for negative numbers', () => {
56
- expect(isVisible(-1)).toBe(true)
57
- })
58
-
59
- it('returns false for zero', () => {
60
- expect(isVisible(0)).toBe(false)
61
- })
62
-
63
- it('returns true for undefined (default visibility)', () => {
64
- expect(isVisible(undefined)).toBe(true)
65
- })
66
-
67
- it('returns false for non-number truthy values', () => {
68
- expect(isVisible('5')).toBe(false)
69
- expect(isVisible(null)).toBe(false)
70
- })
71
- })
72
-
73
- describe('hasWidth', () => {
74
- it('returns true when both size and columns are positive numbers', () => {
75
- expect(hasWidth(6, 12)).toBe(true)
76
- expect(hasWidth(1, 1)).toBe(true)
77
- })
78
-
79
- it('returns false when size is 0', () => {
80
- expect(hasWidth(0, 12)).toBe(false)
81
- })
82
-
83
- it('returns false when columns is 0', () => {
84
- expect(hasWidth(6, 0)).toBe(false)
85
- })
86
-
87
- it('returns false when either is not a number', () => {
88
- expect(hasWidth(null, 12)).toBe(false)
89
- expect(hasWidth(6, null)).toBe(false)
90
- expect(hasWidth(undefined, undefined)).toBe(false)
91
- })
92
- })
93
-
94
- describe('omitCtxKeys', () => {
95
- it('strips context keys from props', () => {
96
- const props = {
97
- columns: 12,
98
- size: 6,
99
- gap: 16,
100
- padding: 4,
101
- gutter: 8,
102
- colCss: 'color: red;',
103
- colComponent: () => null,
104
- rowCss: 'color: blue;',
105
- rowComponent: () => null,
106
- contentAlignX: 'center',
107
- className: 'my-class',
108
- id: 'my-id',
109
- }
110
- const result = omitCtxKeys(props)
111
- expect(result).toEqual({
112
- className: 'my-class',
113
- id: 'my-id',
114
- })
115
- })
116
-
117
- it('returns all props when no context keys present', () => {
118
- const props = { className: 'test', style: 'color: red;' }
119
- const result = omitCtxKeys(props)
120
- expect(result).toEqual(props)
121
- })
122
- })
123
-
124
- describe('getContainerWidth', () => {
125
- it('returns width from props', () => {
126
- const result = getContainerWidth({ width: { xs: 600 } }, {})
127
- expect(result).toEqual({ xs: 600 })
128
- })
129
-
130
- it('falls back to theme.grid.container', () => {
131
- const result = getContainerWidth({}, { grid: { container: { xs: '100%' } } })
132
- expect(result).toEqual({ xs: '100%' })
133
- })
134
-
135
- it('falls back to theme.coolgrid.container', () => {
136
- const result = getContainerWidth({}, { coolgrid: { container: { md: 720 } } })
137
- expect(result).toEqual({ md: 720 })
138
- })
139
-
140
- it('returns undefined when nothing matches', () => {
141
- const result = getContainerWidth({}, {})
142
- expect(result).toBeFalsy()
143
- })
144
- })
package/src/constants.ts DELETED
@@ -1,26 +0,0 @@
1
- export const PKG_NAME = '@pyreon/coolgrid'
2
-
3
- // Dev-mode gate. `import.meta.env.DEV` is literal-replaced by Vite at build
4
- // time and tree-shakes to zero bytes in prod. The previous
5
- // `process.env.NODE_ENV !== 'production'` form was dead code in real Vite
6
- // browser bundles (Vite does not polyfill `process`).
7
- export const __DEV__ = process.env.NODE_ENV !== 'production'
8
-
9
- /**
10
- * Grid configuration keys that are passed through context
11
- * from Container to Row and from Row to Col components.
12
- */
13
- export const CONTEXT_KEYS = [
14
- // 'breakpoints',
15
- // 'rootSize',
16
- 'columns',
17
- 'size',
18
- 'gap',
19
- 'padding',
20
- 'gutter',
21
- 'colCss',
22
- 'colComponent',
23
- 'rowCss',
24
- 'rowComponent',
25
- 'contentAlignX',
26
- ]
@@ -1,9 +0,0 @@
1
- import { createContext } from '@pyreon/core'
2
- import type { Context as ContextType } from '../types'
3
-
4
- /**
5
- * Context for container-level grid configuration.
6
- * Provided by the Container component and consumed by Row children
7
- * to inherit columns, gap, gutter, and other grid settings.
8
- */
9
- export default createContext<ContextType>({})
@@ -1,9 +0,0 @@
1
- import { createContext } from '@pyreon/core'
2
- import type { Context as ContextType } from '../types'
3
-
4
- /**
5
- * Context for row-level grid configuration.
6
- * Provided by the Row component and consumed by Col children
7
- * to inherit columns, gap, gutter, and sizing for width calculations.
8
- */
9
- export default createContext<ContextType>({})
@@ -1,4 +0,0 @@
1
- import ContainerContext from './ContainerContext'
2
- import RowContext from './RowContext'
3
-
4
- export { ContainerContext, RowContext }
package/src/env.d.ts DELETED
@@ -1,6 +0,0 @@
1
- /**
2
- * Minimal process type — just enough for `process.env.NODE_ENV` checks.
3
- * Avoids requiring @types/node in consumers that import pyreon source
4
- * via the `"bun"` export condition.
5
- */
6
- declare var process: { env: { NODE_ENV?: string } }
package/src/index.ts DELETED
@@ -1,7 +0,0 @@
1
- import { Provider } from '@pyreon/unistyle'
2
- import Col from './Col'
3
- import Container from './Container'
4
- import Row from './Row'
5
- import theme from './theme'
6
-
7
- export { Col, Container, Provider, Row, theme }
package/src/theme.ts DELETED
@@ -1,40 +0,0 @@
1
- /**
2
- * Default Bootstrap-like grid configuration. Provides 5 breakpoints (xs-xl),
3
- * a 12-column grid, and responsive container max-widths matching Bootstrap 4.
4
- */
5
- export default {
6
- rootSize: 16,
7
- breakpoints: {
8
- xs: 0,
9
- sm: 576,
10
- md: 768,
11
- lg: 992,
12
- xl: 1200,
13
- },
14
- grid: {
15
- columns: 12,
16
- container: {
17
- xs: '100%',
18
- sm: 540,
19
- md: 720,
20
- lg: 960,
21
- xl: 1140,
22
- },
23
- },
24
- } as const
25
-
26
- export const defaultBreakpoints: Record<string, number> = {
27
- xs: 0,
28
- sm: 576,
29
- md: 768,
30
- lg: 992,
31
- xl: 1200,
32
- }
33
-
34
- export const defaultContainerWidths: Record<string, string | number> = {
35
- xs: '100%',
36
- sm: 540,
37
- md: 720,
38
- lg: 960,
39
- xl: 1140,
40
- }
package/src/types.ts DELETED
@@ -1,72 +0,0 @@
1
- /**
2
- * Type definitions for the coolgrid layout system.
3
- * Supports responsive values as single values, arrays (mobile-first),
4
- * or breakpoint-keyed objects. Defines config props for Container/Row/Col
5
- * and the resolved styled-component prop types.
6
- */
7
-
8
- import type { ComponentFn, VNodeChild } from '@pyreon/core'
9
- import type { BreakpointKeys, config } from '@pyreon/ui-core'
10
- import type { AlignContentAlignXKeys, extendCss } from '@pyreon/unistyle'
11
-
12
- type CreateValueType<T> = T | T[] | Partial<Record<BreakpointKeys, T>>
13
-
14
- export type Obj = Record<string, unknown>
15
- export type Value = string | number
16
- export type Css = Parameters<typeof extendCss>[0]
17
- export type ExtraStyles = CreateValueType<Css>
18
-
19
- export type CssOutput = ReturnType<typeof config.css> | string
20
-
21
- export type ValueType = CreateValueType<number>
22
- export type ContainerWidth = CreateValueType<Value>
23
-
24
- export type ContentAlignX =
25
- | 'center'
26
- | 'left'
27
- | 'right'
28
- | 'spaceAround'
29
- | 'spaceBetween'
30
- | 'spaceEvenly'
31
-
32
- export type ConfigurationProps = Partial<{
33
- size: ValueType
34
- padding: ValueType
35
- gap: ValueType
36
- gutter: ValueType
37
- columns: ValueType
38
- colCss: ExtraStyles
39
- rowCss: ExtraStyles
40
- colComponent: ComponentFn<any>
41
- rowComponent: ComponentFn<any>
42
- contentAlignX: ContentAlignX
43
- containerWidth: ContainerWidth
44
- width: ContainerWidth | ((widths: Record<string, any>) => ContainerWidth)
45
- }>
46
-
47
- export type ComponentProps = ConfigurationProps &
48
- Partial<{
49
- component: ComponentFn<any>
50
- css: ExtraStyles
51
- }>
52
-
53
- export type StyledTypes = Partial<{
54
- size: number | undefined
55
- padding: number | undefined
56
- gap: number | undefined
57
- gutter: number | undefined
58
- columns: number | undefined
59
- extraStyles: Css
60
- contentAlignX: AlignContentAlignXKeys
61
- width: Value
62
- }>
63
-
64
- export type ElementType<O extends string[]> = ComponentFn<
65
- Omit<ComponentProps, O[number]> & Record<string, unknown> & { children?: VNodeChild }
66
- > & {
67
- displayName: string
68
- pkgName: string
69
- PYREON__COMPONENT: string
70
- }
71
-
72
- export type Context = { [K in keyof ConfigurationProps]?: ConfigurationProps[K] | undefined }