mirror of
https://github.com/kennethnym/aris.git
synced 2026-03-20 17:11:17 +00:00
Compare commits
3 Commits
feat/butto
...
ec083c3c77
| Author | SHA1 | Date | |
|---|---|---|---|
| ec083c3c77 | |||
| 45fa539d3e | |||
| b4ad910a14 |
@@ -1,4 +1,3 @@
|
|||||||
import Feather from "@expo/vector-icons/Feather"
|
|
||||||
import { View } from "react-native"
|
import { View } from "react-native"
|
||||||
import tw from "twrnc"
|
import tw from "twrnc"
|
||||||
|
|
||||||
@@ -15,22 +14,22 @@ function ButtonShowcase() {
|
|||||||
<Button
|
<Button
|
||||||
style={tw`self-start`}
|
style={tw`self-start`}
|
||||||
label="Add item"
|
label="Add item"
|
||||||
leadingIcon={<Feather name="plus" size={18} color="#e7e5e4" />}
|
leadingIcon={<Button.Icon name="plus" />}
|
||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
<Section title="Trailing icon">
|
<Section title="Trailing icon">
|
||||||
<Button
|
<Button
|
||||||
style={tw`self-start`}
|
style={tw`self-start`}
|
||||||
label="Next"
|
label="Next"
|
||||||
trailingIcon={<Feather name="arrow-right" size={18} color="#e7e5e4" />}
|
trailingIcon={<Button.Icon name="arrow-right" />}
|
||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
<Section title="Both icons">
|
<Section title="Both icons">
|
||||||
<Button
|
<Button
|
||||||
style={tw`self-start`}
|
style={tw`self-start`}
|
||||||
label="Download"
|
label="Download"
|
||||||
leadingIcon={<Feather name="download" size={18} color="#e7e5e4" />}
|
leadingIcon={<Button.Icon name="download" />}
|
||||||
trailingIcon={<Feather name="chevron-down" size={18} color="#e7e5e4" />}
|
trailingIcon={<Button.Icon name="chevron-down" />}
|
||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
|
import Feather from "@expo/vector-icons/Feather"
|
||||||
import { type PressableProps, Pressable, View } from "react-native"
|
import { type PressableProps, Pressable, View } from "react-native"
|
||||||
import tw from "twrnc"
|
import tw from "twrnc"
|
||||||
|
|
||||||
import { SansSerifText } from "./sans-serif-text"
|
import { SansSerifText } from "./sans-serif-text"
|
||||||
|
|
||||||
|
type FeatherIconName = React.ComponentProps<typeof Feather>["name"]
|
||||||
|
|
||||||
|
type ButtonIconProps = {
|
||||||
|
name: FeatherIconName
|
||||||
|
}
|
||||||
|
|
||||||
|
function ButtonIcon({ name }: ButtonIconProps) {
|
||||||
|
return <Feather name={name} size={18} color={tw.color("text-stone-100 dark:text-stone-200")} />
|
||||||
|
}
|
||||||
|
|
||||||
type ButtonProps = Omit<PressableProps, "children"> & {
|
type ButtonProps = Omit<PressableProps, "children"> & {
|
||||||
label: string
|
label: string
|
||||||
leadingIcon?: React.ReactNode
|
leadingIcon?: React.ReactNode
|
||||||
@@ -12,7 +23,7 @@ type ButtonProps = Omit<PressableProps, "children"> & {
|
|||||||
export function Button({ style, label, leadingIcon, trailingIcon, ...props }: ButtonProps) {
|
export function Button({ style, label, leadingIcon, trailingIcon, ...props }: ButtonProps) {
|
||||||
const hasIcons = leadingIcon != null || trailingIcon != null
|
const hasIcons = leadingIcon != null || trailingIcon != null
|
||||||
|
|
||||||
const textElement = <SansSerifText style={tw`text-stone-200 font-medium`}>{label}</SansSerifText>
|
const textElement = <SansSerifText style={tw`text-stone-100 dark:text-stone-200 font-medium`}>{label}</SansSerifText>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable style={[tw`rounded-full bg-teal-600 px-4 py-3 w-fit`, style]} {...props}>
|
<Pressable style={[tw`rounded-full bg-teal-600 px-4 py-3 w-fit`, style]} {...props}>
|
||||||
@@ -28,3 +39,5 @@ export function Button({ style, label, leadingIcon, trailingIcon, ...props }: Bu
|
|||||||
</Pressable>
|
</Pressable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button.Icon = ButtonIcon
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import Feather from "@expo/vector-icons/Feather"
|
|
||||||
import { defineRegistry } from "@json-render/react-native"
|
import { defineRegistry } from "@json-render/react-native"
|
||||||
import { View } from "react-native"
|
import { View } from "react-native"
|
||||||
import tw from "twrnc"
|
import tw from "twrnc"
|
||||||
@@ -11,10 +10,7 @@ import { SerifText } from "@/components/ui/serif-text"
|
|||||||
|
|
||||||
import { catalog } from "./catalog"
|
import { catalog } from "./catalog"
|
||||||
|
|
||||||
function featherIcon(name: string | null | undefined) {
|
type ButtonIconName = React.ComponentProps<typeof Button.Icon>["name"]
|
||||||
if (!name) return undefined
|
|
||||||
return <Feather name={name as React.ComponentProps<typeof Feather>["name"]} size={18} color="#e7e5e4" />
|
|
||||||
}
|
|
||||||
|
|
||||||
export const { registry } = defineRegistry(catalog, {
|
export const { registry } = defineRegistry(catalog, {
|
||||||
components: {
|
components: {
|
||||||
@@ -22,8 +18,8 @@ export const { registry } = defineRegistry(catalog, {
|
|||||||
Button: ({ props, emit }) => (
|
Button: ({ props, emit }) => (
|
||||||
<Button
|
<Button
|
||||||
label={props.label}
|
label={props.label}
|
||||||
leadingIcon={featherIcon(props.leadingIcon)}
|
leadingIcon={props.leadingIcon ? <Button.Icon name={props.leadingIcon as ButtonIconName} /> : undefined}
|
||||||
trailingIcon={featherIcon(props.trailingIcon)}
|
trailingIcon={props.trailingIcon ? <Button.Icon name={props.trailingIcon as ButtonIconName} /> : undefined}
|
||||||
onPress={() => emit("press")}
|
onPress={() => emit("press")}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
10
bun.lock
10
bun.lock
@@ -110,6 +110,14 @@
|
|||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"packages/aelis-components": {
|
||||||
|
"name": "@aelis/components",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@json-render/core": "*",
|
||||||
|
"@nym.sh/jrx": "*",
|
||||||
|
},
|
||||||
|
},
|
||||||
"packages/aelis-core": {
|
"packages/aelis-core": {
|
||||||
"name": "@aelis/core",
|
"name": "@aelis/core",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
@@ -189,6 +197,8 @@
|
|||||||
|
|
||||||
"@aelis/backend": ["@aelis/backend@workspace:apps/aelis-backend"],
|
"@aelis/backend": ["@aelis/backend@workspace:apps/aelis-backend"],
|
||||||
|
|
||||||
|
"@aelis/components": ["@aelis/components@workspace:packages/aelis-components"],
|
||||||
|
|
||||||
"@aelis/core": ["@aelis/core@workspace:packages/aelis-core"],
|
"@aelis/core": ["@aelis/core@workspace:packages/aelis-core"],
|
||||||
|
|
||||||
"@aelis/data-source-weatherkit": ["@aelis/data-source-weatherkit@workspace:packages/aelis-data-source-weatherkit"],
|
"@aelis/data-source-weatherkit": ["@aelis/data-source-weatherkit@workspace:packages/aelis-data-source-weatherkit"],
|
||||||
|
|||||||
14
packages/aelis-components/package.json
Normal file
14
packages/aelis-components/package.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "@aelis/components",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"types": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "bun test ./src"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@json-render/core": "*",
|
||||||
|
"@nym.sh/jrx": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
packages/aelis-components/src/button.ts
Normal file
15
packages/aelis-components/src/button.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { JrxNode } from "@nym.sh/jrx"
|
||||||
|
|
||||||
|
import { jsx } from "@nym.sh/jrx/jsx-runtime"
|
||||||
|
|
||||||
|
export type ButtonProps = {
|
||||||
|
label: string
|
||||||
|
leadingIcon?: string
|
||||||
|
trailingIcon?: string
|
||||||
|
style?: string
|
||||||
|
children?: JrxNode | JrxNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Button(props: ButtonProps): JrxNode {
|
||||||
|
return jsx("Button", props)
|
||||||
|
}
|
||||||
155
packages/aelis-components/src/components.test.tsx
Normal file
155
packages/aelis-components/src/components.test.tsx
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
/** @jsxImportSource @nym.sh/jrx */
|
||||||
|
|
||||||
|
import { render } from "@nym.sh/jrx"
|
||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
|
||||||
|
import { Button } from "./button.ts"
|
||||||
|
import { FeedCard } from "./feed-card.ts"
|
||||||
|
import { MonospaceText } from "./monospace-text.ts"
|
||||||
|
import { SansSerifText } from "./sans-serif-text.ts"
|
||||||
|
import { SerifText } from "./serif-text.ts"
|
||||||
|
|
||||||
|
describe("Button", () => {
|
||||||
|
test("renders with label", () => {
|
||||||
|
const spec = render(<Button label="Press me" />)
|
||||||
|
|
||||||
|
expect(spec.root).toStartWith("button-")
|
||||||
|
const root = spec.elements[spec.root]!
|
||||||
|
expect(root.type).toBe("Button")
|
||||||
|
expect(root.props).toEqual({ label: "Press me" })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("renders with icon props", () => {
|
||||||
|
const spec = render(<Button label="Add" leadingIcon="plus" trailingIcon="arrow-right" />)
|
||||||
|
|
||||||
|
const root = spec.elements[spec.root]!
|
||||||
|
expect(root.type).toBe("Button")
|
||||||
|
expect(root.props).toEqual({
|
||||||
|
label: "Add",
|
||||||
|
leadingIcon: "plus",
|
||||||
|
trailingIcon: "arrow-right",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("passes style as string prop", () => {
|
||||||
|
const spec = render(<Button label="Go" style="px-4 py-2" />)
|
||||||
|
|
||||||
|
const root = spec.elements[spec.root]!
|
||||||
|
expect(root.props.style).toBe("px-4 py-2")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("FeedCard", () => {
|
||||||
|
test("renders as container", () => {
|
||||||
|
const spec = render(<FeedCard />)
|
||||||
|
|
||||||
|
expect(spec.root).toStartWith("feedcard-")
|
||||||
|
const root = spec.elements[spec.root]!
|
||||||
|
expect(root.type).toBe("FeedCard")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("renders with a single child", () => {
|
||||||
|
const spec = render(
|
||||||
|
<FeedCard>
|
||||||
|
<SansSerifText content="Only child" />
|
||||||
|
</FeedCard>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const root = spec.elements[spec.root]!
|
||||||
|
expect(root.children).toHaveLength(1)
|
||||||
|
const child = spec.elements[root.children![0]!]!
|
||||||
|
expect(child.type).toBe("SansSerifText")
|
||||||
|
expect(child.props).toEqual({ content: "Only child" })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("passes style as string prop", () => {
|
||||||
|
const spec = render(<FeedCard style="p-4 border rounded-lg" />)
|
||||||
|
|
||||||
|
const root = spec.elements[spec.root]!
|
||||||
|
expect(root.props.style).toBe("p-4 border rounded-lg")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("SansSerifText", () => {
|
||||||
|
test("renders with content prop", () => {
|
||||||
|
const spec = render(<SansSerifText content="Hello" />)
|
||||||
|
|
||||||
|
expect(spec.root).toStartWith("sansseriftext-")
|
||||||
|
const root = spec.elements[spec.root]!
|
||||||
|
expect(root.type).toBe("SansSerifText")
|
||||||
|
expect(root.props).toEqual({ content: "Hello" })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("passes style as string prop", () => {
|
||||||
|
const spec = render(<SansSerifText content="Hello" style="text-sm text-stone-500" />)
|
||||||
|
|
||||||
|
const root = spec.elements[spec.root]!
|
||||||
|
expect(root.props.style).toBe("text-sm text-stone-500")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("SerifText", () => {
|
||||||
|
test("renders with content prop", () => {
|
||||||
|
const spec = render(<SerifText content="Title" />)
|
||||||
|
|
||||||
|
expect(spec.root).toStartWith("seriftext-")
|
||||||
|
const root = spec.elements[spec.root]!
|
||||||
|
expect(root.type).toBe("SerifText")
|
||||||
|
expect(root.props).toEqual({ content: "Title" })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("passes style as string prop", () => {
|
||||||
|
const spec = render(<SerifText content="Title" style="text-xl" />)
|
||||||
|
|
||||||
|
const root = spec.elements[spec.root]!
|
||||||
|
expect(root.props.style).toBe("text-xl")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("MonospaceText", () => {
|
||||||
|
test("renders with content prop", () => {
|
||||||
|
const spec = render(<MonospaceText content="code()" />)
|
||||||
|
|
||||||
|
expect(spec.root).toStartWith("monospacetext-")
|
||||||
|
const root = spec.elements[spec.root]!
|
||||||
|
expect(root.type).toBe("MonospaceText")
|
||||||
|
expect(root.props).toEqual({ content: "code()" })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("passes style as string prop", () => {
|
||||||
|
const spec = render(<MonospaceText content="code()" style="text-xs" />)
|
||||||
|
|
||||||
|
const root = spec.elements[spec.root]!
|
||||||
|
expect(root.props.style).toBe("text-xs")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("composite", () => {
|
||||||
|
test("FeedCard with nested children", () => {
|
||||||
|
const spec = render(
|
||||||
|
<FeedCard>
|
||||||
|
<SerifText content="Weather" />
|
||||||
|
<SansSerifText content="Sunny, 22C" />
|
||||||
|
<Button label="Details" />
|
||||||
|
</FeedCard>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const root = spec.elements[spec.root]!
|
||||||
|
expect(root.type).toBe("FeedCard")
|
||||||
|
expect(root.children).toHaveLength(3)
|
||||||
|
|
||||||
|
const childKeys = root.children!
|
||||||
|
const child0 = spec.elements[childKeys[0]!]!
|
||||||
|
const child1 = spec.elements[childKeys[1]!]!
|
||||||
|
const child2 = spec.elements[childKeys[2]!]!
|
||||||
|
|
||||||
|
expect(child0.type).toBe("SerifText")
|
||||||
|
expect(child0.props).toEqual({ content: "Weather" })
|
||||||
|
|
||||||
|
expect(child1.type).toBe("SansSerifText")
|
||||||
|
expect(child1.props).toEqual({ content: "Sunny, 22C" })
|
||||||
|
|
||||||
|
expect(child2.type).toBe("Button")
|
||||||
|
expect(child2.props).toEqual({ label: "Details" })
|
||||||
|
})
|
||||||
|
})
|
||||||
12
packages/aelis-components/src/feed-card.ts
Normal file
12
packages/aelis-components/src/feed-card.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { JrxNode } from "@nym.sh/jrx"
|
||||||
|
|
||||||
|
import { jsx } from "@nym.sh/jrx/jsx-runtime"
|
||||||
|
|
||||||
|
export type FeedCardProps = {
|
||||||
|
style?: string
|
||||||
|
children?: JrxNode | JrxNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FeedCard(props: FeedCardProps): JrxNode {
|
||||||
|
return jsx("FeedCard", props)
|
||||||
|
}
|
||||||
14
packages/aelis-components/src/index.ts
Normal file
14
packages/aelis-components/src/index.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export type { ButtonProps } from "./button.ts"
|
||||||
|
export { Button } from "./button.ts"
|
||||||
|
|
||||||
|
export type { FeedCardProps } from "./feed-card.ts"
|
||||||
|
export { FeedCard } from "./feed-card.ts"
|
||||||
|
|
||||||
|
export type { SansSerifTextProps } from "./sans-serif-text.ts"
|
||||||
|
export { SansSerifText } from "./sans-serif-text.ts"
|
||||||
|
|
||||||
|
export type { SerifTextProps } from "./serif-text.ts"
|
||||||
|
export { SerifText } from "./serif-text.ts"
|
||||||
|
|
||||||
|
export type { MonospaceTextProps } from "./monospace-text.ts"
|
||||||
|
export { MonospaceText } from "./monospace-text.ts"
|
||||||
13
packages/aelis-components/src/monospace-text.ts
Normal file
13
packages/aelis-components/src/monospace-text.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { JrxNode } from "@nym.sh/jrx"
|
||||||
|
|
||||||
|
import { jsx } from "@nym.sh/jrx/jsx-runtime"
|
||||||
|
|
||||||
|
export type MonospaceTextProps = {
|
||||||
|
content?: string
|
||||||
|
style?: string
|
||||||
|
children?: JrxNode | JrxNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MonospaceText(props: MonospaceTextProps): JrxNode {
|
||||||
|
return jsx("MonospaceText", props)
|
||||||
|
}
|
||||||
13
packages/aelis-components/src/sans-serif-text.ts
Normal file
13
packages/aelis-components/src/sans-serif-text.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { JrxNode } from "@nym.sh/jrx"
|
||||||
|
|
||||||
|
import { jsx } from "@nym.sh/jrx/jsx-runtime"
|
||||||
|
|
||||||
|
export type SansSerifTextProps = {
|
||||||
|
content?: string
|
||||||
|
style?: string
|
||||||
|
children?: JrxNode | JrxNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SansSerifText(props: SansSerifTextProps): JrxNode {
|
||||||
|
return jsx("SansSerifText", props)
|
||||||
|
}
|
||||||
13
packages/aelis-components/src/serif-text.ts
Normal file
13
packages/aelis-components/src/serif-text.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { JrxNode } from "@nym.sh/jrx"
|
||||||
|
|
||||||
|
import { jsx } from "@nym.sh/jrx/jsx-runtime"
|
||||||
|
|
||||||
|
export type SerifTextProps = {
|
||||||
|
content?: string
|
||||||
|
style?: string
|
||||||
|
children?: JrxNode | JrxNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SerifText(props: SerifTextProps): JrxNode {
|
||||||
|
return jsx("SerifText", props)
|
||||||
|
}
|
||||||
7
packages/aelis-components/tsconfig.json
Normal file
7
packages/aelis-components/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsxImportSource": "@nym.sh/jrx"
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -79,6 +79,12 @@ export interface FeedItem<
|
|||||||
slots?: Record<string, Slot>
|
slots?: Record<string, Slot>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Takes a FeedItem and returns a JRX node tree for rendering. */
|
||||||
|
export type FeedItemRenderer<
|
||||||
|
TType extends string = string,
|
||||||
|
TData extends Record<string, unknown> = Record<string, unknown>,
|
||||||
|
> = (item: FeedItem<TType, TData>) => JrxNode
|
||||||
|
|
||||||
/** A FeedItem with a JRX UI tree attached for client-side rendering. */
|
/** A FeedItem with a JRX UI tree attached for client-side rendering. */
|
||||||
export interface RenderedFeedItem<
|
export interface RenderedFeedItem<
|
||||||
TType extends string = string,
|
TType extends string = string,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export type { ActionDefinition } from "./action"
|
|||||||
export { UnknownActionError } from "./action"
|
export { UnknownActionError } from "./action"
|
||||||
|
|
||||||
// Feed
|
// Feed
|
||||||
export type { FeedItem, FeedItemSignals, RenderedFeedItem, Slot } from "./feed"
|
export type { FeedItem, FeedItemRenderer, FeedItemSignals, RenderedFeedItem, Slot } from "./feed"
|
||||||
export { TimeRelevance } from "./feed"
|
export { TimeRelevance } from "./feed"
|
||||||
|
|
||||||
// Feed Source
|
// Feed Source
|
||||||
|
|||||||
Reference in New Issue
Block a user