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.
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +17 -0
- package/build/swift-ui/ChevronForward.d.ts +4 -0
- package/build/swift-ui/ChevronForward.js +8 -0
- package/build/swift-ui/Gradient.d.ts +18 -0
- package/build/swift-ui/Gradient.js +8 -0
- package/build/swift-ui/Image.d.ts +20 -0
- package/build/swift-ui/Image.js +11 -0
- package/build/swift-ui/ListItem.d.ts +48 -0
- package/build/swift-ui/ListItem.js +19 -0
- package/build/swift-ui/ListItemSimple.d.ts +19 -0
- package/build/swift-ui/ListItemSimple.js +19 -0
- package/build/swift-ui/NavigationLink.d.ts +12 -0
- package/build/swift-ui/NavigationLink.js +8 -0
- package/build/swift-ui/NavigationStack.d.ts +5 -0
- package/build/swift-ui/NavigationStack.js +8 -0
- package/build/swift-ui/Slot.d.ts +16 -0
- package/build/swift-ui/Slot.js +9 -0
- package/build/swift-ui/Text.d.ts +7 -0
- package/build/swift-ui/Text.js +14 -0
- package/build/swift-ui/index.d.ts +8 -0
- package/build/swift-ui/index.js +24 -0
- package/expo-module.config.json +8 -0
- package/ios/ChevronForwardView.swift +21 -0
- package/ios/GradientView.swift +40 -0
- package/ios/ImageView.swift +32 -0
- package/ios/ListItem.swift +161 -0
- package/ios/ListItemSimple.swift +69 -0
- package/ios/NavigationLinkView.swift +43 -0
- package/ios/NavigationStackView.swift +19 -0
- package/ios/PytechUi.podspec +24 -0
- package/ios/PytechUiModule.swift +18 -0
- package/ios/SlotView.swift +107 -0
- package/ios/_shared/utils.swift +58 -0
- package/package.json +69 -0
- package/swift-ui/index.d.ts +1 -0
- 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`.
|
package/build/index.d.ts
ADDED
|
@@ -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,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,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,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');
|