pytech-ui 1.0.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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/build/index.d.ts +1 -0
  4. package/build/index.js +17 -0
  5. package/build/swift-ui/ChevronForward.d.ts +4 -0
  6. package/build/swift-ui/ChevronForward.js +8 -0
  7. package/build/swift-ui/Gradient.d.ts +18 -0
  8. package/build/swift-ui/Gradient.js +8 -0
  9. package/build/swift-ui/Image.d.ts +20 -0
  10. package/build/swift-ui/Image.js +11 -0
  11. package/build/swift-ui/ListItem.d.ts +48 -0
  12. package/build/swift-ui/ListItem.js +19 -0
  13. package/build/swift-ui/ListItemSimple.d.ts +19 -0
  14. package/build/swift-ui/ListItemSimple.js +19 -0
  15. package/build/swift-ui/NavigationLink.d.ts +12 -0
  16. package/build/swift-ui/NavigationLink.js +8 -0
  17. package/build/swift-ui/NavigationStack.d.ts +5 -0
  18. package/build/swift-ui/NavigationStack.js +8 -0
  19. package/build/swift-ui/Slot.d.ts +16 -0
  20. package/build/swift-ui/Slot.js +9 -0
  21. package/build/swift-ui/Text.d.ts +7 -0
  22. package/build/swift-ui/Text.js +14 -0
  23. package/build/swift-ui/index.d.ts +8 -0
  24. package/build/swift-ui/index.js +24 -0
  25. package/expo-module.config.json +8 -0
  26. package/ios/ChevronForwardView.swift +21 -0
  27. package/ios/GradientView.swift +40 -0
  28. package/ios/ImageView.swift +32 -0
  29. package/ios/ListItem.swift +161 -0
  30. package/ios/ListItemSimple.swift +69 -0
  31. package/ios/NavigationLinkView.swift +43 -0
  32. package/ios/NavigationStackView.swift +19 -0
  33. package/ios/PytechUi.podspec +24 -0
  34. package/ios/PytechUiModule.swift +18 -0
  35. package/ios/SlotView.swift +107 -0
  36. package/ios/_shared/utils.swift +58 -0
  37. package/package.json +69 -0
  38. package/swift-ui/index.d.ts +1 -0
  39. package/swift-ui/index.js +1 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matias Tobias Gutierrez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # pytech-ui
