Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions apps/platform/lib/registry-catalog.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { getComponentLinks } from "@/lib/docs-navigation";
import registryData from "./registry-data.json";
import { customShowcaseItems } from "./custom-showcase";

export type ShowcaseComponent = {
slug: string;
Expand Down Expand Up @@ -29,7 +28,7 @@ export async function getRegistryCatalog(): Promise<ShowcaseCategory[]> {
getComponentLinks().map((item) => [item.slug, item] as const),
);

const items = [...registryData, ...customShowcaseItems].map((component) => {
const items = registryData.map((component) => {
const slug = component.slug;
const preset = docsMeta.get(slug);

Expand Down
12 changes: 12 additions & 0 deletions apps/platform/lib/registry-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,17 @@
"registryDependencies": [],
"sourcePath": "components/ui/textarea.tsx",
"source": "import * as React from 'react';\nimport { StyleSheet, TextInput, type TextInputProps } from 'react-native';\n\ntype TextareaProps = TextInputProps & {\n className?: string;\n variant?: 'default' | 'ghost';\n};\n\nfunction Textarea({ style, variant = 'default', editable = true, ...props }: TextareaProps) {\n return (\n <TextInput\n multiline\n textAlignVertical=\"top\"\n style={[\n styles.base,\n variant === 'ghost' ? styles.ghost : styles.default,\n editable === false ? styles.disabled : undefined,\n style,\n ]}\n editable={editable}\n placeholderTextColor=\"#71717a\"\n {...props}\n />\n );\n}\n\nconst styles = StyleSheet.create({\n base: {\n borderRadius: 8,\n color: '#09090b',\n fontSize: 14,\n minHeight: 128,\n paddingHorizontal: 12,\n paddingVertical: 12,\n width: '100%',\n },\n default: {\n backgroundColor: '#ffffff',\n borderColor: '#e4e4e7',\n borderWidth: 1,\n },\n ghost: {\n backgroundColor: '#f4f4f5',\n borderColor: 'transparent',\n borderWidth: 1,\n },\n disabled: {\n opacity: 0.5,\n },\n});\n\nexport { Textarea };\nexport type { TextareaProps };\n"
},
{
"slug": "spotlight-button",
"name": "spotlight-button",
"dependencies": [
"class-variance-authority",
"lucide-react",
"motion"
],
"registryDependencies": [],
"sourcePath": "components/animated/spotlight-button.tsx",
"source": "import { useEffect, useMemo, useRef } from \"react\";\nimport {\n Animated,\n Easing,\n Pressable,\n StyleSheet,\n View,\n type PressableProps,\n type StyleProp,\n type ViewStyle,\n} from \"react-native\";\n\nimport { Colors } from \"@/constants/theme\";\nimport { useColorScheme } from \"@/hooks/use-color-scheme\";\nimport { Text } from \"@/registry/components/ui/text\";\n\ntype SpotlightButtonVariant = \"default\" | \"neutral\";\ntype SpotlightButtonSize = \"default\" | \"lg\";\n\ntype SpotlightButtonProps = Omit<PressableProps, \"children\"> & {\n badge?: string;\n children: React.ReactNode;\n variant?: SpotlightButtonVariant;\n size?: SpotlightButtonSize;\n};\n\nfunction getPalette(theme: \"light\" | \"dark\", variant: SpotlightButtonVariant) {\n if (variant === \"neutral\") {\n return theme === \"dark\"\n ? {\n background: \"#18181b\",\n border: \"rgba(255,255,255,0.10)\",\n text: \"#f4f4f5\",\n badgeBackground: \"rgba(255,255,255,0.08)\",\n badgeBorder: \"rgba(255,255,255,0.14)\",\n badgeText: \"#d4d4d8\",\n glow: \"rgba(255,255,255,0.12)\",\n shadow: \"rgba(0,0,0,0.45)\",\n }\n : {\n background: \"#ffffff\",\n border: \"rgba(15,23,42,0.10)\",\n text: \"#111827\",\n badgeBackground: \"rgba(15,23,42,0.05)\",\n badgeBorder: \"rgba(15,23,42,0.08)\",\n badgeText: \"#475569\",\n glow: \"rgba(59,130,246,0.14)\",\n shadow: \"rgba(15,23,42,0.14)\",\n };\n }\n\n return theme === \"dark\"\n ? {\n background: \"#60a5fa\",\n border: \"rgba(191,219,254,0.45)\",\n text: \"#08111f\",\n badgeBackground: \"rgba(255,255,255,0.30)\",\n badgeBorder: \"rgba(255,255,255,0.35)\",\n badgeText: \"#0f172a\",\n glow: \"rgba(96,165,250,0.34)\",\n shadow: \"rgba(37,99,235,0.34)\",\n }\n : {\n background: Colors.light.tint,\n border: \"rgba(125,211,252,0.45)\",\n text: \"#eff6ff\",\n badgeBackground: \"rgba(255,255,255,0.18)\",\n badgeBorder: \"rgba(255,255,255,0.22)\",\n badgeText: \"#e0f2fe\",\n glow: \"rgba(14,165,233,0.24)\",\n shadow: \"rgba(14,165,233,0.28)\",\n };\n}\n\nfunction getContainerStyle(size: SpotlightButtonSize): StyleProp<ViewStyle> {\n if (size === \"lg\") return styles.containerLg;\n return styles.containerDefault;\n}\n\nexport function SpotlightButton({\n badge = \"New\",\n children,\n variant = \"default\",\n size = \"default\",\n style,\n ...props\n}: SpotlightButtonProps) {\n const theme = useColorScheme() ?? \"light\";\n const shimmerX = useRef(new Animated.Value(-220)).current;\n const glowPulse = useRef(new Animated.Value(0.88)).current;\n const palette = useMemo(() => getPalette(theme, variant), [theme, variant]);\n\n useEffect(() => {\n const shimmerLoop = Animated.loop(\n Animated.timing(shimmerX, {\n toValue: 220,\n duration: 2600,\n easing: Easing.linear,\n useNativeDriver: true,\n }),\n );\n\n const glowLoop = Animated.loop(\n Animated.sequence([\n Animated.timing(glowPulse, {\n toValue: 1.06,\n duration: 1400,\n easing: Easing.inOut(Easing.ease),\n useNativeDriver: true,\n }),\n Animated.timing(glowPulse, {\n toValue: 0.88,\n duration: 1400,\n easing: Easing.inOut(Easing.ease),\n useNativeDriver: true,\n }),\n ]),\n );\n\n shimmerLoop.start();\n glowLoop.start();\n\n return () => {\n shimmerLoop.stop();\n glowLoop.stop();\n };\n }, [glowPulse, shimmerX]);\n\n return (\n <Pressable\n accessibilityRole=\"button\"\n {...props}\n style={(state) => {\n const { pressed } = state;\n const resolvedStyle =\n typeof style === \"function\" ? style(state) : style;\n\n return [\n styles.wrapper,\n {\n transform: [{ scale: pressed ? 0.985 : 1 }],\n opacity: props.disabled ? 0.55 : 1,\n },\n resolvedStyle,\n ];\n }}\n >\n {({ pressed }) => (\n <View\n style={[\n styles.containerBase,\n getContainerStyle(size),\n {\n backgroundColor: palette.background,\n borderColor: palette.border,\n shadowColor: palette.shadow,\n },\n pressed && styles.containerPressed,\n ]}\n >\n <Animated.View\n pointerEvents=\"none\"\n style={[\n styles.glow,\n {\n backgroundColor: palette.glow,\n transform: [{ scale: glowPulse }],\n },\n ]}\n />\n <Animated.View\n pointerEvents=\"none\"\n style={[\n styles.shimmer,\n {\n transform: [{ translateX: shimmerX }, { rotate: \"16deg\" }],\n },\n ]}\n />\n <View\n style={[\n styles.badge,\n {\n backgroundColor: palette.badgeBackground,\n borderColor: palette.badgeBorder,\n },\n ]}\n >\n <Text style={[styles.badgeText, { color: palette.badgeText }]}>\n {badge}\n </Text>\n </View>\n <View style={styles.labelRow}>\n <Text style={[styles.labelText, { color: palette.text }]}>\n {children}\n </Text>\n <Text style={[styles.arrow, { color: palette.text }]}>→</Text>\n </View>\n </View>\n )}\n </Pressable>\n );\n}\n\nconst styles = StyleSheet.create({\n wrapper: {\n borderRadius: 18,\n },\n containerBase: {\n position: \"relative\",\n overflow: \"hidden\",\n borderRadius: 18,\n borderWidth: 1,\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n gap: 12,\n shadowOffset: { width: 0, height: 18 },\n shadowOpacity: 0.24,\n shadowRadius: 26,\n elevation: 6,\n },\n containerDefault: {\n minHeight: 52,\n paddingHorizontal: 18,\n paddingVertical: 14,\n },\n containerLg: {\n minHeight: 58,\n paddingHorizontal: 22,\n paddingVertical: 16,\n },\n containerPressed: {\n opacity: 0.96,\n },\n glow: {\n position: \"absolute\",\n left: -24,\n right: -24,\n bottom: -14,\n height: 44,\n borderRadius: 999,\n },\n shimmer: {\n position: \"absolute\",\n top: -10,\n bottom: -10,\n width: 84,\n backgroundColor: \"rgba(255,255,255,0.20)\",\n },\n badge: {\n borderRadius: 999,\n borderWidth: 1,\n paddingHorizontal: 10,\n paddingVertical: 5,\n },\n badgeText: {\n fontSize: 10,\n fontWeight: \"700\",\n letterSpacing: 1.3,\n textTransform: \"uppercase\",\n },\n labelRow: {\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: 10,\n },\n labelText: {\n fontSize: 15,\n fontWeight: \"700\",\n },\n arrow: {\n fontSize: 16,\n fontWeight: \"700\",\n },\n});\n"
}
]
23 changes: 22 additions & 1 deletion apps/platform/scripts/generate-registry.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ async function generateRegistryData() {
const root = process.cwd();
const registryRoot = path.join(root, "..", "..", "packages", "registry");
const registryPath = path.join(registryRoot, "registry.json");
const showcaseRoot = path.join(root, "..", "showcase");

console.log("Reading registry from:", registryPath);
const registryRaw = await readFile(registryPath, "utf8");
const registry = JSON.parse(registryRaw);

const items = await Promise.all(
const registryItems = await Promise.all(
Object.entries(registry.components).map(async ([slug, component]) => {
const primaryFile = component.files[0];
const sourcePath = path.join(registryRoot, "src", primaryFile.path);
Expand All @@ -27,6 +28,26 @@ async function generateRegistryData() {
})
);

const spotlightSourcePath = path.join(
showcaseRoot,
"components",
"animated",
"spotlight-button.tsx",
);
const spotlightSource = await readFile(spotlightSourcePath, "utf8");

const items = [
...registryItems,
{
slug: "spotlight-button",
name: "spotlight-button",
dependencies: ["class-variance-authority", "lucide-react", "motion"],
registryDependencies: [],
sourcePath: "components/animated/spotlight-button.tsx",
source: spotlightSource,
},
];

const outputPath = path.join(root, "lib", "registry-data.json");
await writeFile(outputPath, JSON.stringify(items, null, 2));
console.log("Generated registry data to:", outputPath);
Expand Down
6 changes: 5 additions & 1 deletion apps/platform/wrangler.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name = "watermelon-platform"
main = ".open-next/worker.js"
compatibility_date = "2024-11-01"
compatibility_date = "2026-03-19"
compatibility_flags = ["nodejs_compat"]

[vars]
NEXT_TELEMETRY_DISABLED = "1"
Expand All @@ -9,5 +10,8 @@ NEXT_TELEMETRY_DISABLED = "1"
directory = ".open-next/assets"
binding = "ASSETS"

[observability]
enabled = true

[[package_manager_names]]
package_manager = "pnpm"
Loading