@colixsystems/widget-sdk 0.262.0 → 0.263.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/README.md CHANGED
@@ -86,7 +86,11 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
86
86
 
87
87
  ## Status
88
88
 
89
- `v0.262.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
89
+ `v0.263.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
90
+
91
+ ### What's new in 0.263.0 (contract 1.213.0)
92
+
93
+ **`<Tabs>` wraps tabs that do not fit onto another line.** The strip used to scroll sideways with its scrollbar hidden, so on a computer the tabs past the edge were out of sight and a mouse wheel could not reach them. A dialog with eight tabs showed three. Now a strip too wide for its container flows onto more lines, and every tab stays visible. A label wider than the whole strip ends in an ellipsis inside it. A strip that fits is unchanged. The same strip draws the page Tabs container, on the web and in the exported app. No prop changed. A widget that wrapped `<Tabs>` in a horizontal `ScrollView` of its own should drop it.
90
94
 
91
95
  ### What's new in 0.262.0 (contract 1.212.0)
92
96
 
package/dist/contract.cjs CHANGED
@@ -3540,7 +3540,7 @@ const PRIMITIVES = [
3540
3540
  {
3541
3541
  name: "Tabs",
3542
3542
  description:
3543
- 'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`. The default way to switch between views inside a widget: its look (a segmented control, pills, or an underline — Theme Settings > Tabs) and its colours and corner radius come from the theme, so every tab strip in the app matches, including the page Tabs container. It draws the strip only; render the selected panel yourself below it. Use it rather than tabs of your own from `Pressable` chips; when the request needs a strip it cannot draw (a live count badge on each tab, a vertical rail down the side), build your own in the app\'s look with the tab role and its selected state. Props: `items` (`[{ key, label, icon?, disabled?, testID? }]`, icon is a lucide name), `value` (the selected key), `onChange(key)`, `accessibilityLabel`, `style`, `testID`, and the overrides `variant` ("segmented" | "pill" | "underline"), `activeColor`, `activeTextColor`, `textColor`, `radius`. The strip scrolls sideways when the tabs overflow. Declare `tabsStyle` / `tabsActiveColor` / `tabsActiveTextColor` / `tabsTextColor` / `tabsRadius` style fields and pass them through to let an author override one widget.',
3543
+ 'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`. The default way to switch between views inside a widget: its look (a segmented control, pills, or an underline — Theme Settings > Tabs) and its colours and corner radius come from the theme, so every tab strip in the app matches, including the page Tabs container. It draws the strip only; render the selected panel yourself below it. Use it rather than tabs of your own from `Pressable` chips; when the request needs a strip it cannot draw (a live count badge on each tab, a vertical rail down the side), build your own in the app\'s look with the tab role and its selected state. Props: `items` (`[{ key, label, icon?, disabled?, testID? }]`, icon is a lucide name), `value` (the selected key), `onChange(key)`, `accessibilityLabel`, `style`, `testID`, and the overrides `variant` ("segmented" | "pill" | "underline"), `activeColor`, `activeTextColor`, `textColor`, `radius`. When the tabs do not fit the width, the strip wraps them onto another line, so every tab stays visible; never wrap it in a horizontal `ScrollView` of your own. Declare `tabsStyle` / `tabsActiveColor` / `tabsActiveTextColor` / `tabsTextColor` / `tabsRadius` style fields and pass them through to let an author override one widget.',
3544
3544
  rnComponent: null,
3545
3545
  docsUrl: null,
3546
3546
  },
@@ -6430,7 +6430,9 @@ const CONTRACT = deepFreeze({
6430
6430
  // 1.212.0: additive (sc-13843) — `useAction(actionRef)` + the `actions` host
6431
6431
  // client + the `actionRef` propertySchema type + the `actions.run:*`
6432
6432
  // scope. A widget runs one of the workspace's Actions and reads its result.
6433
- version: "1.212.0",
6433
+ // 1.213.0: behavioural (sc-13933) — `<Tabs>` wraps tabs that do not fit onto
6434
+ // another line instead of scrolling them out of sight.
6435
+ version: "1.213.0",
6434
6436
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6435
6437
  hooks: HOOKS,
6436
6438
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -3537,7 +3537,7 @@ const PRIMITIVES = [
3537
3537
  {
3538
3538
  name: "Tabs",
3539
3539
  description:
3540
- 'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`. The default way to switch between views inside a widget: its look (a segmented control, pills, or an underline — Theme Settings > Tabs) and its colours and corner radius come from the theme, so every tab strip in the app matches, including the page Tabs container. It draws the strip only; render the selected panel yourself below it. Use it rather than tabs of your own from `Pressable` chips; when the request needs a strip it cannot draw (a live count badge on each tab, a vertical rail down the side), build your own in the app\'s look with the tab role and its selected state. Props: `items` (`[{ key, label, icon?, disabled?, testID? }]`, icon is a lucide name), `value` (the selected key), `onChange(key)`, `accessibilityLabel`, `style`, `testID`, and the overrides `variant` ("segmented" | "pill" | "underline"), `activeColor`, `activeTextColor`, `textColor`, `radius`. The strip scrolls sideways when the tabs overflow. Declare `tabsStyle` / `tabsActiveColor` / `tabsActiveTextColor` / `tabsTextColor` / `tabsRadius` style fields and pass them through to let an author override one widget.',
3540
+ 'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`. The default way to switch between views inside a widget: its look (a segmented control, pills, or an underline — Theme Settings > Tabs) and its colours and corner radius come from the theme, so every tab strip in the app matches, including the page Tabs container. It draws the strip only; render the selected panel yourself below it. Use it rather than tabs of your own from `Pressable` chips; when the request needs a strip it cannot draw (a live count badge on each tab, a vertical rail down the side), build your own in the app\'s look with the tab role and its selected state. Props: `items` (`[{ key, label, icon?, disabled?, testID? }]`, icon is a lucide name), `value` (the selected key), `onChange(key)`, `accessibilityLabel`, `style`, `testID`, and the overrides `variant` ("segmented" | "pill" | "underline"), `activeColor`, `activeTextColor`, `textColor`, `radius`. When the tabs do not fit the width, the strip wraps them onto another line, so every tab stays visible; never wrap it in a horizontal `ScrollView` of your own. Declare `tabsStyle` / `tabsActiveColor` / `tabsActiveTextColor` / `tabsTextColor` / `tabsRadius` style fields and pass them through to let an author override one widget.',
3541
3541
  rnComponent: null,
3542
3542
  docsUrl: null,
3543
3543
  },
@@ -6427,7 +6427,9 @@ const CONTRACT = deepFreeze({
6427
6427
  // 1.212.0: additive (sc-13843) — `useAction(actionRef)` + the `actions` host
6428
6428
  // client + the `actionRef` propertySchema type + the `actions.run:*`
6429
6429
  // scope. A widget runs one of the workspace's Actions and reads its result.
6430
- version: "1.212.0",
6430
+ // 1.213.0: behavioural (sc-13933) — `<Tabs>` wraps tabs that do not fit onto
6431
+ // another line instead of scrolling them out of sight.
6432
+ version: "1.213.0",
6431
6433
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6432
6434
  hooks: HOOKS,
6433
6435
  primitives: PRIMITIVES,
@@ -106,6 +106,8 @@ export const TAB_ITEM_LAYOUT = Object.freeze({
106
106
  justifyContent: "center",
107
107
  gap: 6,
108
108
  minHeight: 44,
109
+ // A label longer than the whole strip ends in an ellipsis inside it.
110
+ maxWidth: "100%",
109
111
  paddingHorizontal: 12,
110
112
  paddingVertical: 8,
111
113
  });
@@ -124,5 +126,6 @@ export function tabLabelStyle(t, theme, active) {
124
126
  fontSize: typography.sizes.sm,
125
127
  fontWeight: active ? "600" : "500",
126
128
  color: active ? t.activeTextColor : t.textColor,
129
+ flexShrink: 1,
127
130
  };
128
131
  }
package/dist/tabs-view.js CHANGED
@@ -33,10 +33,15 @@ function stripStyles(t, theme) {
33
33
  };
34
34
  }
35
35
  if (t.style === "underline") {
36
- // The rule sits on the content, so the scroller cannot clip the accent.
36
+ // A wrapped line sits close under the one above, not a tab-gap away.
37
37
  return {
38
38
  bar: null,
39
- content: { gap: spacing.lg, borderBottomWidth: 1, borderColor: t.rule },
39
+ content: {
40
+ columnGap: spacing.lg,
41
+ rowGap: spacing.xs,
42
+ borderBottomWidth: 1,
43
+ borderColor: t.rule,
44
+ },
40
45
  item: (active) => ({
41
46
  paddingHorizontal: 4,
42
47
  borderBottomWidth: 2,
@@ -50,10 +55,10 @@ function stripStyles(t, theme) {
50
55
  /**
51
56
  * Bind the shared tab strip to one platform's primitives.
52
57
  *
53
- * @param {{View: any, Text: any, Pressable: any, ScrollView: any}} rn
58
+ * @param {{View: any, Text: any, Pressable: any}} rn
54
59
  */
55
60
  export function makeTabs(rn) {
56
- const { Text, Pressable, ScrollView } = rn;
61
+ const { View, Text, Pressable } = rn;
57
62
 
58
63
  function Tabs({
59
64
  items = [],
@@ -84,13 +89,17 @@ export function makeTabs(rn) {
84
89
  const strip = stripStyles(t, theme);
85
90
 
86
91
  return React.createElement(
87
- ScrollView,
92
+ View,
88
93
  {
89
- horizontal: true,
90
- showsHorizontalScrollIndicator: false,
94
+ // Tabs that do not fit wrap onto another line: a sideways scroller
95
+ // hid them from a mouse, with nothing to say they were there.
91
96
  // A strip never grows into, or shrinks away from, the panel below.
92
- style: [{ flexGrow: 0, flexShrink: 0 }, strip.bar, style],
93
- contentContainerStyle: [{ flexDirection: "row" }, strip.content],
97
+ style: [
98
+ { flexGrow: 0, flexShrink: 0, maxWidth: "100%", flexDirection: "row", flexWrap: "wrap" },
99
+ strip.bar,
100
+ strip.content,
101
+ style,
102
+ ],
94
103
  accessibilityRole: "tablist",
95
104
  accessibilityLabel,
96
105
  testID,
@@ -1,8 +1,8 @@
1
1
  // `<Tabs>` (native). Binds ./tabs-view.js to React Native's own primitives;
2
2
  // only the import differs from ./tabs.js.
3
3
 
4
- import { View, Pressable, ScrollView } from "react-native";
4
+ import { View, Pressable } from "react-native";
5
5
  import { Text } from "./bounded-text.native.js";
6
6
  import { makeTabs } from "./tabs-view.js";
7
7
 
8
- export const Tabs = makeTabs({ View, Text, Pressable, ScrollView });
8
+ export const Tabs = makeTabs({ View, Text, Pressable });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.262.0",
3
+ "version": "0.263.0",
4
4
  "description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
5
5
  "homepage": "https://github.com/Colix-AB/AppStudio",
6
6
  "type": "module",
package/src/contract.js CHANGED
@@ -3537,7 +3537,7 @@ const PRIMITIVES = [
3537
3537
  {
3538
3538
  name: "Tabs",
3539
3539
  description:
3540
- 'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`. The default way to switch between views inside a widget: its look (a segmented control, pills, or an underline — Theme Settings > Tabs) and its colours and corner radius come from the theme, so every tab strip in the app matches, including the page Tabs container. It draws the strip only; render the selected panel yourself below it. Use it rather than tabs of your own from `Pressable` chips; when the request needs a strip it cannot draw (a live count badge on each tab, a vertical rail down the side), build your own in the app\'s look with the tab role and its selected state. Props: `items` (`[{ key, label, icon?, disabled?, testID? }]`, icon is a lucide name), `value` (the selected key), `onChange(key)`, `accessibilityLabel`, `style`, `testID`, and the overrides `variant` ("segmented" | "pill" | "underline"), `activeColor`, `activeTextColor`, `textColor`, `radius`. The strip scrolls sideways when the tabs overflow. Declare `tabsStyle` / `tabsActiveColor` / `tabsActiveTextColor` / `tabsTextColor` / `tabsRadius` style fields and pass them through to let an author override one widget.',
3540
+ 'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`. The default way to switch between views inside a widget: its look (a segmented control, pills, or an underline — Theme Settings > Tabs) and its colours and corner radius come from the theme, so every tab strip in the app matches, including the page Tabs container. It draws the strip only; render the selected panel yourself below it. Use it rather than tabs of your own from `Pressable` chips; when the request needs a strip it cannot draw (a live count badge on each tab, a vertical rail down the side), build your own in the app\'s look with the tab role and its selected state. Props: `items` (`[{ key, label, icon?, disabled?, testID? }]`, icon is a lucide name), `value` (the selected key), `onChange(key)`, `accessibilityLabel`, `style`, `testID`, and the overrides `variant` ("segmented" | "pill" | "underline"), `activeColor`, `activeTextColor`, `textColor`, `radius`. When the tabs do not fit the width, the strip wraps them onto another line, so every tab stays visible; never wrap it in a horizontal `ScrollView` of your own. Declare `tabsStyle` / `tabsActiveColor` / `tabsActiveTextColor` / `tabsTextColor` / `tabsRadius` style fields and pass them through to let an author override one widget.',
3541
3541
  rnComponent: null,
3542
3542
  docsUrl: null,
3543
3543
  },
@@ -6427,7 +6427,9 @@ const CONTRACT = deepFreeze({
6427
6427
  // 1.212.0: additive (sc-13843) — `useAction(actionRef)` + the `actions` host
6428
6428
  // client + the `actionRef` propertySchema type + the `actions.run:*`
6429
6429
  // scope. A widget runs one of the workspace's Actions and reads its result.
6430
- version: "1.212.0",
6430
+ // 1.213.0: behavioural (sc-13933) — `<Tabs>` wraps tabs that do not fit onto
6431
+ // another line instead of scrolling them out of sight.
6432
+ version: "1.213.0",
6431
6433
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6432
6434
  hooks: HOOKS,
6433
6435
  primitives: PRIMITIVES,