2
+
3
+ Librería de componentes de UI nativos para apps **React Native** / **Expo** (Pytech).
4
+
5
+ ## Instalación
6
+
7
+ ```bash
8
+ npm install pytech-ui
9
+ npx expo install pytech-ui @expo/ui expo react react-native
10
+ ```
11
+
12
+ Después de instalar, en iOS:
13
+
14
+ ```bash
15
+ npx pod-install
16
+ ```
17
+
18
+ ## Uso
19
+
20
+ Componentes SwiftUI (iOS):
21
+
22
+ ```tsx
23
+ import { Gradient, ListItem, NavigationStack } from 'pytech-ui/swift-ui';
24
+ import { padding } from '@expo/ui/swift-ui/modifiers';
25
+
26
+ export function Example() {
27
+ return (
28
+ <NavigationStack modifiers={[padding({ all: 16 })]}>
29
+ <ListItem onPress={() => {}}>
30
+ <ListItem.Headline>Hola</ListItem.Headline>
31
+ </ListItem>
32
+ <Gradient systemImage="star.fill" colors={['#7580ba', '#b1c6e4']} />
33
+ </NavigationStack>
34
+ );
35
+ }
36
+ ```
37
+
38
+ También podés importar desde el entry principal:
39
+
40
+ ```tsx
41
+ import { Gradient } from 'pytech-ui';
42
+ ```
43
+
44
+ ## Plataformas
45
+
46
+ Los componentes de `pytech-ui/swift-ui` requieren **iOS** y dependen de `@expo/ui`.
47
+
48
+ ## Licencia
49
+
50
+ MIT — ver `LICENSE`.
@@ -0,0 +1 @@
1
+ export * from './swift-ui';
package/build/index.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./swift-ui"), exports);
@@ -0,0 +1,4 @@
1
+ import { type CommonViewModifierProps } from '@expo/ui/swift-ui';
2
+ export interface ChevronForwardProps extends CommonViewModifierProps {
3
+ }
4
+ export declare const ChevronForward: ({ modifiers, ...restProps }: ChevronForwardProps) => import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChevronForward = void 0;
4
+ const expo_1 = require("expo");
5
+ const modifiers_1 = require("@expo/ui/swift-ui/modifiers");
6
+ const Native = (0, expo_1.requireNativeView)('PytechUi', 'ChevronForwardView');
7
+ const ChevronForward = ({ modifiers, ...restProps }) => (<Native modifiers={modifiers} {...(modifiers ? (0, modifiers_1.createViewModifierEventListener)(modifiers) : undefined)} {...restProps}/>);
8
+ exports.ChevronForward = ChevronForward;
@@ -0,0 +1,18 @@
1
+ import { type CommonViewModifierProps } from '@expo/ui/swift-ui';
2
+ import { type ColorValue } from 'react-native';
3
+ import { type SFSymbol } from 'sf-symbols-typescript';
4
+ export interface GradientProps extends CommonViewModifierProps {
5
+ /**
6
+ If `text` is omitted/undefined, it will use `systemImage` (SF Symbol).
7
+ */
8
+ text?: string;
9
+ /**
10
+ shows an SF Symbol when `text` is omitted/undefined.
11
+ */
12
+ systemImage?: SFSymbol;
13
+ /**
14
+ An array of color values to use for the gradient.
15
+ */
16
+ colors?: ColorValue[];
17
+ }
18
+ export declare const Gradient: ({ modifiers, systemImage, colors, ...restProps }: GradientProps) => import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Gradient = void 0;
4
+ const modifiers_1 = require("@expo/ui/swift-ui/modifiers");
5
+ const expo_1 = require("expo");
6
+ const Native = (0, expo_1.requireNativeView)('PytechUi', 'GradientView');
7
+ const Gradient = ({ modifiers, systemImage = 'person.fill', colors = ['#7580ba', '#b1c6e4'], ...restProps }) => (<Native modifiers={modifiers} systemImage={systemImage} colors={colors} {...(modifiers ? (0, modifiers_1.createViewModifierEventListener)(modifiers) : undefined)} {...restProps}/>);
8
+ exports.Gradient = Gradient;
@@ -0,0 +1,20 @@
1
+ import type { ColorValue } from 'react-native';
2
+ import { type SFSymbol } from 'sf-symbols-typescript';
3
+ import type { CommonViewModifierProps } from '@expo/ui/swift-ui';
4
+ export interface ImageProps extends CommonViewModifierProps {
5
+ /**
6
+ The name of the system image (SF Symbol).
7
+ */
8
+ systemName: SFSymbol;
9
+ /**
10
+ The color of the system image.
11
+ Accepts hex, names ('red'), etc.
12
+ */
13
+ color?: ColorValue;
14
+ /**
15
+ The size of the system image in points.
16
+ If omitted, SwiftUI uses the context size (e.g. Label).
17
+ */
18
+ size?: number;
19
+ }
20
+ export declare const Image: ({ modifiers, ...restProps }: ImageProps) => import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Image = void 0;
4
+ const expo_1 = require("expo");
5
+ const modifiers_1 = require("@expo/ui/swift-ui/modifiers");
6
+ const Slot_1 = require("./Slot");
7
+ const Native = (0, expo_1.requireNativeView)('PytechUi', 'ImageView');
8
+ const Image = ({ modifiers, ...restProps }) => (<Slot_1.Slot name="image">
9
+ <Native modifiers={modifiers} {...(modifiers ? (0, modifiers_1.createViewModifierEventListener)(modifiers) : undefined)} {...restProps}/>
10
+ </Slot_1.Slot>);
11
+ exports.Image = Image;
@@ -0,0 +1,48 @@
1
+ import { type CommonViewModifierProps } from '@expo/ui/swift-ui';
2
+ export interface ListItemInsets {
3
+ horizontal?: number;
4
+ vertical?: number;
5
+ top?: number;
6
+ bottom?: number;
7
+ leading?: number;
8
+ trailing?: number;
9
+ }
10
+ export type ListItemButtonRole = 'cancel' | 'destructive';
11
+ export interface ListItemProps extends CommonViewModifierProps {
12
+ children?: React.ReactNode;
13
+ /**
14
+ When true, shows a navigation chevron on the trailing side.
15
+ Defaults to false.
16
+ */
17
+ hasNavigationLink?: boolean;
18
+ /**
19
+ SwiftUI Button role applied when the list item is pressable.
20
+ Omit to use the default button appearance.
21
+ */
22
+ buttonRole?: ListItemButtonRole;
23
+ /**
24
+ List row insets in points (iOS 26+).
25
+ Use horizontal/vertical for both sides, or top/bottom/leading/trailing individually.
26
+ Individual edges override the corresponding axis value when both are set.
27
+ */
28
+ insets?: ListItemInsets;
29
+ /**
30
+ The callback to be called when the list item is pressed.
31
+ */
32
+ onPress?: () => void;
33
+ }
34
+ export declare const ListItem: {
35
+ ({ modifiers, onPress, children, ...restProps }: ListItemProps): import("react").JSX.Element;
36
+ Headline: ({ children }: {
37
+ children: React.ReactNode;
38
+ }) => import("react").JSX.Element;
39
+ Supporting: ({ children }: {
40
+ children: React.ReactNode;
41
+ }) => import("react").JSX.Element;
42
+ Leading: ({ children }: {
43
+ children: React.ReactNode;
44
+ }) => import("react").JSX.Element;
45
+ Trailing: ({ children }: {
46
+ children: React.ReactNode;
47
+ }) => import("react").JSX.Element;
48
+ };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ListItem = void 0;
4
+ const modifiers_1 = require("@expo/ui/swift-ui/modifiers");
5
+ const expo_1 = require("expo");
6
+ const Slot_1 = require("./Slot");
7
+ const Headline = ({ children }) => (<Slot_1.Slot name="headlineContent">{children}</Slot_1.Slot>);
8
+ const Supporting = ({ children }) => (<Slot_1.Slot name="supportingContent">{children}</Slot_1.Slot>);
9
+ const Leading = ({ children }) => (<Slot_1.Slot name="leadingContent">{children}</Slot_1.Slot>);
10
+ const Trailing = ({ children }) => (<Slot_1.Slot name="trailingContent">{children}</Slot_1.Slot>);
11
+ const Native = (0, expo_1.requireNativeView)('PytechUi', 'ListItemView');
12
+ const ListItem = ({ modifiers, onPress, children, ...restProps }) => (<Native modifiers={modifiers} {...(modifiers ? (0, modifiers_1.createViewModifierEventListener)(modifiers) : undefined)} hasOnPress={onPress !== undefined} onListItemPress={onPress} {...restProps}>
13
+ {children}
14
+ </Native>);
15
+ exports.ListItem = ListItem;
16
+ exports.ListItem.Headline = Headline;
17
+ exports.ListItem.Supporting = Supporting;
18
+ exports.ListItem.Leading = Leading;
19
+ exports.ListItem.Trailing = Trailing;
@@ -0,0 +1,19 @@
1
+ import { type CommonViewModifierProps } from '@expo/ui/swift-ui';
2
+ export interface ListItemSimpleProps extends CommonViewModifierProps {
3
+ children?: React.ReactNode;
4
+ }
5
+ export declare const ListItemSimple: {
6
+ ({ modifiers, children, ...restProps }: ListItemSimpleProps): import("react").JSX.Element;
7
+ Headline: ({ children }: {
8
+ children: React.ReactNode;
9
+ }) => import("react").JSX.Element;
10
+ Supporting: ({ children }: {
11
+ children: React.ReactNode;
12
+ }) => import("react").JSX.Element;
13
+ Leading: ({ children }: {
14
+ children: React.ReactNode;
15
+ }) => import("react").JSX.Element;
16
+ Trailing: ({ children }: {
17
+ children: React.ReactNode;
18
+ }) => import("react").JSX.Element;
19
+ };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ListItemSimple = void 0;
4
+ const expo_1 = require("expo");
5
+ const modifiers_1 = require("@expo/ui/swift-ui/modifiers");
6
+ const Slot_1 = require("./Slot");
7
+ const Headline = ({ children }) => (<Slot_1.Slot name="headlineContent">{children}</Slot_1.Slot>);
8
+ const Supporting = ({ children }) => (<Slot_1.Slot name="supportingContent">{children}</Slot_1.Slot>);
9
+ const Leading = ({ children }) => (<Slot_1.Slot name="leadingContent">{children}</Slot_1.Slot>);
10
+ const Trailing = ({ children }) => (<Slot_1.Slot name="trailingContent">{children}</Slot_1.Slot>);
11
+ const Native = (0, expo_1.requireNativeView)('PytechUi', 'ListItemSimpleView');
12
+ const ListItemSimple = ({ modifiers, children, ...restProps }) => (<Native modifiers={modifiers} {...(modifiers ? (0, modifiers_1.createViewModifierEventListener)(modifiers) : undefined)} {...restProps}>
13
+ {children}
14
+ </Native>);
15
+ exports.ListItemSimple = ListItemSimple;
16
+ exports.ListItemSimple.Headline = Headline;
17
+ exports.ListItemSimple.Supporting = Supporting;
18
+ exports.ListItemSimple.Leading = Leading;
19
+ exports.ListItemSimple.Trailing = Trailing;
@@ -0,0 +1,12 @@
1
+ import { type CommonViewModifierProps } from '@expo/ui/swift-ui';
2
+ export interface NavigationLinkProps extends CommonViewModifierProps {
3
+ /**
4
+ The content that will be displayed as the navigation link.
5
+ */
6
+ children?: React.ReactNode;
7
+ /**
8
+ The callback to be called when the navigation link is pressed.
9
+ */
10
+ onPress?: () => void;
11
+ }
12
+ export declare const NavigationLink: ({ modifiers, onPress, ...restProps }: NavigationLinkProps) => import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NavigationLink = void 0;
4
+ const expo_1 = require("expo");
5
+ const modifiers_1 = require("@expo/ui/swift-ui/modifiers");
6
+ const Native = (0, expo_1.requireNativeView)('PytechUi', 'NavigationLinkView');
7
+ const NavigationLink = ({ modifiers, onPress, ...restProps }) => (<Native modifiers={modifiers} {...(modifiers ? (0, modifiers_1.createViewModifierEventListener)(modifiers) : undefined)} {...restProps} onLinkPress={onPress}/>);
8
+ exports.NavigationLink = NavigationLink;
@@ -0,0 +1,5 @@
1
+ import { type CommonViewModifierProps } from '@expo/ui/swift-ui';
2
+ export interface NavigationStackProps extends CommonViewModifierProps {
3
+ children?: React.ReactNode;
4
+ }
5
+ export declare const NavigationStack: ({ modifiers, ...restProps }: NavigationStackProps) => import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NavigationStack = void 0;
4
+ const expo_1 = require("expo");
5
+ const modifiers_1 = require("@expo/ui/swift-ui/modifiers");
6
+ const Native = (0, expo_1.requireNativeView)('PytechUi', 'NavigationStackView');
7
+ const NavigationStack = ({ modifiers, ...restProps }) => (<Native modifiers={modifiers} {...(modifiers ? (0, modifiers_1.createViewModifierEventListener)(modifiers) : undefined)} {...restProps}/>);
8
+ exports.NavigationStack = NavigationStack;
@@ -0,0 +1,16 @@
1
+ type SlotProps<ExtraProps extends Record<string, unknown> = Record<string, unknown>> = {
2
+ /**
3
+ The content of the slot.
4
+ */
5
+ children?: React.ReactNode;
6
+ /**
7
+ The name of the slot.
8
+ */
9
+ name: string;
10
+ /**
11
+ Extra props to be passed to the slot.
12
+ */
13
+ extraProps?: ExtraProps;
14
+ };
15
+ export declare const Slot: <ExtraProps extends Record<string, unknown> = Record<string, unknown>>({ children, name, extraProps, }: SlotProps<ExtraProps>) => import("react").JSX.Element;
16
+ export {};
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Slot = void 0;
4
+ const expo_1 = require("expo");
5
+ const Native = (0, expo_1.requireNativeView)('PytechUi', 'SlotView');
6
+ const Slot = ({ children, name, extraProps, }) => (<Native name={name} extraProps={extraProps}>
7
+ {children}
8
+ </Native>);
9
+ exports.Slot = Slot;
@@ -0,0 +1,7 @@
1
+ import { type TextProps } from '@expo/ui/swift-ui';
2
+ /**
3
+ Text con slot "text" para que vistas nativas (p. ej. ListItem) puedan
4
+ detectar si el contenido es texto plano o una vista custom.
5
+ */
6
+ export declare const Text: ({ modifiers, ...restProps }: TextProps) => import("react").JSX.Element;
7
+ export type { TextProps };
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Text = void 0;
4
+ const swift_ui_1 = require("@expo/ui/swift-ui");
5
+ const modifiers_1 = require("@expo/ui/swift-ui/modifiers");
6
+ const Slot_1 = require("./Slot");
7
+ /**
8
+ Text con slot "text" para que vistas nativas (p. ej. ListItem) puedan
9
+ detectar si el contenido es texto plano o una vista custom.
10
+ */
11
+ const Text = ({ modifiers, ...restProps }) => (<Slot_1.Slot name="text">
12
+ <swift_ui_1.Text modifiers={modifiers} {...(modifiers ? (0, modifiers_1.createViewModifierEventListener)(modifiers) : undefined)} {...restProps}/>
13
+ </Slot_1.Slot>);
14
+ exports.Text = Text;
@@ -0,0 +1,8 @@
1
+ export * from './NavigationLink';
2
+ export * from './NavigationStack';
3
+ export * from './ListItemSimple';
4
+ export * from './ListItem';
5
+ export * from './Text';
6
+ export * from './Image';
7
+ export * from './Gradient';
8
+ export * from './ChevronForward';
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./NavigationLink"), exports);
18
+ __exportStar(require("./NavigationStack"), exports);
19
+ __exportStar(require("./ListItemSimple"), exports);
20
+ __exportStar(require("./ListItem"), exports);
21
+ __exportStar(require("./Text"), exports);
22
+ __exportStar(require("./Image"), exports);
23
+ __exportStar(require("./Gradient"), exports);
24
+ __exportStar(require("./ChevronForward"), exports);
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "pytech-ui",
3
+ "platforms": ["apple"],
4
+ "coreFeatures": ["swiftui"],
5
+ "apple": {
6
+ "modules": ["PytechUiModule"]
7
+ }
8
+ }
@@ -0,0 +1,21 @@
1
+ import SwiftUI
2
+ import ExpoModulesCore
3
+ import ExpoUI
4
+
5
+ struct ChevronForward: View {
6
+ var body: some View {
7
+ Image(systemName: "chevron.forward")
8
+ .font(.footnote.weight(.bold))
9
+ .foregroundStyle(Color(.tertiaryLabel))
10
+ }
11
+ }
12
+
13
+ public final class ChevronForwardViewProps: UIBaseViewProps {}
14
+
15
+ struct ChevronForwardView: ExpoSwiftUI.View {
16
+ @ObservedObject var props: ChevronForwardViewProps
17
+
18
+ var body: some View {
19
+ ChevronForward()
20
+ }
21
+ }
@@ -0,0 +1,40 @@
1
+ import SwiftUI
2
+ import ExpoModulesCore
3
+ import ExpoUI
4
+
5
+
6
+ final class GradientViewProps: UIBaseViewProps {
7
+ @Field var text: String?
8
+ @Field var systemImage: String?
9
+ @Field var colors: [Color]
10
+ }
11
+
12
+ struct GradientView: ExpoSwiftUI.View {
13
+ @ObservedObject public var props: GradientViewProps
14
+
15
+ public init(props: GradientViewProps) {
16
+ self.props = props
17
+ }
18
+
19
+ public var body: some View {
20
+ Circle()
21
+ .fill(
22
+ LinearGradient(
23
+ colors: props.colors,
24
+ startPoint: .top,
25
+ endPoint: .bottom
26
+ )
27
+ )
28
+ .overlay {
29
+ if let text = props.text {
30
+ Text(String(text.prefix(3)).uppercased())
31
+ .fontWeight(.bold)
32
+ .kerning(-0.5)
33
+ .foregroundStyle(.white)
34
+ } else if let systemImage = props.systemImage {
35
+ Image(systemName: systemImage)
36
+ .foregroundStyle(.white)
37
+ }
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,32 @@
1
+ import SwiftUI
2
+ import ExpoModulesCore
3
+ import ExpoUI
4
+
5
+ public final class ImageViewProps: UIBaseViewProps {
6
+ @Field var systemName: String = ""
7
+ @Field var color: Color?
8
+ @Field var size: Double?
9
+ }
10
+
11
+ struct ImageView: ExpoSwiftUI.View {
12
+ @ObservedObject var props: ImageViewProps
13
+
14
+ var body: some View {
15
+ styledImage(Image(systemName: props.systemName))
16
+ }
17
+
18
+ @ViewBuilder
19
+ private func styledImage(_ image: Image) -> some View {
20
+ if let size = props.size, let color = props.color {
21
+ image
22
+ .font(.system(size: size))
23
+ .foregroundStyle(color)
24
+ } else if let size = props.size {
25
+ image.font(.system(size: size))
26
+ } else if let color = props.color {
27
+ image.foregroundStyle(color)
28
+ } else {
29
+ image
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,161 @@
1
+ import SwiftUI
2
+ import ExpoModulesCore
3
+ import ExpoUI
4
+
5
+ struct ListItemInsetsRecord: Record {
6
+ @Field var horizontal: Double?
7
+ @Field var vertical: Double?
8
+ @Field var top: Double?
9
+ @Field var bottom: Double?
10
+ @Field var leading: Double?
11
+ @Field var trailing: Double?
12
+ }
13
+
14
+ extension ListItemInsetsRecord: ListRowInsetsProviding {}
15
+
16
+ enum ListItemButtonRole: String, Enumerable {
17
+ case cancel
18
+ case destructive
19
+
20
+ var swiftUIRole: SwiftUI.ButtonRole {
21
+ switch self {
22
+ case .cancel:
23
+ return .cancel
24
+ case .destructive:
25
+ return .destructive
26
+ }
27
+ }
28
+ }
29
+
30
+ public final class ListItemViewProps: UIBaseViewProps {
31
+ @Field var hasNavigationLink: Bool = false
32
+ @Field var insets: ListItemInsetsRecord?
33
+ @Field var hasOnPress: Bool = false
34
+ @Field var buttonRole: ListItemButtonRole?
35
+ var onListItemPress = EventDispatcher()
36
+ }
37
+
38
+ struct ListItemView: ExpoSwiftUI.View {
39
+ @ObservedObject var props: ListItemViewProps
40
+
41
+ // Si se le pasan insets, y tiene una versión mínima de iOS 26, se aplican al item
42
+ var body: some View {
43
+ if #available(iOS 26.0, *) {
44
+ applyInsets(to: pressableRowContent)
45
+ } else {
46
+ pressableRowContent
47
+ }
48
+ }
49
+
50
+ // Si se le envia un onPress sin navigation link, se coloca un Button sobre toda la row.
51
+ @ViewBuilder
52
+ private var pressableRowContent: some View {
53
+ if props.hasOnPress && !props.hasNavigationLink {
54
+ listItemButton {
55
+ rowContent
56
+ }
57
+ } else {
58
+ rowContent
59
+ }
60
+ }
61
+
62
+ @ViewBuilder
63
+ private var rowContent: some View {
64
+ if props.children?.slot("trailingContent") != nil || props.hasNavigationLink {
65
+ HStack {
66
+ if props.hasOnPress && props.hasNavigationLink {
67
+ SwiftUI.Button(action: {
68
+ props.onListItemPress()
69
+ }){
70
+ mainLabel
71
+ }
72
+ .foregroundStyle(.primary)
73
+ }
74
+
75
+ Spacer()
76
+
77
+ slotContent(in: props.children, named: "trailingContent", secondary: true)
78
+
79
+ if props.hasNavigationLink {
80
+ ChevronForward()
81
+ }
82
+ }
83
+ } else {
84
+ mainLabel
85
+ }
86
+ }
87
+
88
+ // Es para aplicar los insets al item. Los ejes se aplican primero; top/bottom/leading/trailing los sobrescriben.
89
+ @available(iOS 26.0, *)
90
+ @ViewBuilder
91
+ private func applyInsets(to content: some View) -> some View {
92
+ if let insets = props.insets {
93
+ applyEdgeInsets(to: content, insets: insets)
94
+ } else {
95
+ content
96
+ }
97
+ }
98
+
99
+ // Button con role aplicado.
100
+ @ViewBuilder
101
+ private func listItemButton<Label: View>(@ViewBuilder label: () -> Label) -> some View {
102
+ if let buttonRole = props.buttonRole {
103
+ Button(role: buttonRole.swiftUIRole) {
104
+ props.onListItemPress()
105
+ } label: {
106
+ label()
107
+ }
108
+ } else {
109
+ Button {
110
+ props.onListItemPress()
111
+ } label: {
112
+ label()
113
+ }
114
+ }
115
+ }
116
+
117
+ // Renderiza el leading, headline y supporting content.
118
+ @ViewBuilder
119
+ private var mainLabel: some View {
120
+ let leading = props.children?.slot("leadingContent")
121
+ let hasImage = leading?.props.children?.slot("image") != nil
122
+
123
+ if let leading {
124
+ if hasImage {
125
+ Label {
126
+ titleContent // Label ya apila título + subtítulo
127
+ } icon: {
128
+ leading.foregroundStyle(.blue)
129
+ }
130
+ } else {
131
+ HStack {
132
+ leading
133
+ alignedTitleContent
134
+ }
135
+ }
136
+ } else {
137
+ alignedTitleContent
138
+ }
139
+ }
140
+
141
+ // Se fija si tiene que llevar un VStack o no.
142
+ @ViewBuilder
143
+ private var alignedTitleContent: some View {
144
+ let hasSupporting = props.children?.slot("supportingContent") != nil
145
+
146
+ if hasSupporting {
147
+ VStack(alignment: .leading) {
148
+ titleContent
149
+ }
150
+ } else {
151
+ slotContent(in: props.children, named: "headlineContent")
152
+ }
153
+ }
154
+
155
+ // Renderiza el headline y supporting content dentro de Label o VStack.
156
+ @ViewBuilder
157
+ private var titleContent: some View {
158
+ slotContent(in: props.children, named: "headlineContent")
159
+ slotContent(in: props.children, named: "supportingContent", font: .subheadline, secondary: true)
160
+ }
161
+ }
@@ -0,0 +1,69 @@
1
+ import SwiftUI
2
+ import ExpoModulesCore
3
+ import ExpoUI
4
+
5
+ public final class ListItemSimpleViewProps: UIBaseViewProps {}
6
+
7
+ struct ListItemSimpleView: ExpoSwiftUI.View {
8
+ @ObservedObject var props: ListItemSimpleViewProps
9
+
10
+ var body: some View {
11
+ if props.children?.slot("trailingContent") != nil {
12
+ HStack(){
13
+ mainLabel
14
+
15
+ Spacer()
16
+
17
+ slotContent(in: props.children, named: "trailingContent", secondary: true)
18
+ }
19
+ } else {
20
+ mainLabel
21
+ }
22
+ }
23
+
24
+ // Renderiza el leading, headline y supporting content.
25
+ @ViewBuilder
26
+ private var mainLabel: some View {
27
+ let leading = props.children?.slot("leadingContent")
28
+ let hasImage = leading?.props.children?.slot("image") != nil
29
+
30
+ if let leading {
31
+ if hasImage {
32
+ Label {
33
+ titleContent // Label ya apila título + subtítulo
34
+ } icon: {
35
+ leading
36
+ }
37
+ } else {
38
+ HStack {
39
+ leading
40
+ alignedTitleContent
41
+ }
42
+ }
43
+ } else {
44
+ alignedTitleContent
45
+ }
46
+ }
47
+
48
+ // Se fija si tiene que llevar un VStack o no.
49
+ @ViewBuilder
50
+ private var alignedTitleContent: some View {
51
+ let hasSupporting = props.children?.slot("supportingContent") != nil
52
+
53
+ if hasSupporting {
54
+ VStack(alignment: .leading) {
55
+ titleContent
56
+ }
57
+ } else {
58
+ slotContent(in: props.children, named: "headlineContent")
59
+ }
60
+ }
61
+
62
+ // Renderiza el headline y supporting content dentro de Label o VStack.
63
+ @ViewBuilder
64
+ private var titleContent: some View {
65
+ slotContent(in: props.children, named: "headlineContent")
66
+ slotContent(in: props.children, named: "supportingContent", font: .subheadline, secondary: true)
67
+ }
68
+
69
+ }
@@ -0,0 +1,43 @@
1
+ import SwiftUI
2
+ import ExpoModulesCore
3
+ import ExpoUI
4
+
5
+ public final class NavigationLinkViewProps: UIBaseViewProps {
6
+ var onLinkPress = EventDispatcher()
7
+ }
8
+
9
+ struct NavigationLinkView: ExpoSwiftUI.View {
10
+ @ObservedObject public var props: NavigationLinkViewProps
11
+
12
+ public init(props: NavigationLinkViewProps) {
13
+ self.props = props
14
+ }
15
+
16
+ public var body: some View {
17
+ ZStack {
18
+ NavigationLink {
19
+ EmptyView()
20
+ } label: {
21
+ labelContent
22
+ }
23
+ .allowsHitTesting(false)
24
+ .buttonStyle(.plain)
25
+
26
+ Color.clear
27
+ .contentShape(Rectangle())
28
+ .onTapGesture {
29
+ props.onLinkPress()
30
+ }
31
+ .onLongPressGesture(minimumDuration: 0.5) {
32
+ props.onLinkPress()
33
+ }
34
+ }
35
+ }
36
+
37
+ private var labelContent: some View {
38
+ HStack {
39
+ Children()
40
+ }
41
+ .frame(maxWidth: .infinity, alignment: .leading)
42
+ }
43
+ }
@@ -0,0 +1,19 @@
1
+ import SwiftUI
2
+ import ExpoModulesCore
3
+ import ExpoUI
4
+
5
+ public final class NavigationStackViewProps: UIBaseViewProps {}
6
+
7
+ struct NavigationStackView: ExpoSwiftUI.View {
8
+ @ObservedObject public var props: NavigationStackViewProps
9
+
10
+ public init(props: NavigationStackViewProps) {
11
+ self.props = props
12
+ }
13
+
14
+ public var body: some View {
15
+ NavigationStack {
16
+ Children()
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,24 @@
1
+ Pod::Spec.new do |s|
2
+ s.name = 'PytechUi'
3
+ s.version = '1.0.0'
4
+ s.summary = 'Native SwiftUI components for pytech-ui'
5
+ s.description = 'Native SwiftUI components for pytech-ui'
6
+ s.author = 'Matias Tobias Gutierrez'
7
+ s.homepage = 'https://github.com/MatiTobi/UI#readme'
8
+ s.license = { :type => 'MIT' }
9
+ s.platforms = {
10
+ :ios => '16.4',
11
+ :tvos => '16.4'
12
+ }
13
+ s.source = { git: 'https://github.com/MatiTobi/UI.git' }
14
+ s.static_framework = true
15
+
16
+ s.dependency 'ExpoModulesCore'
17
+ s.dependency 'ExpoUI'
18
+
19
+ s.pod_target_xcconfig = {
20
+ 'DEFINES_MODULE' => 'YES',
21
+ }
22
+
23
+ s.source_files = '**/*.{h,m,mm,swift,hpp,cpp}'
24
+ end
@@ -0,0 +1,18 @@
1
+ import ExpoModulesCore
2
+ import ExpoUI
3
+
4
+ public class MyUiModule: Module {
5
+ public func definition() -> ModuleDefinition {
6
+ Name("MyUi")
7
+
8
+ View(SlotView.self)
9
+
10
+ ExpoUIView(NavigationStackView.self)
11
+ ExpoUIView(NavigationLinkView.self)
12
+ ExpoUIView(ListItemSimpleView.self)
13
+ ExpoUIView(ListItemView.self)
14
+ ExpoUIView(ImageView.self)
15
+ ExpoUIView(GradientView.self)
16
+ ExpoUIView(ChevronForwardView.self)
17
+ }
18
+ }
@@ -0,0 +1,107 @@
1
+ import SwiftUI
2
+ import ExpoModulesCore
3
+ import ExpoUI
4
+
5
+
6
+
7
+ internal final class SlotViewProps: ExpoSwiftUI.ViewProps {
8
+ @Field var name: String = ""
9
+ @Field var extraProps: [String: Any]?
10
+ }
11
+
12
+ internal struct SlotView: ExpoSwiftUI.View {
13
+ @ObservedObject var props: SlotViewProps
14
+
15
+ init(props: SlotViewProps) {
16
+ self.props = props
17
+ }
18
+
19
+ var body: some View {
20
+ Children()
21
+ }
22
+
23
+ func extra<T>(_ key: String, as type: T.Type = T.self) -> T? {
24
+ return props.extraProps?[key] as? T
25
+ }
26
+ }
27
+
28
+ extension [any ExpoSwiftUI.AnyChild] {
29
+ func slot(_ name: String) -> SlotView? {
30
+ compactMap { $0.childView as? SlotView }
31
+ .first { $0.props.name == name }
32
+ }
33
+
34
+ func withoutSlot(_ name: String) -> [any ExpoSwiftUI.AnyChild] {
35
+ filter {
36
+ guard let slot = $0.childView as? SlotView else { return true }
37
+ return slot.props.name != name
38
+ }
39
+ }
40
+
41
+ func withoutSlots() -> [any ExpoSwiftUI.AnyChild] {
42
+ filter { !($0.childView is SlotView) }
43
+ }
44
+ }
45
+
46
+
47
+ /**
48
+ Le agrega una función para obtener el texto directo del slot.
49
+ Esto es útil para los slots que contienen nuestro `<Text>` (slot "text" → TextView).
50
+ Por ejemplo, si el slot contiene nuestro `<Text>` (slot "text" → TextView), devuelve el string.
51
+ Si no, devuelve nil.
52
+
53
+ Ejemplo:
54
+ <Slot name="text">
55
+ <Text>Hello</Text>
56
+ </Slot>
57
+ */
58
+ extension SlotView {
59
+ var isTextContent: Bool {
60
+ props.children?.slot("text") != nil || textContent() != nil
61
+ }
62
+
63
+ @ViewBuilder
64
+ func renderedContent(defaultFont: Font = .body, secondary: Bool = false) -> some View {
65
+ if isTextContent {
66
+ if let textSlot = props.children?.slot("text") {
67
+ if secondary {
68
+ textSlot
69
+ .font(defaultFont)
70
+ .foregroundStyle(.secondary)
71
+ } else {
72
+ textSlot
73
+ }
74
+ } else if secondary {
75
+ self
76
+ .font(defaultFont)
77
+ .foregroundStyle(.secondary)
78
+ } else {
79
+ self
80
+ }
81
+ } else {
82
+ self
83
+ }
84
+ }
85
+
86
+ func textContent() -> String? {
87
+ if let textSlot = props.children?.slot("text") {
88
+ return textSlot.directTextContent()
89
+ }
90
+ return directTextContent()
91
+ }
92
+
93
+ fileprivate func directTextContent() -> String? {
94
+ guard let children = props.children else { return nil }
95
+
96
+ for child in children {
97
+ if let textView = child.childView as? TextView {
98
+ let text = textView.props.text
99
+ if !text.isEmpty {
100
+ return text
101
+ }
102
+ }
103
+ }
104
+
105
+ return nil
106
+ }
107
+ }
@@ -0,0 +1,58 @@
1
+ import SwiftUI
2
+ import ExpoModulesCore
3
+ import ExpoUI
4
+
5
+
6
+
7
+ // Coloca los insets a una view, segun lo que se le pase.
8
+ protocol ListRowInsetsProviding {
9
+ var vertical: Double? { get }
10
+ var horizontal: Double? { get }
11
+ var top: Double? { get }
12
+ var bottom: Double? { get }
13
+ var leading: Double? { get }
14
+ var trailing: Double? { get }
15
+ }
16
+
17
+ @available(iOS 26.0, *)
18
+ func applyEdgeInsets<V: View, I: ListRowInsetsProviding>(to content: V, insets: I) -> some View {
19
+ let orderedInsets: [(Edge.Set, Double?)] = [
20
+ (.vertical, insets.vertical),
21
+ (.horizontal, insets.horizontal),
22
+ (.top, insets.top),
23
+ (.bottom, insets.bottom),
24
+ (.leading, insets.leading),
25
+ (.trailing, insets.trailing),
26
+ ]
27
+
28
+ return orderedInsets.reduce(AnyView(content)) { view, item in
29
+ guard let value = item.1 else { return view }
30
+ return AnyView(view.listRowInsets(item.0, CGFloat(value)))
31
+ }
32
+ }
33
+
34
+
35
+ // Renderiza un componente o un texto (si existe el slot)
36
+ @ViewBuilder
37
+ func slotContent(
38
+ in children: [any ExpoSwiftUI.AnyChild]?,
39
+ named name: String,
40
+ font: Font = .body,
41
+ secondary: Bool = false
42
+ ) -> some View {
43
+ if let slot = children?.slot(name) {
44
+ slot.renderedContent(defaultFont: font, secondary: secondary)
45
+ }
46
+ }
47
+
48
+ @ViewBuilder
49
+ func pressableRow<Content: View>(
50
+ onPress: EventDispatcher,
51
+ @ViewBuilder content: () -> Content
52
+ ) -> some View {
53
+ Button {
54
+ onPress()
55
+ } label: {
56
+ content()
57
+ }
58
+ }
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "pytech-ui",
3
+ "version": "1.0.0",
4
+ "description": "Librería de componentes de UI para Pytech",
5
+ "keywords": [
6
+ "react-native",
7
+ "expo",
8
+ "ui",
9
+ "components",
10
+ "swiftui",
11
+ "pytech"
12
+ ],
13
+ "homepage": "https://github.com/MatiTobi/UI#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/MatiTobi/UI/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/MatiTobi/UI.git"
20
+ },
21
+ "license": "MIT",
22
+ "author": "Matias Tobias Gutierrez",
23
+ "type": "commonjs",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./build/index.d.ts",
27
+ "default": "./build/index.js"
28
+ },
29
+ "./swift-ui": {
30
+ "types": "./build/swift-ui/index.d.ts",
31
+ "default": "./build/swift-ui/index.js"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "main": "build/index.js",
36
+ "types": "build/index.d.ts",
37
+ "files": [
38
+ "build",
39
+ "ios",
40
+ "swift-ui",
41
+ "expo-module.config.json",
42
+ "LICENSE",
43
+ "README.md"
44
+ ],
45
+ "scripts": {
46
+ "build": "tsc",
47
+ "clean": "node -e \"require('fs').rmSync('build',{recursive:true,force:true})\"",
48
+ "typecheck": "tsc --noEmit",
49
+ "prepublishOnly": "npm run build",
50
+ "release": "dev git && npm version patch && npm publish && dev git"
51
+ },
52
+ "dependencies": {
53
+ "sf-symbols-typescript": "^1.0.0"
54
+ },
55
+ "devDependencies": {
56
+ "@expo/ui": "~57.0.15",
57
+ "@types/react": "~19.1.0",
58
+ "expo": "~57.0.19",
59
+ "react": "19.2.3",
60
+ "react-native": "0.86.3",
61
+ "typescript": "^5.9.2"
62
+ },
63
+ "peerDependencies": {
64
+ "@expo/ui": "~57.0.15",
65
+ "expo": "~57.0.19",
66
+ "react": "19.2.3",
67
+ "react-native": "0.86.3"
68
+ }
69
+ }
@@ -0,0 +1 @@
1
+ export * from '../build/swift-ui/index';
@@ -0,0 +1 @@
1
+ module.exports = require('../build/swift-ui/index.js');