@@ -2,92 +2,24 @@ module Main where
3import Prelude
3import Prelude
5โimport Data.Array (mapWithIndex, length, filter, null, (!!))
5+import Data.Maybe (Maybe(..))
6import Data.Nullable (toNullable)
6import Data.Nullable (toNullable)
7โimport Data.Maybe (Maybe(..), fromMaybe)
7+import Demo.Navigation (demoContent, outlineSidebar)
8โimport Data.String (take, joinWith)
8+import Demo.Shared (DemoProps)
9โimport Data.Foldable (for_, foldl)
10โimport Data.Traversable (for)
11import Effect (Effect)
9import Effect (Effect)
12โimport Data.Either (Either(..))
13โimport Effect.Aff (launchAff_, try)
14โimport Effect.Class (liftEffect)
15import React.Basic (JSX)
10import React.Basic (JSX)
16โimport React.Basic.Events (handler, handler_, unsafeEventFn)
11+import React.Basic.Events (handler_)
17โimport React.Basic.Hooks (useState, useState', useEffect, useRef, readRef, writeRef, (/\))
12+import React.Basic.Hooks (useState', (/\))
18import React.Basic.Hooks as React
13import React.Basic.Hooks as React
19import Yoga.React (component)
14import Yoga.React (component)
20โimport Yoga.React.Native (nativeEvent, registerComponent, safeAreaView, text, tw, view)
15+import Yoga.React.Native (registerComponent, safeAreaView, tw, view)
21import Yoga.React.Native.Appearance (useColorScheme)
16import Yoga.React.Native.Appearance (useColorScheme)
22โimport Yoga.React.Native.MacOS.Button (nativeButton)
23โimport Yoga.React.Native.MacOS.ColorWell (nativeColorWell)
24โimport Yoga.React.Native.MacOS.DatePicker (nativeDatePicker)
25โimport Yoga.React.Native.MacOS.LevelIndicator (nativeLevelIndicator)
26โimport Yoga.React.Native.MacOS.PopUp (nativePopUp)
27โimport Yoga.React.Native.MacOS.Progress (nativeProgress)
28โimport Yoga.React.Native.MacOS.Slider (nativeSlider)
29โimport Yoga.React.Native.MacOS.Switch (nativeSwitch)
30โimport Yoga.React.Native.MacOS.TextField (nativeTextField)
31โimport Yoga.React.Native.MacOS.TextEditor (nativeTextEditor)
32โimport Yoga.React.Native.MacOS.WebView (nativeWebView)
33โimport Yoga.React.Native.MacOS.ScrollView (nativeScrollView)
34โimport Yoga.React.Native.MacOS.Rive (nativeRiveView_)
35โimport Yoga.React.Native.MacOS.Toolbar (nativeToolbar)
36โimport Yoga.React.Native.MacOS.VisualEffect (nativeVisualEffect)
37import Yoga.React.Native.MacOS.Sidebar (sidebarLayout)
17import Yoga.React.Native.MacOS.Sidebar (sidebarLayout)
38โimport Yoga.React.Native.MacOS.ContextMenu (nativeContextMenu)
18+import Yoga.React.Native.MacOS.Toolbar (nativeToolbar)
39โimport Yoga.React.Native.MacOS.FilePicker (nativeFilePicker)
40โimport Yoga.React.Native.MacOS.VideoPlayer (nativeVideoPlayer)
41โimport Yoga.React.Native.MacOS.AnimatedImage (nativeAnimatedImage)
42โimport Yoga.React.Native.MacOS.PatternBackground (nativePatternBackground)
43โimport Yoga.React.Native.MacOS.SplitView (nativeSplitView)
44โimport Yoga.React.Native.MacOS.TabView (nativeTabView)
45โimport Yoga.React.Native.MacOS.ComboBox (nativeComboBox)
46โimport Yoga.React.Native.MacOS.Stepper (nativeStepper)
47โimport Yoga.React.Native.MacOS.Box (nativeBox)
48โimport Yoga.React.Native.MacOS.Popover (nativePopover)
49โimport Yoga.React.Native.MacOS.Image (nativeImage)
50โimport Yoga.React.Native.MacOS.Alert (macosAlert)
51โimport Yoga.React.Native.MacOS.Checkbox (nativeCheckbox)
52โimport Yoga.React.Native.MacOS.RadioButton (nativeRadioButton)
53โimport Yoga.React.Native.MacOS.SearchField (nativeSearchField)
54โimport Yoga.React.Native.MacOS.TokenField (nativeTokenField)
55โimport Yoga.React.Native.MacOS.Separator (nativeSeparator)
56โimport Yoga.React.Native.MacOS.HelpButton (nativeHelpButton)
57โimport Yoga.React.Native.MacOS.PathControl (nativePathControl)
58โimport Yoga.React.Native.MacOS.Sheet (nativeSheet)
59โimport Yoga.React.Native.MacOS.TableView (nativeTableView)
60โimport Yoga.React.Native.MacOS.Menu (macosShowMenu)
61โimport Yoga.React.Native.MacOS.Pasteboard (copyToClipboard)
62โimport Yoga.React.Native.MacOS.OutlineView (nativeOutlineView, OutlineItem(..))
63โimport Yoga.React.Native.MacOS.ShareService (macosShare)
64โimport Yoga.React.Native.MacOS.UserNotification (macosNotify)
65โimport Yoga.React.Native.MacOS.Sound (macosPlaySound, macosBeep)
66โimport Yoga.React.Native.MacOS.StatusBarItem (macosSetStatusBarItem, macosRemoveStatusBarItem)
67โimport Yoga.React.Native.MacOS.MapView (nativeMapView)
68โimport Yoga.React.Native.MacOS.PDFView (nativePDFView)
69โimport Yoga.React.Native.MacOS.QuickLook (macosQuickLook)
70โimport Yoga.React.Native.MacOS.SpeechSynthesizer (say, stopSpeaking)
71โimport Yoga.React.Native.MacOS.ColorPanel (macosShowColorPanel)
72โimport Yoga.React.Native.MacOS.FontPanel (macosShowFontPanel)
73โimport Yoga.React.Native.MacOS.OCR (recognizeText)
74โimport Yoga.React.Native.MacOS.SpeechRecognition (useSpeechRecognition)
75โimport Yoga.React.Native.MacOS.NaturalLanguage (detectLanguage, analyzeSentiment, tokenize)
76โimport Yoga.React.Native.MacOS.CameraView (nativeCameraView)
77โimport Yoga.React.Native.MacOS.Events as E
78import Yoga.React.Native.MacOS.Types as T
19import Yoga.React.Native.MacOS.Types as T
79โimport Yoga.React.Native.Matrix as Matrix
20+import Yoga.React.Native.MacOS.VisualEffect (nativeVisualEffect)
80import Yoga.React.Native.Style as Style
21import Yoga.React.Native.Style as Style
82โ-- Shared props for all leaf demos
88โ , isDark :: Boolean
91main :: Effect Unit
23main :: Effect Unit
92main = registerComponent "YogaReactExample" \_ -> app {}
24main = registerComponent "YogaReactExample" \_ -> app {}
@@ -124,1779 +56,3 @@ app = component "App" \_ -> React.do
129โoutlineTree :: Array OutlineItem
132โ leaf id title symbol = OutlineItem { id, title, sfSymbol: symbol, children: [] }
133โ folder id title symbol children = OutlineItem { id, title, sfSymbol: symbol, children }
135โ [ folder "input" "Input Controls" "slider.horizontal.3"
136โ [ leaf "button" "Button" "button.horizontal"
137โ , leaf "switch" "Switch" "switch.2"
138โ , leaf "slider" "Slider" "slider.horizontal.below.rectangle"
139โ , leaf "popup" "Pop Up" "chevron.up.chevron.down"
140โ , leaf "combobox" "Combo Box" "rectangle.and.pencil.and.ellipsis"
141โ , leaf "stepper" "Stepper" "plus.forwardslash.minus"
142โ , leaf "datepicker" "Date Picker" "calendar"
143โ , leaf "colorwell" "Color Well" "paintpalette"
144โ , leaf "checkbox" "Checkbox" "checkmark.square"
145โ , leaf "radiobutton" "Radio Button" "circle.inset.filled"
146โ , leaf "searchfield" "Search Field" "magnifyingglass"
147โ , leaf "tokenfield" "Token Field" "tag"
149โ , folder "text" "Text & Editing" "doc.richtext"
150โ [ leaf "textfield" "Text Field" "character.cursor.ibeam"
151โ , leaf "texteditor" "Text Editor" "doc.richtext"
153โ , folder "display" "Display" "photo"
154โ [ leaf "image" "Image" "photo"
155โ , leaf "animatedimage" "Animated Image" "photo.on.rectangle.angled"
156โ , leaf "levelindicator" "Level Indicator" "gauge.with.dots.needle.33percent"
157โ , leaf "progress" "Progress" "chart.bar.fill"
158โ , leaf "separator" "Separator" "minus"
159โ , leaf "pathcontrol" "Path Control" "chevron.compact.right"
161โ , folder "layout" "Layout & Containers" "rectangle.split.3x1"
162โ [ leaf "box" "Box" "square.dashed"
163โ , leaf "splitview" "Split View" "rectangle.split.2x1"
164โ , leaf "tabview" "Tab View" "rectangle.topthird.inset.filled"
166โ , folder "overlays" "Overlays & Dialogs" "rectangle.on.rectangle"
167โ [ leaf "alert" "Alert" "exclamationmark.triangle"
168โ , leaf "sheet" "Sheet" "rectangle.bottomhalf.inset.filled"
169โ , leaf "popover" "Popover" "text.bubble"
170โ , leaf "contextmenu" "Context Menu" "contextualmenu.and.cursorarrow"
171โ , leaf "menu" "Menu" "list.bullet"
173โ , folder "dataviews" "Data Views" "tablecells"
174โ [ leaf "tableview" "Table View" "tablecells"
175โ , leaf "outlineview" "Outline View" "list.bullet.indent"
177โ , folder "dragdrop" "Drag & Drop / Files" "doc.badge.plus"
178โ [ leaf "dropzone" "Drop Zone" "square.and.arrow.down"
179โ , leaf "filepicker" "File Picker" "folder"
181โ , folder "animation" "Animation" "play.circle"
182โ [ leaf "rive" "Rive" "play.circle"
184โ , folder "system" "System Services" "gearshape.2"
185โ [ leaf "clipboard" "Clipboard" "doc.on.clipboard"
186โ , leaf "share" "Share" "square.and.arrow.up"
187โ , leaf "notifications" "Notifications" "bell"
188โ , leaf "sound" "Sound" "speaker.wave.2"
189โ , leaf "statusbar" "Status Bar" "menubar.rectangle"
190โ , leaf "quicklook" "Quick Look" "eye"
191โ , leaf "colorpanel" "Color Panel" "paintpalette"
192โ , leaf "fontpanel" "Font Panel" "textformat"
193โ , leaf "speech" "Speech Synthesis" "speaker.wave.2"
195โ , folder "aiml" "AI & ML" "brain"
196โ [ leaf "ocr" "OCR" "doc.text.viewfinder"
197โ , leaf "speechrecognition" "Speech Recognition" "mic"
198โ , leaf "naturallanguage" "Natural Language" "text.magnifyingglass"
199โ , leaf "camera" "Camera" "video"
201โ , folder "mapsdocs" "Maps & Documents" "map"
202โ [ leaf "mapview" "Map View" "map"
203โ , leaf "pdfview" "PDF View" "doc.richtext"
205โ , folder "webbrowser" "Web Browser" "globe"
206โ [ leaf "webview" "Web View" "globe"
208โ , folder "chat" "Chat" "bubble.left.and.bubble.right"
209โ [ leaf "matrix" "Matrix" "bubble.left.and.bubble.right"
213โoutlineSidebar :: String -> (String -> Effect Unit) -> JSX
214โoutlineSidebar selectedItem setSelectedItem =
215โ view { style: tw "flex-1 pt-2" }
216โ [ nativeOutlineView
217โ { items: outlineTree
218โ , headerVisible: false
219โ , onSelectItem: E.onString "id" setSelectedItem
220โ , style: tw "flex-1"
224โ-- Content dispatch
225โdemoContent :: DemoProps -> String -> JSX
226โdemoContent dp = case _ of
227โ "button" -> buttonDemo dp
228โ "switch" -> switchDemo dp
229โ "slider" -> sliderDemo dp
230โ "popup" -> popUpDemo dp
231โ "combobox" -> comboBoxDemo dp
232โ "stepper" -> stepperDemo dp
233โ "datepicker" -> datePickerDemo dp
234โ "colorwell" -> colorWellDemo dp
235โ "checkbox" -> checkboxDemo dp
236โ "radiobutton" -> radioButtonDemo dp
237โ "searchfield" -> searchFieldDemo dp
238โ "tokenfield" -> tokenFieldDemo dp
239โ "textfield" -> textFieldDemo dp
240โ "texteditor" -> textEditorDemo dp
241โ "image" -> imageDemo dp
242โ "animatedimage" -> animatedImageDemo dp
243โ "levelindicator" -> levelIndicatorDemo dp
244โ "progress" -> progressDemo dp
245โ "separator" -> separatorDemo dp
246โ "pathcontrol" -> pathControlDemo dp
247โ "box" -> boxDemo dp
248โ "splitview" -> splitViewDemo dp
249โ "tabview" -> tabViewDemo dp
250โ "alert" -> alertDemo dp
251โ "sheet" -> sheetDemo dp
252โ "popover" -> popoverDemo dp
253โ "contextmenu" -> contextMenuDemo dp
254โ "menu" -> menuDemo dp
255โ "tableview" -> tableViewDemo dp
256โ "outlineview" -> outlineViewDemo dp
257โ "dropzone" -> dropZoneDemo dp
258โ "filepicker" -> filePickerDemo dp
259โ "rive" -> riveDemo dp
260โ "clipboard" -> clipboardDemo dp
261โ "share" -> shareDemo dp
262โ "notifications" -> notificationsDemo dp
263โ "sound" -> soundDemo dp
264โ "statusbar" -> statusBarDemo dp
265โ "quicklook" -> quickLookDemo dp
266โ "colorpanel" -> colorPanelDemo dp
267โ "fontpanel" -> fontPanelDemo dp
268โ "speech" -> speechDemo dp
269โ "ocr" -> ocrDemo dp
270โ "speechrecognition" -> speechRecognitionDemo dp
271โ "naturallanguage" -> naturalLanguageDemo dp
272โ "camera" -> cameraDemo dp
273โ "mapview" -> mapViewDemo dp
274โ "pdfview" -> pdfViewDemo dp
275โ "webview" -> webViewDemo dp
276โ "matrix" -> matrixDemo dp
277โ _ -> placeholder dp
279โplaceholder :: DemoProps -> JSX
281โ view { style: tw "flex-1 items-center justify-center" }
282โ [ text { style: tw "text-sm" <> Style.style { color: dp.dimFg } } "Select a component from the sidebar" ]
286โbuttonDemo :: DemoProps -> JSX
287โbuttonDemo = component "ButtonDemo" \dp -> React.do
288โ status /\ setStatus <- useState' "Ready"
291โ [ sectionTitle dp.fg "Button"
293โ [ view { style: tw "flex-row items-center" }
295โ { title: "Say Hello"
296โ , sfSymbol: "hand.wave"
297โ , bezelStyle: T.push
299โ , onPress: handler_ (setStatus "Hello from PureScript!")
300โ , style: Style.style { height: 24.0, width: 140.0 }
304โ , sfSymbol: "arrow.counterclockwise"
305โ , bezelStyle: T.push
306โ , onPress: handler_ (setStatus "Ready")
307โ , style: Style.style { height: 24.0, width: 100.0, marginLeft: 8.0 }
309โ , label dp.dimFg status
314โswitchDemo :: DemoProps -> JSX
315โswitchDemo = component "SwitchDemo" \dp -> React.do
316โ on /\ setOn <- useState' false
319โ [ sectionTitle dp.fg "Switch"
321โ [ view { style: tw "flex-row items-center" }
324โ , onChange: E.onBool "on" setOn
325โ , style: Style.style { height: 24.0, width: 48.0 }
327โ , label dp.dimFg (if on then "On" else "Off")
332โsliderDemo :: DemoProps -> JSX
333โsliderDemo = component "SliderDemo" \dp -> React.do
334โ value /\ setValue <- useState' 50.0
337โ [ sectionTitle dp.fg "Slider + Level Indicator + Progress"
343โ , numberOfTickMarks: 11
344โ , onChange: E.onNumber "value" setValue
345โ , style: Style.style { height: 24.0 }
347โ , label dp.dimFg ("Value: " <> show (round value) <> " / 100")
348โ , nativeLevelIndicator
352โ , warningValue: 70.0
353โ , criticalValue: 90.0
354โ , style: Style.style { height: 18.0, marginTop: 8.0 }
358โ , style: Style.style { height: 18.0, marginTop: 8.0 }
363โpopUpDemo :: DemoProps -> JSX
364โpopUpDemo = component "PopUpDemo" \dp -> React.do
365โ idx /\ setIdx <- useState' 0
366โ title /\ setTitle <- useState' "Small"
369โ [ sectionTitle dp.fg "Pop Up Button"
371โ [ view { style: tw "flex-row items-center" }
373โ { items: [ "Small", "Medium", "Large", "Extra Large" ]
374โ , selectedIndex: idx
375โ , onChange: handler
376โ ( nativeEvent >>> unsafeEventFn \e ->
377โ { idx: E.getFieldInt "selectedIndex" e, title: E.getFieldStr "title" e }
382โ , style: Style.style { height: 24.0, width: 160.0 }
384โ , label dp.dimFg ("Selected: " <> title)
389โcomboBoxDemo :: DemoProps -> JSX
390โcomboBoxDemo = component "ComboBoxDemo" \dp -> React.do
391โ txt /\ setTxt <- useState' ""
392โ result /\ setResult <- useState' ""
395โ [ sectionTitle dp.fg "Combo Box"
396โ , desc dp "Editable dropdown with autocomplete"
399โ { items: [ "Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape" ]
401โ , placeholder: "Type a fruit..."
402โ , onChangeText: E.onString "text" setTxt
403โ , onSelectItem: E.onString "text" \t -> do
405โ setResult ("Selected: " <> t)
406โ , style: Style.style { height: 28.0 } <> tw "mb-2"
408โ , if result == "" then mempty
409โ else label dp.dimFg result
413โstepperDemo :: DemoProps -> JSX
414โstepperDemo = component "StepperDemo" \dp -> React.do
415โ value /\ setValue <- useState' 5.0
418โ [ sectionTitle dp.fg "Stepper"
419โ , desc dp "Up/down increment control"
421โ [ view { style: tw "flex-row items-center" }
422โ [ text { style: tw "text-sm mr-3" <> Style.style { color: dp.fg } } "Quantity:"
428โ , onChange: E.onNumber "value" setValue
429โ , style: Style.style { width: 100.0, height: 24.0 }
435โdatePickerDemo :: DemoProps -> JSX
436โdatePickerDemo = component "DatePickerDemo" \dp -> React.do
437โ dateText /\ setDateText <- useState' ""
440โ [ sectionTitle dp.fg "Date Picker"
442โ [ nativeDatePicker
443โ { graphical: false
444โ , onChange: E.onString "date" setDateText
445โ , style: Style.style { height: 24.0, width: 200.0 }
447โ , if dateText == "" then mempty
448โ else label dp.dimFg ("Picked: " <> dateText)
452โcolorWellDemo :: DemoProps -> JSX
453โcolorWellDemo = component "ColorWellDemo" \dp -> React.do
454โ color /\ setColor <- useState' "#FF6600"
457โ [ sectionTitle dp.fg "Color Well"
459โ [ view { style: tw "flex-row items-center" }
463โ , onChange: E.onString "color" setColor
464โ , style: Style.style { height: 32.0, width: 48.0 }
467โ { style: tw "ml-3 rounded" <> Style.style
468โ { width: 24.0, height: 24.0, backgroundColor: color }
471โ , label dp.dimFg color
476โcheckboxDemo :: DemoProps -> JSX
477โcheckboxDemo = component "CheckboxDemo" \dp -> React.do
478โ a /\ setA <- useState' true
479โ b /\ setB <- useState' false
482โ [ sectionTitle dp.fg "Checkbox"
486โ , title: "Enable notifications"
488โ , onChange: E.onBool "checked" setA
489โ , style: Style.style { height: 24.0 } <> tw "mb-1"
493โ , title: "Dark mode"
495โ , onChange: E.onBool "checked" setB
496โ , style: Style.style { height: 24.0 }
501โradioButtonDemo :: DemoProps -> JSX
502โradioButtonDemo = component "RadioButtonDemo" \dp -> React.do
503โ choice /\ setChoice <- useState' "option1"
506โ [ sectionTitle dp.fg "Radio Button"
508โ [ nativeRadioButton
509โ { selected: choice == "option1"
512โ , onChange: handler_ (setChoice "option1")
513โ , style: Style.style { height: 24.0 } <> tw "mb-1"
515โ , nativeRadioButton
516โ { selected: choice == "option2"
519โ , onChange: handler_ (setChoice "option2")
520โ , style: Style.style { height: 24.0 } <> tw "mb-1"
522โ , nativeRadioButton
523โ { selected: choice == "option3"
526โ , onChange: handler_ (setChoice "option3")
527โ , style: Style.style { height: 24.0 }
529โ , label dp.dimFg ("Selected: " <> choice)
533โsearchFieldDemo :: DemoProps -> JSX
534โsearchFieldDemo = component "SearchFieldDemo" \dp -> React.do
535โ query /\ setQuery <- useState' ""
536โ result /\ setResult <- useState' ""
539โ [ sectionTitle dp.fg "Search Field"
540โ , nativeSearchField
542โ , placeholder: "Search..."
543โ , onChangeText: E.onString "text" setQuery
544โ , onSearch: E.onString "text" \t -> setResult ("Searched: " <> t)
545โ , style: Style.style { height: 28.0 } <> tw "mb-2"
547โ , if result == "" then mempty
548โ else label dp.dimFg result
551โtokenFieldDemo :: DemoProps -> JSX
552โtokenFieldDemo = component "TokenFieldDemo" \dp -> React.do
553โ tokens /\ setTokens <- useState' ([ "PureScript", "React", "macOS" ] :: Array String)
556โ [ sectionTitle dp.fg "Token Field"
557โ , desc dp "Type text and press Enter to add tokens"
558โ , nativeTokenField
560โ , placeholder: "Add tags..."
561โ , onChangeTokens: E.onStrings "tokens" setTokens
562โ , style: Style.style { height: 28.0 } <> tw "mb-2"
568โtextFieldDemo :: DemoProps -> JSX
569โtextFieldDemo = component "TextFieldDemo" \dp -> React.do
570โ txt /\ setTxt <- useState' ""
573โ [ sectionTitle dp.fg "Text Field"
576โ { placeholder: "Type something..."
579โ , onChangeText: E.onString "text" setTxt
580โ , style: Style.style { height: 24.0 }
582โ , if txt == "" then mempty
583โ else label dp.dimFg ("You typed: " <> txt)
587โtextEditorDemo :: DemoProps -> JSX
588โtextEditorDemo = component "TextEditorDemo" \dp -> React.do
590โ view { style: tw "flex-1 px-4" }
591โ [ sectionTitle dp.fg "Rich Text Editor"
592โ , nativeTextEditor
593โ { text: "Welcome to the PureScript-driven native text editor.\n\nThis uses NSTextView with rich text support and a formatting ruler."
595โ , showsRuler: true
596โ , style: tw "flex-1" <> Style.style { minHeight: 300.0 }
602โimageDemo :: DemoProps -> JSX
603โimageDemo = component "ImageDemo" \dp -> React.do
606โ [ sectionTitle dp.fg "Image"
607โ , desc dp "Native NSImageView with URL loading and corner radius"
609โ { source: "https://placedog.net/640/400"
610โ , contentMode: T.scaleProportionally
611โ , cornerRadius: 12.0
612โ , style: Style.style { height: 200.0 } <> tw "mb-2"
616โanimatedImageDemo :: DemoProps -> JSX
617โanimatedImageDemo = component "AnimatedImageDemo" \dp -> React.do
620โ [ sectionTitle dp.fg "Animated Image"
621โ , desc dp "Native NSImageView with animated GIF support"
622โ , nativeAnimatedImage
623โ { source: "https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif"
625โ , style: Style.style { height: 200.0 } <> tw "rounded-lg overflow-hidden mb-2"
627โ , nativeAnimatedImage
628โ { source: "https://media.giphy.com/media/13CoXDiaCcCoyk/giphy.gif"
630โ , style: Style.style { height: 200.0 } <> tw "rounded-lg overflow-hidden mb-2"
634โlevelIndicatorDemo :: DemoProps -> JSX
635โlevelIndicatorDemo = component "LevelIndicatorDemo" \dp -> React.do
636โ value /\ setValue <- useState' 50.0
639โ [ sectionTitle dp.fg "Level Indicator"
645โ , numberOfTickMarks: 11
646โ , onChange: E.onNumber "value" setValue
647โ , style: Style.style { height: 24.0 }
649โ , nativeLevelIndicator
653โ , warningValue: 70.0
654โ , criticalValue: 90.0
655โ , style: Style.style { height: 18.0, marginTop: 8.0 }
660โprogressDemo :: DemoProps -> JSX
661โprogressDemo = component "ProgressDemo" \dp -> React.do
662โ value /\ setValue <- useState' 50.0
665โ [ sectionTitle dp.fg "Progress"
671โ , numberOfTickMarks: 11
672โ , onChange: E.onNumber "value" setValue
673โ , style: Style.style { height: 24.0 }
677โ , style: Style.style { height: 18.0, marginTop: 8.0 }
682โseparatorDemo :: DemoProps -> JSX
683โseparatorDemo = component "SeparatorDemo" \dp -> React.do
686โ [ sectionTitle dp.fg "Separator"
687โ , desc dp "Horizontal and vertical separators"
690โ , style: Style.style { height: 2.0 } <> tw "my-2"
692โ , text { style: tw "text-xs mb-2" <> Style.style { color: dp.dimFg } } "Content between separators"
695โ , style: Style.style { height: 2.0 } <> tw "my-2"
699โpathControlDemo :: DemoProps -> JSX
700โpathControlDemo = component "PathControlDemo" \dp -> React.do
703โ [ sectionTitle dp.fg "Path Control"
704โ , nativePathControl
706โ , pathStyle: T.standardPath
707โ , onSelectPath: E.onString "url" \_ -> pure unit
708โ , style: Style.style { height: 24.0 } <> tw "mb-2"
712โ-- Layout & Containers
714โboxDemo :: DemoProps -> JSX
715โboxDemo = component "BoxDemo" \dp -> React.do
718โ [ sectionTitle dp.fg "Box"
719โ , desc dp "Native NSBox with title and border"
721โ { boxTitle: "Settings"
722โ , fillColor2: dp.cardBg
723โ , borderColor2: dp.dimFg
725โ , style: Style.style { height: 100.0 } <> tw "mb-2"
727โ [ view { style: tw "flex-row items-center p-2" }
728โ [ text { style: tw "text-sm" <> Style.style { color: dp.fg } } "Content inside a native box" ]
732โsplitViewDemo :: DemoProps -> JSX
733โsplitViewDemo = component "SplitViewDemo" \dp -> React.do
736โ [ sectionTitle dp.fg "Split View"
737โ , desc dp "Native NSSplitView with resizable divider"
739โ { isVertical: true
740โ , style: Style.style { height: 150.0 } <> tw "rounded-lg overflow-hidden mb-2"
742โ [ view { style: tw "flex-1 p-4" <> Style.style { backgroundColor: dp.cardBg } }
743โ [ text { style: tw "text-sm font-semibold" <> Style.style { color: dp.fg } } "Left Pane"
744โ , text { style: tw "text-xs mt-1" <> Style.style { color: dp.dimFg } } "Drag the divider to resize"
746โ , view { style: tw "flex-1 p-4" <> Style.style { backgroundColor: dp.cardBg } }
747โ [ text { style: tw "text-sm font-semibold" <> Style.style { color: dp.fg } } "Right Pane"
748โ , text { style: tw "text-xs mt-1" <> Style.style { color: dp.dimFg } } "Content fills available space"
753โtabViewDemo :: DemoProps -> JSX
754โtabViewDemo = component "TabViewDemo" \dp -> React.do
755โ selected /\ setSelected <- useState' "general"
758โ [ sectionTitle dp.fg "Tab View"
759โ , desc dp "Native segmented tab bar"
762โ [ { id: "general", label: "General" }
763โ , { id: "advanced", label: "Advanced" }
764โ , { id: "about", label: "About" }
766โ , selectedItem: selected
767โ , onSelectTab: E.onString "tabId" setSelected
768โ , style: Style.style { height: 32.0 } <> tw "mb-2"
772โ-- Overlays & Dialogs
774โalertDemo :: DemoProps -> JSX
775โalertDemo = component "AlertDemo" \dp -> React.do
778โ [ sectionTitle dp.fg "Alert"
779โ , desc dp "Native alert dialog"
781โ { title: "Show Alert"
782โ , bezelStyle: T.push
783โ , onPress: handler_ (macosAlert T.warning "Are you sure?" "This action cannot be undone." [ "Cancel", "OK" ])
784โ , style: Style.style { height: 24.0, width: 120.0 } <> tw "mb-4"
788โsheetDemo :: DemoProps -> JSX
789โsheetDemo = component "SheetDemo" \dp -> React.do
790โ visible /\ setVisible <- useState' false
793โ [ sectionTitle dp.fg "Sheet"
794โ , desc dp "Modal sheet attached to the window"
796โ { title: "Show Sheet"
797โ , bezelStyle: T.push
798โ , onPress: handler_ (setVisible true)
799โ , style: Style.style { height: 24.0, width: 120.0 } <> tw "mb-2"
803โ , onDismiss: handler_ (setVisible false)
805โ [ view { style: tw "p-6" <> Style.style { width: 400.0, height: 200.0 } }
806โ [ text { style: tw "text-lg font-semibold mb-2" <> Style.style { color: dp.fg } } "Sheet Content"
807โ , text { style: tw "text-sm mb-4" <> Style.style { color: dp.dimFg } } "This is a native macOS sheet."
809โ { title: "Dismiss"
810โ , bezelStyle: T.push
811โ , onPress: handler_ (setVisible false)
812โ , style: Style.style { height: 24.0, width: 100.0 }
818โpopoverDemo :: DemoProps -> JSX
819โpopoverDemo = component "PopoverDemo" \dp -> React.do
820โ visible /\ setVisible <- useState' false
823โ [ sectionTitle dp.fg "Popover"
824โ , desc dp "Click button to toggle a popover"
827โ , preferredEdge: T.bottom
828โ , behavior: T.transient
829โ , onClose: handler_ (setVisible false)
830โ , style: tw "mb-2"
833โ { title: if visible then "Hide Popover" else "Show Popover"
834โ , bezelStyle: T.push
835โ , onPress: handler_ (setVisible (not visible))
836โ , style: Style.style { height: 24.0, width: 140.0 }
838โ , view { style: tw "p-4" <> Style.style { width: 200.0, height: 80.0 } }
839โ [ text { style: tw "text-sm font-semibold" <> Style.style { color: dp.fg } } "Popover Content"
840โ , text { style: tw "text-xs mt-1" <> Style.style { color: dp.dimFg } } "Click outside to dismiss"
845โcontextMenuDemo :: DemoProps -> JSX
846โcontextMenuDemo = component "ContextMenuDemo" \dp -> React.do
847โ result /\ setResult <- useState' ""
850โ [ sectionTitle dp.fg "Context Menu"
851โ , desc dp "Right-click the area below to open a context menu"
852โ , nativeContextMenu
854โ [ { id: "cut", title: "Cut", sfSymbol: "scissors" }
855โ , { id: "copy", title: "Copy", sfSymbol: "doc.on.doc" }
856โ , { id: "paste", title: "Paste", sfSymbol: "doc.on.clipboard" }
857โ , { id: "sep", title: "-", sfSymbol: "" }
858โ , { id: "delete", title: "Delete", sfSymbol: "trash" }
859โ , { id: "selectAll", title: "Select All", sfSymbol: "selection.pin.in.out" }
861โ , onSelectItem: E.onString "itemId" setResult
862โ , style: Style.style {}
865โ [ view { style: tw "items-center justify-center" <> Style.style { minHeight: 80.0 } }
866โ [ text { style: tw "text-sm" <> Style.style { color: dp.fg } } "Right-click me!"
867โ , if result == "" then mempty
868โ else label dp.dimFg ("Selected: " <> result)
874โmenuDemo :: DemoProps -> JSX
875โmenuDemo = component "MenuDemo" \dp -> React.do
876โ result /\ setResult <- useState' ""
879โ [ sectionTitle dp.fg "Menu"
880โ , desc dp "Imperative popup menu at mouse position"
881โ , view { style: tw "flex-row items-center mb-2" }
883โ { title: "Show Menu"
884โ , bezelStyle: T.push
885โ , onPress: handler_
887โ [ { title: "New File", id: "new" }
888โ , { title: "Open...", id: "open" }
889โ , { title: "-", id: "sep" }
890โ , { title: "Save", id: "save" }
891โ , { title: "Save As...", id: "saveAs" }
893โ \itemId -> setResult ("Menu: " <> itemId)
895โ , style: Style.style { height: 24.0, width: 120.0 }
897โ , if result == "" then mempty
898โ else text { style: tw "text-xs ml-3" <> Style.style { color: dp.dimFg } } result
904โtableViewDemo :: DemoProps -> JSX
905โtableViewDemo = component "TableViewDemo" \dp -> React.do
906โ selected /\ setSelected <- useState' ""
909โ [ sectionTitle dp.fg "Table View"
910โ , desc dp "Native NSTableView with columns and rows"
913โ [ { id: "name", title: "Name", width: 150.0 }
914โ , { id: "type", title: "Type", width: 100.0 }
915โ , { id: "size", title: "Size", width: 80.0 }
918โ [ [ "Main.purs", "PureScript", "12 KB" ]
919โ , [ "App.tsx", "TypeScript", "8 KB" ]
920โ , [ "style.css", "CSS", "3 KB" ]
921โ , [ "index.html", "HTML", "1 KB" ]
922โ , [ "package.json", "JSON", "2 KB" ]
924โ , headerVisible: true
925โ , alternatingRows: true
926โ , onSelectRow: E.onInt "rowIndex" \i -> setSelected ("Row " <> show i)
927โ , onDoubleClickRow: E.onInt "rowIndex" \i -> setSelected ("Double-clicked row " <> show i)
928โ , style: Style.style { height: 180.0 } <> tw "rounded-lg overflow-hidden mb-2"
930โ , if selected == "" then mempty
931โ else label dp.dimFg selected
934โoutlineViewDemo :: DemoProps -> JSX
935โoutlineViewDemo = component "OutlineViewDemo" \dp -> React.do
936โ selection /\ setSelection <- useState' ""
939โ [ sectionTitle dp.fg "Outline View"
940โ , desc dp "Hierarchical tree list (NSOutlineView)"
941โ , nativeOutlineView
944โ file id title = OutlineItem { id, title, sfSymbol: "doc", children: [] }
945โ folder id title children = OutlineItem { id, title, sfSymbol: "folder", children }
947โ [ folder "src" "src"
948โ [ file "main" "Main.purs"
949โ , folder "macos" "MacOS"
950โ [ file "btn" "Button.purs"
951โ , file "sl" "Slider.purs"
952โ , file "sw" "Switch.purs"
955โ , folder "test" "test"
956โ [ file "t1" "MacOSComponents.test.js"
957โ , file "t2" "MacOSSnapshots.test.js"
959โ , OutlineItem { id: "pkg", title: "package.json", sfSymbol: "doc.text", children: [] }
961โ , headerVisible: false
962โ , onSelectItem: E.onString "id" setSelection
963โ , style: Style.style { height: 200.0 } <> tw "rounded-lg overflow-hidden mb-2"
965โ , if selection == "" then mempty
966โ else label dp.dimFg ("Selected: " <> selection)
969โ-- Drag & Drop / Files
971โdropZoneDemo :: DemoProps -> JSX
972โdropZoneDemo = component "DropZoneDemo" \dp -> React.do
973โ status /\ setStatus <- useState' "Drop files here"
974โ dropped /\ setDropped <- useState' ""
975โ dragging /\ setDragging <- useState' false
977โ let accentBorder = if dragging then "#007AFF" else dp.dimFg
979โ [ sectionTitle dp.fg "Drop Zone"
980โ , desc dp "Drag files from Finder into the area below"
982โ { draggedTypes: [ "NSFilenamesPboardType" ]
984โ (nativeEvent >>> unsafeEventFn \e -> getFieldJSON "dataTransfer" e)
987โ setStatus "Drop files here"
989โ , onDragEnter: handler_ do
990โ setStatus "Release to drop!"
992โ , onDragLeave: handler_ do
993โ setStatus "Drop files here"
995โ , style: tw "rounded-lg items-center justify-center mb-2"
997โ { minHeight: 100.0
998โ , borderWidth: 2.0
999โ , borderColor: accentBorder
1000โ , backgroundColor: dp.cardBg
1003โ [ text { style: tw "text-sm" <> Style.style { color: dp.fg } } status
1004โ , if dropped == "" then mempty
1005โ else text { style: tw "text-xs mt-2 px-4" <> Style.style { color: dp.dimFg } }
1006โ ("Dropped: " <> dropped)
1010โfilePickerDemo :: DemoProps -> JSX
1011โfilePickerDemo = component "FilePickerDemo" \dp -> React.do
1012โ picked /\ setPicked <- useState' ""
1015โ [ sectionTitle dp.fg "File Picker"
1016โ , desc dp "Click buttons to open native file panels"
1018โ [ view { style: tw "flex-row items-center" }
1019โ [ nativeFilePicker
1020โ { mode: T.openFile
1021โ , title: "Open Files"
1022โ , sfSymbol: "doc.badge.plus"
1023โ , allowMultiple: true
1024โ , allowedTypes: [ "public.image", "public.text" ]
1025โ , message: "Select files to open"
1026โ , onPickFiles: handler
1027โ (nativeEvent >>> unsafeEventFn \e -> getFieldJSON "files" e)
1029โ , onCancel: handler_ (setPicked "Cancelled")
1030โ , style: Style.style { height: 24.0, width: 120.0 }
1032โ , nativeFilePicker
1033โ { mode: T.openFile
1034โ , title: "Choose Folder"
1035โ , sfSymbol: "folder"
1036โ , canChooseDirectories: true
1037โ , message: "Select a folder"
1038โ , onPickFiles: handler
1039โ (nativeEvent >>> unsafeEventFn \e -> getFieldJSON "files" e)
1041โ , onCancel: handler_ (setPicked "Cancelled")
1042โ , style: Style.style { height: 24.0, width: 140.0, marginLeft: 8.0 }
1044โ , nativeFilePicker
1045โ { mode: T.saveFile
1046โ , title: "Save As..."
1047โ , sfSymbol: "square.and.arrow.down"
1048โ , defaultName: "Untitled.txt"
1049โ , allowedTypes: [ "public.plain-text" ]
1050โ , message: "Choose save location"
1051โ , onPickFiles: handler
1052โ (nativeEvent >>> unsafeEventFn \e -> getFieldJSON "files" e)
1054โ , onCancel: handler_ (setPicked "Cancelled")
1055โ , style: Style.style { height: 24.0, width: 120.0, marginLeft: 8.0 }
1058โ , if picked == "" then mempty
1059โ else label dp.dimFg picked
1065โriveDemo :: DemoProps -> JSX
1066โriveDemo = component "RiveDemo" \dp -> React.do
1068โ nativeScrollView { style: tw "flex-1" <> Style.style { backgroundColor: "transparent" } }
1069โ ( view { style: tw "px-4 pb-4" }
1070โ [ sectionTitle dp.fg "Mouse Tracking"
1071โ , text { style: tw "text-xs mb-2" <> Style.style { color: dp.fg } }
1072โ "Move your cursor over the cat โ it follows your mouse!"
1073โ , nativeRiveView_
1074โ { resourceName: "cat_following_mouse"
1075โ , stateMachineName: "State Machine 1"
1078โ , style: Style.style { height: 300.0 }
1080โ , sectionTitle dp.fg "Cursor Tracking"
1081โ , text { style: tw "text-xs mb-2" <> Style.style { color: dp.fg } }
1082โ "Shapes follow your cursor via Pointer Move listeners"
1083โ , nativeRiveView_
1084โ { resourceName: "cursor_tracking"
1085โ , stateMachineName: "State Machine 1"
1088โ , style: Style.style { height: 300.0 }
1090โ , sectionTitle dp.fg "Interactive Rive Animations"
1091โ , text { style: tw "text-xs mb-2" <> Style.style { color: dp.fg } }
1092โ "Click the stars to rate! State machine handles interaction."
1093โ , nativeRiveView_
1094โ { resourceName: "rating_animation"
1095โ , stateMachineName: "State Machine 1"
1098โ , style: Style.style { height: 200.0 }
1100โ , sectionTitle dp.fg "Light Switch"
1101โ , text { style: tw "text-xs mb-2" <> Style.style { color: dp.fg } }
1102โ "Click to toggle"
1103โ , nativeRiveView_
1104โ { resourceName: "switch_event_example"
1105โ , stateMachineName: "State Machine 1"
1108โ , style: Style.style { height: 200.0 }
1110โ , sectionTitle dp.fg "Windy Tree"
1111โ , nativeRiveView_
1112โ { resourceName: "windy_tree"
1115โ , style: tw "flex-1" <> Style.style { minHeight: 300.0, backgroundColor: dp.bg }
1120โ-- System Services
1122โclipboardDemo :: DemoProps -> JSX
1123โclipboardDemo = component "ClipboardDemo" \dp -> React.do
1126โ [ sectionTitle dp.fg "Clipboard"
1127โ , desc dp "Read and write the system clipboard"
1128โ , view { style: tw "flex-row items-center mb-2" }
1130โ { title: "Copy 'Hello'"
1131โ , bezelStyle: T.push
1132โ , onPress: handler_ (copyToClipboard "Hello from PureScript!")
1133โ , style: Style.style { height: 24.0, width: 120.0 }
1138โshareDemo :: DemoProps -> JSX
1139โshareDemo = component "ShareDemo" \dp -> React.do
1142โ [ sectionTitle dp.fg "Share"
1143โ , desc dp "System share picker"
1145โ { title: "Share Text"
1146โ , sfSymbol: "square.and.arrow.up"
1147โ , bezelStyle: T.push
1148โ , onPress: handler_ (macosShare [ "Hello from PureScript React Native!" ])
1149โ , style: Style.style { height: 24.0, width: 120.0 } <> tw "mb-2"
1153โnotificationsDemo :: DemoProps -> JSX
1154โnotificationsDemo = component "NotificationsDemo" \dp -> React.do
1157โ [ sectionTitle dp.fg "Notifications"
1158โ , desc dp "System notifications (requires permission)"
1160โ { title: "Send Notification"
1161โ , sfSymbol: "bell"
1162โ , bezelStyle: T.push
1163โ , onPress: handler_ (macosNotify "PureScript" "Hello from React Native macOS!")
1164โ , style: Style.style { height: 24.0, width: 160.0 } <> tw "mb-2"
1168โsoundDemo :: DemoProps -> JSX
1169โsoundDemo = component "SoundDemo" \dp -> React.do
1172โ [ sectionTitle dp.fg "Sound"
1173โ , desc dp "Play system sounds"
1174โ , view { style: tw "flex-row items-center mb-2" }
1177โ , bezelStyle: T.push
1178โ , onPress: handler_ (macosPlaySound "Glass")
1179โ , style: Style.style { height: 24.0, width: 80.0 }
1183โ , bezelStyle: T.push
1184โ , onPress: handler_ (macosPlaySound "Ping")
1185โ , style: Style.style { height: 24.0, width: 80.0, marginLeft: 8.0 }
1189โ , bezelStyle: T.push
1190โ , onPress: handler_ macosBeep
1191โ , style: Style.style { height: 24.0, width: 80.0, marginLeft: 8.0 }
1196โstatusBarDemo :: DemoProps -> JSX
1197โstatusBarDemo = component "StatusBarDemo" \dp -> React.do
1198โ active /\ setActive <- useState' false
1201โ [ sectionTitle dp.fg "Status Bar"
1202โ , desc dp "Menu bar icon with dropdown"
1203โ , view { style: tw "flex-row items-center mb-4" }
1205โ { title: if active then "Remove" else "Add to Menu Bar"
1206โ , sfSymbol: if active then "minus.circle" else "plus.circle"
1207โ , bezelStyle: T.push
1208โ , onPress: handler_
1209โ ( if active then do
1210โ macosRemoveStatusBarItem
1213โ macosSetStatusBarItem
1215โ , sfSymbol: "swift"
1217โ [ { id: "about", title: "About PureScript RN" }
1218โ , { id: "sep", title: "-" }
1219โ , { id: "quit", title: "Quit" }
1224โ , style: Style.style { height: 24.0, width: 180.0 }
1229โquickLookDemo :: DemoProps -> JSX
1230โquickLookDemo = component "QuickLookDemo" \dp -> React.do
1233โ [ sectionTitle dp.fg "Quick Look"
1234โ , desc dp "Preview files with QLPreviewPanel"
1235โ , view { style: tw "flex-row items-center mb-4" }
1237โ { title: "Preview /etc/hosts"
1238โ , sfSymbol: "eye"
1239โ , bezelStyle: T.push
1240โ , onPress: handler_ (macosQuickLook "/etc/hosts")
1241โ , style: Style.style { height: 24.0, width: 180.0 }
1246โcolorPanelDemo :: DemoProps -> JSX
1247โcolorPanelDemo = component "ColorPanelDemo" \dp -> React.do
1250โ [ sectionTitle dp.fg "Color Picker"
1251โ , desc dp "System color panel (NSColorPanel)"
1252โ , view { style: tw "flex-row items-center mb-4" }
1254โ { title: "Show Color Panel"
1255โ , sfSymbol: "paintpalette"
1256โ , bezelStyle: T.push
1257โ , onPress: handler_ macosShowColorPanel
1258โ , style: Style.style { height: 24.0, width: 180.0 }
1263โfontPanelDemo :: DemoProps -> JSX
1264โfontPanelDemo = component "FontPanelDemo" \dp -> React.do
1267โ [ sectionTitle dp.fg "Font Panel"
1268โ , desc dp "System font panel (NSFontPanel)"
1269โ , view { style: tw "flex-row items-center mb-4" }
1271โ { title: "Show Font Panel"
1272โ , sfSymbol: "textformat"
1273โ , bezelStyle: T.push
1274โ , onPress: handler_ macosShowFontPanel
1275โ , style: Style.style { height: 24.0, width: 180.0 }
1280โspeechDemo :: DemoProps -> JSX
1281โspeechDemo = component "SpeechDemo" \dp -> React.do
1282โ txt /\ setTxt <- useState' "Hello from PureScript React Native on macOS!"
1285โ [ sectionTitle dp.fg "Speech Synthesis"
1286โ , desc dp "Text-to-speech (NSSpeechSynthesizer)"
1287โ , nativeTextField
1289โ , placeholder: "Text to speak..."
1290โ , onChangeText: E.onString "text" setTxt
1291โ , style: Style.style { height: 24.0 } <> tw "mb-2"
1293โ , view { style: tw "flex-row items-center mb-4" }
1296โ , sfSymbol: "speaker.wave.2"
1297โ , bezelStyle: T.push
1298โ , onPress: handler_ (say txt)
1299โ , style: Style.style { height: 24.0, width: 100.0 }
1303โ , sfSymbol: "stop.circle"
1304โ , bezelStyle: T.push
1305โ , onPress: handler_ stopSpeaking
1306โ , style: Style.style { height: 24.0, width: 100.0, marginLeft: 8.0 }
1313โocrDemo :: DemoProps -> JSX
1314โocrDemo = component "OCRDemo" \dp -> React.do
1315โ result /\ setResult <- useState' ""
1318โ [ sectionTitle dp.fg "OCR (Vision)"
1319โ , desc dp "Recognize text in images (VNRecognizeTextRequest)"
1320โ , view { style: tw "flex-row items-center mb-2" }
1321โ [ nativeFilePicker
1322โ { mode: T.openFile
1323โ , allowedTypes: [ "public.image" ]
1324โ , title: "Pick Image"
1325โ , sfSymbol: "photo"
1326โ , onPickFiles: handler
1327โ (nativeEvent >>> unsafeEventFn \e -> E.getFieldArray "files" e)
1328โ \paths -> for_ paths \path -> do
1329โ setResult "Recognizing..."
1331โ r <- recognizeText path
1332โ liftEffect (setResult r)
1333โ , style: Style.style { height: 24.0, width: 140.0 }
1336โ , if result /= "" then view { style: tw "p-2 rounded mb-4" <> Style.style { backgroundColor: dp.cardBg } }
1337โ [ text { style: tw "text-xs font-mono" <> Style.style { color: dp.fg } } result ]
1341โspeechRecognitionDemo :: DemoProps -> JSX
1342โspeechRecognitionDemo = component "SpeechRecognitionDemo" \dp -> React.do
1343โ speech <- useSpeechRecognition
1346โ [ sectionTitle dp.fg "Speech Recognition"
1347โ , desc dp "Live microphone-to-text (click Start, speak, click Stop)"
1348โ , view { style: tw "flex-row items-center mb-2" }
1350โ { title: if speech.listening then "Listening..." else "Start"
1351โ , sfSymbol: if speech.listening then "mic.fill" else "mic"
1352โ , bezelStyle: T.push
1353โ , onPress: handler_ speech.start
1354โ , style: Style.style { height: 24.0, width: 130.0 }
1358โ , sfSymbol: "stop.circle"
1359โ , bezelStyle: T.push
1360โ , onPress: handler_ speech.stop
1361โ , style: Style.style { height: 24.0, width: 80.0, marginLeft: 8.0 }
1364โ , if speech.transcript /= "" then view { style: tw "p-2 rounded mb-4" <> Style.style { backgroundColor: dp.cardBg } }
1365โ [ text { style: tw "text-xs font-mono" <> Style.style { color: dp.fg } } speech.transcript ]
1369โnaturalLanguageDemo :: DemoProps -> JSX
1370โnaturalLanguageDemo = component "NaturalLanguageDemo" \dp -> React.do
1371โ txt /\ setTxt <- useState' "I love this amazing app! C'est magnifique."
1372โ result /\ setResult <- useState' ""
1375โ [ sectionTitle dp.fg "Natural Language"
1376โ , desc dp "Language detection, sentiment, tokenization (NaturalLanguage.framework)"
1377โ , nativeTextField
1379โ , placeholder: "Enter text to analyze..."
1380โ , onChangeText: E.onString "text" setTxt
1381โ , style: Style.style { height: 24.0 } <> tw "mb-2"
1383โ , view { style: tw "flex-row items-center mb-2" }
1385โ { title: "Language"
1386โ , sfSymbol: "globe"
1387โ , bezelStyle: T.push
1388โ , onPress: handler_ $ launchAff_ do
1389โ lang <- detectLanguage txt
1390โ liftEffect (setResult ("Language: " <> lang))
1391โ , style: Style.style { height: 24.0, width: 110.0 }
1394โ { title: "Sentiment"
1395โ , sfSymbol: "face.smiling"
1396โ , bezelStyle: T.push
1397โ , onPress: handler_ $ launchAff_ do
1398โ score <- analyzeSentiment txt
1399โ liftEffect (setResult ("Sentiment: " <> show score))
1400โ , style: Style.style { height: 24.0, width: 110.0, marginLeft: 8.0 }
1403โ { title: "Tokenize"
1404โ , sfSymbol: "text.word.spacing"
1405โ , bezelStyle: T.push
1406โ , onPress: handler_ $ launchAff_ do
1407โ tokens <- tokenize txt
1408โ liftEffect (setResult ("Tokens: " <> joinWith ", " tokens))
1409โ , style: Style.style { height: 24.0, width: 110.0, marginLeft: 8.0 }
1412โ , if result /= "" then view { style: tw "p-2 rounded mb-4" <> Style.style { backgroundColor: dp.cardBg } }
1413โ [ text { style: tw "text-xs font-mono" <> Style.style { color: dp.fg } } result ]
1417โcameraDemo :: DemoProps -> JSX
1418โcameraDemo = component "CameraDemo" \dp -> React.do
1419โ on /\ setOn <- useState' false
1422โ [ sectionTitle dp.fg "Camera"
1423โ , desc dp "Live camera preview (AVCaptureVideoPreviewLayer)"
1425โ { title: if on then "Stop Camera" else "Start Camera"
1426โ , sfSymbol: if on then "video.slash" else "video"
1427โ , bezelStyle: T.push
1428โ , onPress: handler_ (setOn (not on))
1429โ , style: Style.style { height: 24.0, width: 140.0 } <> tw "mb-2"
1431โ , if on then nativeCameraView
1433โ , style: Style.style { height: 240.0 } <> tw "rounded-lg overflow-hidden mb-2"
1438โ-- Maps & Documents
1440โmapViewDemo :: DemoProps -> JSX
1441โmapViewDemo = component "MapViewDemo" \dp -> React.do
1444โ [ sectionTitle dp.fg "Map View"
1445โ , desc dp "Embedded MKMapView (San Francisco)"
1447โ { latitude: 37.7749
1448โ , longitude: -122.4194
1449โ , latitudeDelta: 0.05
1450โ , longitudeDelta: 0.05
1451โ , mapType: T.standardMap
1452โ , showsUserLocation: false
1454โ [ { latitude: 37.7749, longitude: -122.4194, title: "San Francisco", subtitle: "California" }
1455โ , { latitude: 37.8199, longitude: -122.4783, title: "Golden Gate Bridge", subtitle: "" }
1457โ , style: Style.style { height: 250.0 }
1461โpdfViewDemo :: DemoProps -> JSX
1462โpdfViewDemo = component "PDFViewDemo" \dp -> React.do
1465โ [ sectionTitle dp.fg "PDF Viewer"
1466โ , desc dp "PDFKit document viewer"
1468โ { source: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
1469โ , autoScales: true
1470โ , displayMode: T.singlePageContinuous
1471โ , style: Style.style { height: 300.0 }
1477โwebViewDemo :: DemoProps -> JSX
1478โwebViewDemo = component "WebViewDemo" \dp -> React.do
1479โ url /\ setUrl <- useState' "https://pursuit.purescript.org"
1480โ urlBar /\ setUrlBar <- useState' "https://pursuit.purescript.org"
1482โ view { style: tw "flex-1 px-4" }
1483โ [ sectionTitle dp.fg "Web Browser"
1484โ , nativeTextField
1486โ , placeholder: "Enter URL..."
1488โ , onChangeText: E.onString "text" setUrlBar
1489โ , onSubmit: E.onString "text" setUrl
1490โ , style: Style.style { height: 24.0, marginBottom: 8.0 }
1494โ , onFinishLoad: handler
1495โ ( nativeEvent >>> unsafeEventFn \e ->
1496โ { url: E.getFieldStr "url" e, title: E.getFieldStr "title" e }
1498โ \r -> setUrlBar r.url
1499โ , style: tw "flex-1" <> Style.style { minHeight: 400.0 }
1505โvideoPlayerDemo :: DemoProps -> JSX
1506โvideoPlayerDemo = component "VideoPlayerDemo" \dp -> React.do
1507โ playing /\ setPlaying <- useState' true
1510โ [ sectionTitle dp.fg "Video Player"
1511โ , desc dp "Native AVPlayerView with floating controls"
1512โ , nativeVideoPlayer
1513โ { source: "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/720/Big_Buck_Bunny_720_10s_1MB.mp4"
1517โ , style: Style.style { height: 240.0 } <> tw "rounded-lg overflow-hidden mb-2"
1519โ , view { style: tw "flex-row mb-2" }
1521โ { title: if playing then "Pause" else "Play"
1522โ , sfSymbol: if playing then "pause.fill" else "play.fill"
1523โ , bezelStyle: T.push
1524โ , onPress: handler_ (setPlaying (not playing))
1525โ , style: Style.style { height: 24.0, width: 100.0 }
1533โ { sender :: String
1536โ , eventId :: String
1540โ { homeserver :: String
1541โ , accessToken :: String
1542โ , userId :: String
1546โ { roomId :: String
1548โ , lastMessage :: String
1551โavatarColors :: Array String
1552โavatarColors = [ "#5856D6", "#FF9500", "#34C759", "#FF2D55", "#5AC8FA", "#FF6B6B", "#4ECDC4", "#45B7D1" ]
1554โavatarColor :: String -> String
1555โavatarColor roomId = fromMaybe "#5856D6" (avatarColors !! (mod (hashCode roomId) (length avatarColors)))
1557โ hashCode s = abs (foldl (\acc c -> acc * 31 + c) 0 (charCodes s))
1559โforeign import charCodes :: String -> Array Int
1560โforeign import abs :: Int -> Int
1562โmatrixDemo :: DemoProps -> JSX
1563โmatrixDemo = component "MatrixDemo" \dp -> React.do
1564โ session /\ setSession <- useState' (Nothing :: Maybe Session)
1565โ rooms /\ setRooms <- useState' ([] :: Array Room)
1566โ activeRoom /\ setActiveRoom <- useState' (Nothing :: Maybe String)
1567โ messages /\ setMessages <- useState ([] :: Array Message)
1568โ inputText /\ setInputText <- useState' ""
1569โ loginError /\ setLoginError <- useState' ""
1570โ loginServer /\ setLoginServer <- useState' "https://matrix.org"
1571โ loginUser /\ setLoginUser <- useState' ""
1572โ loginPass /\ setLoginPass <- useState' ""
1573โ syncTokenRef <- useRef ""
1574โ activeRoomRef <- useRef (Nothing :: Maybe String)
1575โ sessionRef <- useRef (Nothing :: Maybe Session)
1576โ seenIdsRef <- useRef ([] :: Array String)
1578โ useEffect session do
1580โ Nothing -> pure mempty
1582โ writeRef sessionRef (Just sess)
1584โ doSync = launchAff_ do
1585โ token <- readRef syncTokenRef # liftEffect
1586โ result <- try (Matrix.sync sess.homeserver sess.accessToken token 5000)
1588โ Left _ -> pure unit
1589โ Right resp -> liftEffect do
1590โ let nextBatch = Matrix.getSyncNextBatch resp
1591โ writeRef syncTokenRef nextBatch
1592โ let roomIds = Matrix.getSyncJoinedRoomIds resp
1593โ currentRoom <- readRef activeRoomRef
1594โ for_ roomIds \rid -> do
1595โ let events = Matrix.getSyncTimelineEvents resp rid
1596โ let msgEvents = filter (\ev -> Matrix.getEventType ev == "m.room.message") events
1597โ case currentRoom of
1598โ Just cr | cr == rid -> do
1599โ seen <- readRef seenIdsRef
1600โ let newMsgs = filter (\ev -> not (Matrix.getEventId ev `elem` seen)) msgEvents
1602โ converted = newMsgs <#> \ev ->
1603โ { sender: Matrix.getEventSender ev
1604โ , body: Matrix.getEventBody ev
1605โ , kind: eventKind ev
1606โ , eventId: Matrix.getEventId ev
1608โ if null converted then pure unit
1610โ let newIds = converted <#> _.eventId
1611โ writeRef seenIdsRef (seen <> newIds)
1612โ setMessages \msgs -> msgs <> converted
1614โ timerId <- setInterval 3000 doSync
1616โ pure (clearInterval timerId)
1619โ doLogin = launchAff_ do
1620โ result <- try (Matrix.login loginServer loginUser loginPass)
1622โ Left _ -> liftEffect (setLoginError "Login failed. Check credentials.")
1624โ let sess = { homeserver: loginServer, accessToken: resp.access_token, userId: resp.user_id }
1625โ roomIds <- try (Matrix.joinedRooms loginServer resp.access_token)
1627โ Left _ -> liftEffect do
1628โ setSession (Just sess)
1631โ names <- for rids \rid -> do
1632โ n <- try (Matrix.roomName loginServer resp.access_token rid)
1638โ pure { roomId: rid, name, lastMessage: "" }
1640โ setSession (Just sess)
1644โ selectRoom sess rid = do
1645โ setActiveRoom (Just rid)
1646โ writeRef activeRoomRef (Just rid)
1647โ setMessages (const [])
1648โ writeRef seenIdsRef []
1650โ result <- try (Matrix.sync sess.homeserver sess.accessToken "" 0)
1652โ Left _ -> pure unit
1653โ Right resp -> liftEffect do
1654โ let events = Matrix.getSyncTimelineEvents resp rid
1655โ let msgEvents = filter (\ev -> Matrix.getEventType ev == "m.room.message") events
1657โ converted = msgEvents <#> \ev ->
1658โ { sender: Matrix.getEventSender ev
1659โ , body: Matrix.getEventBody ev
1660โ , kind: eventKind ev
1661โ , eventId: Matrix.getEventId ev
1663โ let ids = converted <#> _.eventId
1664โ writeRef seenIdsRef ids
1665โ setMessages (const converted)
1667โ sendMessage sess rid msg = do
1668โ if msg == "" then pure unit
1672โ txnId <- Matrix.genTxnId # liftEffect
1673โ _ <- try (Matrix.sendMessage sess.homeserver sess.accessToken rid txnId msg)
1676โ sentBubbleBg = if dp.isDark then "#65B86A" else "#5CB85C"
1677โ receivedBubbleBg = if dp.isDark then "#3B3B3D" else "#FFFFFF"
1678โ chatBg = if dp.isDark then "#17212B" else "#E8DFD3"
1681โ { style: tw "flex-1 items-center justify-center"
1682โ <> Style.style { backgroundColor: chatBg }
1684โ [ view { style: tw "rounded-xl p-6" <> Style.style { backgroundColor: dp.cardBg, width: 340.0 } }
1685โ [ text { style: tw "text-lg font-bold mb-4 text-center" <> Style.style { color: dp.fg } } "Matrix Login"
1686โ , text { style: tw "text-xs mb-1" <> Style.style { color: dp.dimFg } } "Homeserver"
1687โ , nativeTextField
1688โ { text: loginServer
1689โ , placeholder: "https://matrix.org"
1692โ , onChangeText: E.onString "text" setLoginServer
1693โ , onSubmit: handler_ (pure unit)
1694โ , style: tw "mb-3" <> Style.style { height: 28.0 }
1696โ , text { style: tw "text-xs mb-1" <> Style.style { color: dp.dimFg } } "Username"
1697โ , nativeTextField
1698โ { text: loginUser
1699โ , placeholder: "username"
1702โ , onChangeText: E.onString "text" setLoginUser
1703โ , onSubmit: handler_ (pure unit)
1704โ , style: tw "mb-3" <> Style.style { height: 28.0 }
1706โ , text { style: tw "text-xs mb-1" <> Style.style { color: dp.dimFg } } "Password"
1707โ , nativeTextField
1708โ { text: loginPass
1709โ , placeholder: "password"
1712โ , onChangeText: E.onString "text" setLoginPass
1713โ , onSubmit: handler_ doLogin
1714โ , style: tw "mb-4" <> Style.style { height: 28.0 }
1716โ , if loginError /= "" then
1717โ text { style: tw "text-xs mb-2 text-center" <> Style.style { color: "#FF3B30" } } loginError
1718โ else text { style: Style.style { height: 0.0 } } ""
1720โ { title: "Sign In"
1721โ , bezelStyle: T.push
1722โ , sfSymbol: "arrow.right.circle.fill"
1723โ , onPress: handler_ doLogin
1724โ , style: Style.style { height: 32.0 }
1729โ roomSidebar sess = view { style: tw "pt-2" }
1730โ ( rooms <#> \r ->
1732โ { style: tw "flex-row items-center px-3 py-2 mx-2 rounded-lg"
1734โ { backgroundColor: if activeRoom == Just r.roomId then (if dp.isDark then "#2B5278" else "#419FD9") else "transparent" }
1737โ { style: tw "rounded-full items-center justify-center"
1738โ <> Style.style { width: 32.0, height: 32.0, backgroundColor: avatarColor r.roomId }
1740โ [ text { style: tw "text-xs font-bold" <> Style.style { color: "#FFFFFF" } } (take 1 r.name) ]
1741โ , view { style: tw "ml-2 flex-1" }
1743โ { style: tw "text-sm font-semibold"
1744โ <> Style.style { color: if activeRoom == Just r.roomId then "#FFFFFF" else dp.fg }
1748โ { style: tw "text-xs"
1749โ <> Style.style { color: if activeRoom == Just r.roomId then "#FFFFFFCC" else dp.dimFg }
1755โ , bezelStyle: T.toolbar
1757โ , onPress: handler_ (selectRoom sess r.roomId)
1758โ , style: Style.style { position: "absolute", top: 0.0, left: 0.0, right: 0.0, bottom: 0.0, opacity: 0.0 }
1763โ messageBubble sess _ msg = do
1765โ isMine = case sess of
1766โ Just s -> msg.sender == s.userId
1768โ let align = if isMine then "flex-end" else "flex-start"
1769โ let bigEmoji = msg.kind == "text" && isSingleEmoji msg.body
1770โ let senderLabel = if isMine then "" else msg.sender
1771โ nativeContextMenu
1773โ [ { id: "copy", title: "Copy", sfSymbol: "doc.on.doc" }
1774โ , { id: "reply", title: "Reply", sfSymbol: "arrowshape.turn.up.left" }
1775โ , { id: "sep", title: "-", sfSymbol: "" }
1776โ , { id: "delete", title: "Delete", sfSymbol: "trash" }
1778โ , onSelectItem: handler_ (pure unit)
1779โ , style: tw "px-3 mb-1"
1781โ ( view { style: Style.style { alignSelf: align, maxWidth: 320.0 } }
1782โ [ if senderLabel == "" then text { style: Style.style { height: 0.0 } } ""
1783โ else text { style: tw "text-xs mb-0.5" <> Style.style { color: dp.dimFg } } senderLabel
1784โ , if bigEmoji then
1786โ { style: Style.style { fontSize: 64.0, lineHeight: 72.0 } }
1790โ { style: tw "rounded-2xl px-3 py-2"
1792โ { backgroundColor: if isMine then sentBubbleBg else receivedBubbleBg }
1795โ { style: tw "text-sm"
1796โ <> Style.style { color: if isMine then "#FFFFFF" else (if dp.isDark then "#FFFFFF" else "#000000") }
1803โ activeRoomName = case activeRoom of
1804โ Nothing -> "Select a room"
1805โ Just rid -> fromMaybe rid (map _.name (filter (\r -> r.roomId == rid) rooms !! 0))
1807โ chatView sess = sidebarLayout
1808โ { sidebar: roomSidebar sess
1809โ , sidebarWidth: 220.0
1810โ , content: view { style: tw "flex-1" <> Style.style { backgroundColor: "transparent" } }
1812โ { style: tw "px-4 py-2 border-b"
1813โ <> Style.style { borderBottomWidth: 0.5, borderColor: dp.dimFg, backgroundColor: "transparent" }
1815โ [ text { style: tw "text-base font-semibold" <> Style.style { color: dp.fg } } activeRoomName ]
1816โ , nativePatternBackground
1817โ { patternColor: if dp.isDark then "#FFFFFF" else "#000000"
1818โ , background: chatBg
1819โ , patternOpacity: if dp.isDark then 0.04 else 0.06
1820โ , patternScale: 1.0
1821โ , style: tw "flex-1"
1823โ ( nativeScrollView { scrollToBottom: length messages, style: tw "flex-1" <> Style.style { backgroundColor: "transparent" } }
1824โ ( view { style: tw "py-2 pr-3" }
1825โ (mapWithIndex (messageBubble session) messages)
1829โ { style: tw "flex-row items-center px-3 py-2"
1830โ <> Style.style { borderTopWidth: 0.5, borderColor: dp.dimFg, backgroundColor: "transparent" }
1832โ ( case activeRoom of
1834โ [ text { style: tw "text-sm" <> Style.style { color: dp.dimFg } } "Select a room to start chatting" ]
1836โ [ nativeTextField
1837โ { text: inputText
1838โ , placeholder: "Message..."
1841โ , onChangeText: E.onString "text" setInputText
1842โ , onSubmit: E.onString "text" \t -> sendMessage sess rid t
1843โ , style: tw "flex-1" <> Style.style { height: 28.0 }
1847โ , sfSymbol: "paperplane.fill"
1848โ , bezelStyle: T.toolbar
1849โ , onPress: handler_ (sendMessage sess rid inputText)
1850โ , style: Style.style { height: 28.0, width: 36.0, marginLeft: 8.0 }
1859โ Nothing -> loginForm
1860โ Just sess -> chatView sess
1862โeventKind :: Matrix.MatrixEvent -> String
1863โeventKind ev = case Matrix.getEventMsgType ev of
1864โ "m.image" -> "gif"
1867โelem :: forall a. Eq a => a -> Array a -> Boolean
1868โelem x xs = not (null (filter (_ == x) xs))
1870โforeign import setInterval :: Int -> Effect Unit -> Effect Int
1871โforeign import clearInterval :: Int -> Effect Unit
1875โscrollWrap :: DemoProps -> Array JSX -> JSX
1876โscrollWrap dp children =
1877โ nativeScrollView { style: tw "flex-1" <> Style.style { backgroundColor: "transparent" } }
1878โ (view { style: tw "px-4 pb-4" } children)
1880โsectionTitle :: String -> String -> JSX
1881โsectionTitle color title =
1882โ text { style: tw "text-sm font-semibold mt-4 mb-1" <> Style.style { color } } title
1884โdesc :: DemoProps -> String -> JSX
1886โ text { style: tw "text-xs mb-2" <> Style.style { color: dp.dimFg } } str
1888โcard :: String -> Array JSX -> JSX
1889โcard bg children =
1890โ view { style: tw "rounded-lg p-3 mb-2" <> Style.style { backgroundColor: bg, overflow: "hidden" } }
1893โlabel :: String -> String -> JSX
1895โ text { style: tw "ml-3 text-sm" <> Style.style { color } } str
1897โround :: Number -> Int
1898โround n = unsafeRound n
1900โforeign import unsafeRound :: Number -> Int
1901โforeign import getFieldJSON :: String -> forall r. r -> String
1902โforeign import isSingleEmoji :: String -> Boolean