create-flowmo 1.2.0 → 1.2.2
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/index.js +1 -0
- package/package.json +1 -1
- package/skills/outsystems-ui/SKILL.md +114 -11
- package/skills/outsystems-ui/assets/layout-base.html +2 -1
- package/skills/outsystems-ui/assets/layout-blank.html +2 -1
- package/skills/outsystems-ui/assets/layout-side.html +2 -1
- package/skills/outsystems-ui/assets/layout-top.html +2 -1
- package/skills/outsystems-ui/references/ui-patterns.md +37 -9
- package/template/screens/home.visual.html +2 -1
- package/template/scripts/device-detect.js +40 -0
package/index.js
CHANGED
|
@@ -88,6 +88,7 @@ async function init() {
|
|
|
88
88
|
// Copy starter template files (CSS, starter screen, data, logic)
|
|
89
89
|
const templateDir = path.join(__dirname, 'template');
|
|
90
90
|
await fs.copy(path.join(templateDir, 'theme'), path.join(projectPath, 'theme'));
|
|
91
|
+
await fs.copy(path.join(templateDir, 'scripts'), path.join(projectPath, 'scripts'));
|
|
91
92
|
await fs.copy(path.join(templateDir, 'screens'), path.join(projectPath, 'screens'));
|
|
92
93
|
await fs.copy(path.join(templateDir, 'data'), path.join(projectPath, 'data'));
|
|
93
94
|
await fs.copy(path.join(templateDir, 'logic'), path.join(projectPath, 'logic'));
|
package/package.json
CHANGED
|
@@ -360,19 +360,108 @@ Some patterns expose CSS custom properties for fine-tuning. Set these on the pat
|
|
|
360
360
|
| Rating | `--rating-size: 16px` |
|
|
361
361
|
| Scrollable Area | `--scrollable-area-width`, `--scrollable-area-height` |
|
|
362
362
|
|
|
363
|
+
## Responsive & Device Classes
|
|
364
|
+
|
|
365
|
+
OutSystems uses a **JavaScript-based** responsive system, not CSS media queries. A runtime script detects the viewport size and applies device + orientation classes to `<body>`. All OSUI responsive CSS rules are scoped to these body classes.
|
|
366
|
+
|
|
367
|
+
### Body Classes
|
|
368
|
+
|
|
369
|
+
| Viewport width | Body class | Orientation class |
|
|
370
|
+
|----------------|-----------|-------------------|
|
|
371
|
+
| `< 768px` | `phone` | `portrait` (w ≤ h) or `landscape` (w > h) |
|
|
372
|
+
| `768px – 1024px` | `tablet` | `portrait` or `landscape` |
|
|
373
|
+
| `> 1024px` | `desktop` | `landscape` (typically) |
|
|
374
|
+
|
|
375
|
+
In the real OutSystems platform, this is handled by the platform runtime. For static `.visual.html` files, include the **`device-detect.js`** script at the bottom of `<body>` — it replicates the same behavior:
|
|
376
|
+
|
|
377
|
+
```html
|
|
378
|
+
<script src="../scripts/device-detect.js"></script>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
This script runs on load AND on resize, so the body class updates when the browser window changes size.
|
|
382
|
+
|
|
383
|
+
### Column Break Behavior
|
|
384
|
+
|
|
385
|
+
Columns patterns (`columns2` through `columns6`, `columns-small-left`, etc.) stay side-by-side on desktop. On tablet and phone, you control how they collapse using **break classes**:
|
|
386
|
+
|
|
387
|
+
| Break class | Effect on the target breakpoint |
|
|
388
|
+
|-------------|--------------------------------|
|
|
389
|
+
| *(none)* | Columns keep their desktop proportions — no stacking |
|
|
390
|
+
| `{device}-break-all` | **All** columns stack to 100% width (full-width rows) |
|
|
391
|
+
| `{device}-break-first` | **First** column breaks to 100% width; remaining stay side-by-side |
|
|
392
|
+
| `{device}-break-last` | **Last** column breaks to 100% width; preceding stay side-by-side |
|
|
393
|
+
| `{device}-break-middle` | Middle columns break — behavior varies by column count (see below) |
|
|
394
|
+
|
|
395
|
+
Where `{device}` is `tablet` or `phone`. You can combine both independently:
|
|
396
|
+
|
|
397
|
+
```html
|
|
398
|
+
<!-- On tablet: first column breaks. On phone: all columns stack. -->
|
|
399
|
+
<div class="columns columns3 gutter-base tablet-break-first phone-break-all">
|
|
400
|
+
<div class="columns-item">Sidebar</div>
|
|
401
|
+
<div class="columns-item">Main</div>
|
|
402
|
+
<div class="columns-item">Aside</div>
|
|
403
|
+
</div>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### `break-middle` Details
|
|
407
|
+
|
|
408
|
+
`break-middle` is a halfway collapse — it reduces the column count without going to full-width:
|
|
409
|
+
|
|
410
|
+
| Column type | `break-middle` result |
|
|
411
|
+
|-------------|----------------------|
|
|
412
|
+
| `columns2` | All items → 100% width (same as break-all) |
|
|
413
|
+
| `columns3` | Last item → 100% width; first two stay side-by-side |
|
|
414
|
+
| `columns4` | All items → 50% width (2×2 grid) |
|
|
415
|
+
| `columns5` / `columns6` | First 3 items → 33.333% width (3-col row); rest flow below |
|
|
416
|
+
| `columns-small-left`, `-medium-left`, `-small-right`, `-medium-right` | All items → 100% width |
|
|
417
|
+
|
|
418
|
+
### Gutter Classes
|
|
419
|
+
|
|
420
|
+
The gutter controls spacing between columns. It works with break classes — when columns stack, the gutter becomes vertical margin:
|
|
421
|
+
|
|
422
|
+
| Gutter | Class |
|
|
423
|
+
|--------|-------|
|
|
424
|
+
| None | `gutter-none` |
|
|
425
|
+
| XS (4px) | `gutter-xs` |
|
|
426
|
+
| S (8px) | `gutter-s` |
|
|
427
|
+
| Base (16px) | `gutter-base` |
|
|
428
|
+
| M (24px) | `gutter-m` |
|
|
429
|
+
| L (32px) | `gutter-l` |
|
|
430
|
+
| XL (40px) | `gutter-xl` |
|
|
431
|
+
| XXL (48px) | `gutter-xxl` |
|
|
432
|
+
|
|
433
|
+
### Display On Device
|
|
434
|
+
|
|
435
|
+
Show/hide entire elements per breakpoint:
|
|
436
|
+
|
|
437
|
+
```html
|
|
438
|
+
<div class="display-on-device-desktop">Desktop only content</div>
|
|
439
|
+
<div class="display-on-device-tablet">Tablet only content</div>
|
|
440
|
+
<div class="display-on-device-phone">Phone only content</div>
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
These require the correct body class to work — so `device-detect.js` is required for static previews.
|
|
444
|
+
|
|
363
445
|
## Theme Customization
|
|
364
446
|
|
|
365
|
-
Every project ships three CSS files, loaded in this order:
|
|
447
|
+
Every project ships three CSS files and one JS file, loaded in this order:
|
|
366
448
|
|
|
367
449
|
1. **`outsystems-ui.css`** — the OSUI framework (patterns, utilities, layout). Never edit this.
|
|
368
450
|
2. **`grid.css`** — platform grid definitions (`.ThemeGrid_Container` max-width, `.ThemeGrid_Width*` columns, `.ThemeGrid_MarginGutter` spacing, `.active-screen` scroll model). Normally injected by OutSystems at runtime — we provide it statically. Never edit this.
|
|
369
451
|
3. **`theme.css`** — project-specific brand tokens and custom styles. This is the only file you should edit.
|
|
452
|
+
4. **`device-detect.js`** (in `scripts/`) — viewport detection script that applies `phone`/`tablet`/`desktop` + `portrait`/`landscape` classes to `<body>`. Required for responsive column breaks and `display-on-device-*` classes. Never edit this.
|
|
370
453
|
|
|
371
|
-
All three `<link>` tags must be present in `<head>`:
|
|
454
|
+
All three `<link>` tags must be present in `<head>`, and the script at the bottom of `<body>`:
|
|
372
455
|
```html
|
|
373
|
-
<
|
|
374
|
-
<link rel="stylesheet" href="../theme/
|
|
375
|
-
<link rel="stylesheet" href="../theme/
|
|
456
|
+
<head>
|
|
457
|
+
<link rel="stylesheet" href="../theme/outsystems-ui.css" />
|
|
458
|
+
<link rel="stylesheet" href="../theme/grid.css" />
|
|
459
|
+
<link rel="stylesheet" href="../theme/theme.css" />
|
|
460
|
+
</head>
|
|
461
|
+
<body>
|
|
462
|
+
<!-- ... page content ... -->
|
|
463
|
+
<script src="../scripts/device-detect.js"></script>
|
|
464
|
+
</body>
|
|
376
465
|
```
|
|
377
466
|
|
|
378
467
|
### What theme.css MUST contain
|
|
@@ -442,15 +531,27 @@ Follow this checklist when creating or editing a screen. Do not skip steps.
|
|
|
442
531
|
2. No inline `style=""` attributes anywhere
|
|
443
532
|
3. All classes are OSUI utilities OR custom classes defined in `theme.css`
|
|
444
533
|
4. All three CSS stylesheets linked in `<head>`: `outsystems-ui.css`, `grid.css`, `theme.css` (in that order)
|
|
445
|
-
5.
|
|
446
|
-
6.
|
|
447
|
-
7.
|
|
448
|
-
8.
|
|
534
|
+
5. `device-detect.js` script tag present at end of `<body>`
|
|
535
|
+
6. If Font Awesome icons are used, the FA CDN `<link>` is in `<head>`
|
|
536
|
+
7. No `@media` queries or custom breakpoints
|
|
537
|
+
8. Scroll model and grid are handled by `grid.css` (not duplicated in `theme.css`)
|
|
538
|
+
9. **Text readability** — for every text element, confirm the text color contrasts with its background:
|
|
539
|
+
- Dark backgrounds (`.background-primary`, `.background-neutral-10`, navy/dark sections) → use light text (`.text-neutral-0` or white custom class)
|
|
540
|
+
- Light backgrounds (`.background-neutral-0`, white sections) → use dark text (`.text-neutral-10` or default)
|
|
541
|
+
- **Header and menu links are especially prone** — if the header has a dark background, menu links MUST use a light text class. OSUI link defaults are the primary color, which may be invisible on a dark header.
|
|
542
|
+
- Buttons: check that button text contrasts with the button background (`.btn` defaults to white text — don't add `.text-neutral-0` on a white button)
|
|
543
|
+
10. **Responsive columns** — for every `.columns*` pattern, verify the break behavior:
|
|
544
|
+
- Every multi-column layout SHOULD have a `phone-break-*` class. Without one, columns stay side-by-side on phone, which is almost always wrong.
|
|
545
|
+
- Choose the right break: `phone-break-all` (stack everything) is the safe default. Use `phone-break-first`/`-last` when one column should stay full-width while others share a row.
|
|
546
|
+
- For tablet, add `tablet-break-*` only if the column count is 4+ or if side-by-side is too cramped at 768px.
|
|
547
|
+
- `break-middle` is the right choice for `columns4`–`columns6` on tablet — it goes to a 2- or 3-column grid instead of full-width stacking.
|
|
548
|
+
- Verify gutter class is present (`gutter-base` is the safe default).
|
|
549
|
+
11. If any check fails, fix and re-check before continuing
|
|
449
550
|
- [ ] Step 3: **Install dependencies** — if `node_modules/` does not exist, run `npm install`
|
|
450
551
|
- [ ] Step 4: **Start the dev server** — run `npm run dev` to serve the project at `http://localhost:5173/`
|
|
451
552
|
- [ ] Step 5: **Visual verification** — open the screen URL in the browser and check:
|
|
452
553
|
- Layout renders correctly (no collapsed sections, no overlapping elements)
|
|
453
|
-
-
|
|
554
|
+
- **Text readability** — read every section and confirm ALL text is visible against its background. Pay special attention to: header/nav menu links, buttons, text in dark sections, links in footers
|
|
454
555
|
- Page scrolls within `.active-screen`
|
|
455
556
|
- Icons render (not 0×0 invisible boxes)
|
|
456
557
|
- Menu links and form fields have proper spacing (`.ThemeGrid_MarginGutter`)
|
|
@@ -460,11 +561,13 @@ Follow this checklist when creating or editing a screen. Do not skip steps.
|
|
|
460
561
|
|
|
461
562
|
- `.active-screen` is **REQUIRED** as the outermost wrapper. Without it, utility classes and CSS variables will NOT resolve. It also serves as the **scroll container** \u2014 OSUI sets `html { overflow: hidden }` so `.active-screen` must have `overflow-y: auto; height: 100vh` (provided by `grid.css`).\n- **`.ThemeGrid_MarginGutter` needs `grid.css`** \u2014 this class is used for menu link spacing and form field gutters, but its `margin-left` value is NOT in `outsystems-ui.css`. It comes from `grid.css`. Without it, menu links will bunch together with no spacing.
|
|
462
563
|
- **Button + color utilities don't mix** — `.btn` overrides `color` and `background-color` at high specificity. Stacking `.btn .background-neutral-0 .text-primary` will produce invisible text (white on white). Create custom button classes in `theme.css` instead.
|
|
564
|
+
- **Header menu links vanish on dark headers** — OSUI link color defaults to `var(--color-primary)`. On a dark header where `--color-primary` is also dark (e.g. navy), menu links become invisible. Fix: add a custom class in `theme.css` that forces light link color in the header (e.g. `.header__link { color: #fff; }`).
|
|
463
565
|
- **`.list-item` has white background** — In dark-background sections (footer, sidebar), list items will show as white rectangles. Override with `background: transparent !important` in `theme.css`.
|
|
464
566
|
- **Font Awesome is not bundled** — If using `<i class="fa fa-exchange">` style icons, add the FA 4.7 CDN link to `<head>` or the icons will be invisible (0×0 size).
|
|
465
567
|
- OutSystems pattern previews render inside iframes — CSS resolves against the OSUI framework, not browser defaults. Your `.visual.html` must link the OSUI stylesheet.
|
|
466
568
|
- **Never mix** inline `style=""` attributes with utility classes. Always prefer utility classes.
|
|
467
|
-
- `.columns*` patterns auto-stack on mobile breakpoints. Do NOT add custom `@media` queries
|
|
569
|
+
- `.columns*` patterns auto-stack on mobile breakpoints **only if a break class is applied** (e.g. `phone-break-all`). Without a break class, columns stay side-by-side on all viewports. Do NOT add custom `@media` queries — use the break classes instead.
|
|
570
|
+
- **`device-detect.js` is required** — responsive column breaks and `display-on-device-*` classes are scoped to body classes (`phone`, `tablet`, `desktop`). Without the script, they have no effect in static previews.
|
|
468
571
|
- Button loading state is purely CSS (`.btn-loading` + spinner element), not a JavaScript toggle.
|
|
469
572
|
- Use `{{content}}` placeholders in component templates to indicate where nested child widgets go.
|
|
470
573
|
- Always generate BOTH the `.visual.html` AND corresponding `theme.css` styles together. A screen without proper theme styles will look broken.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="stylesheet" href="../theme/grid.css" />
|
|
9
9
|
<link rel="stylesheet" href="../theme/theme.css" />
|
|
10
10
|
</head>
|
|
11
|
-
<body
|
|
11
|
+
<body>
|
|
12
12
|
<!--
|
|
13
13
|
LayoutBase — Full-width sections, content-first.
|
|
14
14
|
Best for: landing pages, marketing pages, public-facing screens.
|
|
@@ -105,5 +105,6 @@
|
|
|
105
105
|
</div>
|
|
106
106
|
</div>
|
|
107
107
|
</div>
|
|
108
|
+
<script src="../scripts/device-detect.js"></script>
|
|
108
109
|
</body>
|
|
109
110
|
</html>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="stylesheet" href="../theme/grid.css" />
|
|
9
9
|
<link rel="stylesheet" href="../theme/theme.css" />
|
|
10
10
|
</head>
|
|
11
|
-
<body
|
|
11
|
+
<body>
|
|
12
12
|
<!--
|
|
13
13
|
LayoutBlank — No header, no footer, no navigation.
|
|
14
14
|
Best for: login pages, error pages, splash screens, or any screen
|
|
@@ -27,5 +27,6 @@
|
|
|
27
27
|
|
|
28
28
|
</div>
|
|
29
29
|
</div>
|
|
30
|
+
<script src="../scripts/device-detect.js"></script>
|
|
30
31
|
</body>
|
|
31
32
|
</html>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="stylesheet" href="../theme/grid.css" />
|
|
9
9
|
<link rel="stylesheet" href="../theme/theme.css" />
|
|
10
10
|
</head>
|
|
11
|
-
<body
|
|
11
|
+
<body>
|
|
12
12
|
<!--
|
|
13
13
|
LayoutSideMenu — Vertical navigation in a left sidebar.
|
|
14
14
|
Best for: backoffice/admin apps with many pages or deep navigation.
|
|
@@ -114,5 +114,6 @@
|
|
|
114
114
|
</div>
|
|
115
115
|
</div>
|
|
116
116
|
</div>
|
|
117
|
+
<script src="../scripts/device-detect.js"></script>
|
|
117
118
|
</body>
|
|
118
119
|
</html>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="stylesheet" href="../theme/grid.css" />
|
|
9
9
|
<link rel="stylesheet" href="../theme/theme.css" />
|
|
10
10
|
</head>
|
|
11
|
-
<body
|
|
11
|
+
<body>
|
|
12
12
|
<!--
|
|
13
13
|
LayoutTopMenu — Horizontal navigation bar at the top.
|
|
14
14
|
Best for: web apps with few top-level pages (3–6 menu items).
|
|
@@ -97,5 +97,6 @@
|
|
|
97
97
|
</div>
|
|
98
98
|
</div>
|
|
99
99
|
</div>
|
|
100
|
+
<script src="../scripts/device-detect.js"></script>
|
|
100
101
|
</body>
|
|
101
102
|
</html>
|
|
@@ -11,7 +11,7 @@ Notation: `tag.class1.class2` for elements, `[Block.Name]` for OutSystems block
|
|
|
11
11
|
### Columns 2
|
|
12
12
|
|
|
13
13
|
```html
|
|
14
|
-
<div class="columns columns2 gutter-base">
|
|
14
|
+
<div class="columns columns2 gutter-base phone-break-all">
|
|
15
15
|
<div class="columns-item">Column 1</div>
|
|
16
16
|
<div class="columns-item">Column 2</div>
|
|
17
17
|
</div>
|
|
@@ -20,7 +20,7 @@ Notation: `tag.class1.class2` for elements, `[Block.Name]` for OutSystems block
|
|
|
20
20
|
### Columns 3
|
|
21
21
|
|
|
22
22
|
```html
|
|
23
|
-
<div class="columns columns3 gutter-base">
|
|
23
|
+
<div class="columns columns3 gutter-base phone-break-all">
|
|
24
24
|
<div class="columns-item">Column 1</div>
|
|
25
25
|
<div class="columns-item">Column 2</div>
|
|
26
26
|
<div class="columns-item">Column 3</div>
|
|
@@ -30,7 +30,7 @@ Notation: `tag.class1.class2` for elements, `[Block.Name]` for OutSystems block
|
|
|
30
30
|
### Columns 4
|
|
31
31
|
|
|
32
32
|
```html
|
|
33
|
-
<div class="columns columns4 gutter-base">
|
|
33
|
+
<div class="columns columns4 gutter-base tablet-break-middle phone-break-all">
|
|
34
34
|
<div class="columns-item">Column 1</div>
|
|
35
35
|
<div class="columns-item">Column 2</div>
|
|
36
36
|
<div class="columns-item">Column 3</div>
|
|
@@ -41,7 +41,7 @@ Notation: `tag.class1.class2` for elements, `[Block.Name]` for OutSystems block
|
|
|
41
41
|
### Columns 5
|
|
42
42
|
|
|
43
43
|
```html
|
|
44
|
-
<div class="columns columns5 gutter-base">
|
|
44
|
+
<div class="columns columns5 gutter-base tablet-break-middle phone-break-all">
|
|
45
45
|
<div class="columns-item">...</div>
|
|
46
46
|
<!-- 5 columns-item -->
|
|
47
47
|
</div>
|
|
@@ -50,7 +50,7 @@ Notation: `tag.class1.class2` for elements, `[Block.Name]` for OutSystems block
|
|
|
50
50
|
### Columns 6
|
|
51
51
|
|
|
52
52
|
```html
|
|
53
|
-
<div class="columns columns6 gutter-base">
|
|
53
|
+
<div class="columns columns6 gutter-base tablet-break-middle phone-break-all">
|
|
54
54
|
<div class="columns-item">...</div>
|
|
55
55
|
<!-- 6 columns-item -->
|
|
56
56
|
</div>
|
|
@@ -59,7 +59,7 @@ Notation: `tag.class1.class2` for elements, `[Block.Name]` for OutSystems block
|
|
|
59
59
|
### Columns Medium Left
|
|
60
60
|
|
|
61
61
|
```html
|
|
62
|
-
<div class="columns columns-medium-left gutter-base">
|
|
62
|
+
<div class="columns columns-medium-left gutter-base phone-break-all">
|
|
63
63
|
<div class="columns-item">Wider left</div>
|
|
64
64
|
<div class="columns-item">Narrower right</div>
|
|
65
65
|
</div>
|
|
@@ -68,7 +68,7 @@ Notation: `tag.class1.class2` for elements, `[Block.Name]` for OutSystems block
|
|
|
68
68
|
### Columns Medium Right
|
|
69
69
|
|
|
70
70
|
```html
|
|
71
|
-
<div class="columns columns-medium-right gutter-base">
|
|
71
|
+
<div class="columns columns-medium-right gutter-base phone-break-all">
|
|
72
72
|
<div class="columns-item">Narrower left</div>
|
|
73
73
|
<div class="columns-item">Wider right</div>
|
|
74
74
|
</div>
|
|
@@ -77,7 +77,7 @@ Notation: `tag.class1.class2` for elements, `[Block.Name]` for OutSystems block
|
|
|
77
77
|
### Columns Small Left
|
|
78
78
|
|
|
79
79
|
```html
|
|
80
|
-
<div class="columns columns-small-left gutter-base">
|
|
80
|
+
<div class="columns columns-small-left gutter-base phone-break-all">
|
|
81
81
|
<div class="columns-item">Small left</div>
|
|
82
82
|
<div class="columns-item">Large right</div>
|
|
83
83
|
</div>
|
|
@@ -86,12 +86,40 @@ Notation: `tag.class1.class2` for elements, `[Block.Name]` for OutSystems block
|
|
|
86
86
|
### Columns Small Right
|
|
87
87
|
|
|
88
88
|
```html
|
|
89
|
-
<div class="columns columns-small-right gutter-base">
|
|
89
|
+
<div class="columns columns-small-right gutter-base phone-break-all">
|
|
90
90
|
<div class="columns-item">Large left</div>
|
|
91
91
|
<div class="columns-item">Small right</div>
|
|
92
92
|
</div>
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
### Column Break Variants
|
|
96
|
+
|
|
97
|
+
Combine `tablet-break-*` and `phone-break-*` independently:
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<!-- First column breaks on phone, all stay on tablet -->
|
|
101
|
+
<div class="columns columns3 gutter-base phone-break-first">
|
|
102
|
+
<div class="columns-item">Sidebar (full-width on phone)</div>
|
|
103
|
+
<div class="columns-item">Main</div>
|
|
104
|
+
<div class="columns-item">Aside</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- 4-column grid: 2×2 on tablet, full-stack on phone -->
|
|
108
|
+
<div class="columns columns4 gutter-base tablet-break-middle phone-break-all">
|
|
109
|
+
<div class="columns-item">Card 1</div>
|
|
110
|
+
<div class="columns-item">Card 2</div>
|
|
111
|
+
<div class="columns-item">Card 3</div>
|
|
112
|
+
<div class="columns-item">Card 4</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<!-- Last column breaks on both tablet and phone -->
|
|
116
|
+
<div class="columns columns3 gutter-m tablet-break-last phone-break-all">
|
|
117
|
+
<div class="columns-item">Main</div>
|
|
118
|
+
<div class="columns-item">Secondary</div>
|
|
119
|
+
<div class="columns-item">Full-width footer row</div>
|
|
120
|
+
</div>
|
|
121
|
+
```
|
|
122
|
+
|
|
95
123
|
### Display On Device
|
|
96
124
|
|
|
97
125
|
Show/hide content per breakpoint.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="stylesheet" href="../theme/grid.css">
|
|
9
9
|
<link rel="stylesheet" href="../theme/theme.css">
|
|
10
10
|
</head>
|
|
11
|
-
<body
|
|
11
|
+
<body>
|
|
12
12
|
<div class="active-screen">
|
|
13
13
|
<div data-block="Layouts.LayoutTopMenu" class="layout layout-top fixed-header">
|
|
14
14
|
<div class="main">
|
|
@@ -68,5 +68,6 @@
|
|
|
68
68
|
</div>
|
|
69
69
|
</div>
|
|
70
70
|
</div>
|
|
71
|
+
<script src="../scripts/device-detect.js"></script>
|
|
71
72
|
</body>
|
|
72
73
|
</html>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/* ── OutSystems Device Class Detection ────────────────
|
|
2
|
+
In the real OutSystems platform, a runtime script detects
|
|
3
|
+
the viewport size and applies device + orientation classes
|
|
4
|
+
to <body>. Since we render static .visual.html files
|
|
5
|
+
outside the platform, this script replicates that behavior.
|
|
6
|
+
|
|
7
|
+
Breakpoints (matches OS runtime):
|
|
8
|
+
phone: width < 768
|
|
9
|
+
tablet: 768 ≤ width ≤ 1024
|
|
10
|
+
desktop: width > 1024
|
|
11
|
+
|
|
12
|
+
Orientation:
|
|
13
|
+
landscape: width > height
|
|
14
|
+
portrait: width ≤ height
|
|
15
|
+
|
|
16
|
+
Usage: add <script src="../theme/device-detect.js"></script>
|
|
17
|
+
at the end of <body>, AFTER the HTML content.
|
|
18
|
+
──────────────────────────────────────────────────────── */
|
|
19
|
+
(function () {
|
|
20
|
+
var PHONE = 'phone';
|
|
21
|
+
var TABLET = 'tablet';
|
|
22
|
+
var DESKTOP = 'desktop';
|
|
23
|
+
var PORTRAIT = 'portrait';
|
|
24
|
+
var LANDSCAPE = 'landscape';
|
|
25
|
+
var ALL_CLASSES = [PHONE, TABLET, DESKTOP, PORTRAIT, LANDSCAPE];
|
|
26
|
+
|
|
27
|
+
function applyDeviceClasses() {
|
|
28
|
+
var w = window.innerWidth || document.documentElement.clientWidth;
|
|
29
|
+
var h = window.innerHeight || document.documentElement.clientHeight;
|
|
30
|
+
var device = w < 768 ? PHONE : w <= 1024 ? TABLET : DESKTOP;
|
|
31
|
+
var orientation = w > h ? LANDSCAPE : PORTRAIT;
|
|
32
|
+
var body = document.body;
|
|
33
|
+
|
|
34
|
+
ALL_CLASSES.forEach(function (c) { body.classList.remove(c); });
|
|
35
|
+
body.classList.add(device, orientation);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
applyDeviceClasses();
|
|
39
|
+
window.addEventListener('resize', applyDeviceClasses);
|
|
40
|
+
})();
|