Repository: shoutem/shoutem.github.io Branch: master Commit: 0b5e153a0a1e Files: 136 Total size: 1.0 MB Directory structure: gitextract_rdoazj5u/ ├── .gitignore ├── Gemfile ├── README.md ├── _config.yml ├── _includes/ │ ├── cards.html │ ├── footer.html │ ├── ga.html │ ├── head.html │ ├── home-header-video.html │ ├── navbar.html │ ├── overview-content.html │ ├── sidebar-nav.html │ └── signup-modal.html ├── _layouts/ │ ├── doc.html │ ├── home.html │ └── overview.html ├── _sass/ │ ├── animation.scss │ ├── base.scss │ ├── cards.scss │ ├── colors.scss │ ├── documentation-layout.scss │ ├── footer.scss │ ├── github.scss │ ├── helpers.scss │ ├── menu-overlay.scss │ ├── mixins.scss │ ├── navbar.scss │ ├── pager.scss │ ├── sidebar.scss │ ├── typography.scss │ └── variables.scss ├── css/ │ ├── documentation.scss │ ├── home.scss │ ├── prism.css │ └── style.scss ├── csv/ │ └── restaurants.csv ├── docs/ │ ├── cloud/ │ │ └── _posts/ │ │ ├── 1970-01-01-ShoutemCloud.md │ │ └── 1970-01-02-DataSchemas.md │ ├── extensions/ │ │ ├── my-first-extension/ │ │ │ └── _posts/ │ │ │ ├── 1970-01-01-Introduction.md │ │ │ ├── 1970-01-03-InitializingExtension.md │ │ │ ├── 1970-01-04-CreatingShortcutAndScreen.md │ │ │ ├── 1970-01-05-UsingUIToolkit.md │ │ │ ├── 1970-01-06-UsingCloudStorage.md │ │ │ ├── 1970-01-07-WorkingWithData.md │ │ │ └── 1970-01-08-Publish.md │ │ ├── reference/ │ │ │ └── _posts/ │ │ │ ├── 1970-01-01-ExtensionFile.md │ │ │ ├── 1970-01-01-Overview.md │ │ │ ├── 1970-01-02-Platform.md │ │ │ ├── 1970-01-03-ExtensionExports.md │ │ │ ├── 1970-01-04-SettingsTypesInExtension.md │ │ │ ├── 1970-01-05-ThemeVariables.md │ │ │ └── 1970-01-06-CLI.md │ │ └── tutorials/ │ │ └── _posts/ │ │ ├── 1970-01-01-ConnectToApi.md │ │ ├── 1970-01-01-GettingStarted.md │ │ ├── 1970-01-01-PublishYourApp.md │ │ ├── 1970-01-01-SettingLocalEnvironment.md │ │ ├── 1970-01-02-WritingATheme.md │ │ ├── 1970-01-03-ScreenLayouts.md │ │ ├── 1970-01-04-WritingReactSettingsPage.md │ │ ├── 1970-01-05-Installing3rdPartyPackages.md │ │ ├── 1970-01-06-UsingNativeModules.md │ │ ├── 1970-01-07-ModifyingNativeProject.md │ │ ├── 1970-01-08-ModifiyingExtensions.md │ │ ├── 1970-01-09-FAQ.md │ │ ├── 1970-01-10-DebugSettingsPages.md │ │ ├── 1970-01-11-WritingHTMLSettingsPages.md │ │ ├── 1970-01-12-SettingsPageIntro.md │ │ ├── 1970-01-13-UsingLocalization.md │ │ ├── 1970-01-14-SettingUpInstagram.md │ │ ├── 1970-01-15-UsingPatchPackage.md │ │ ├── 1970-01-16-NavigationIntroduction.md │ │ ├── 1970-01-17-NavigationBreakingChanges.md │ │ ├── 1970-01-18-NavigationStacks.md │ │ └── 1970-01-19-NavigationScreenDecorators.md │ └── ui-toolkit/ │ ├── animation/ │ │ └── _posts/ │ │ ├── 1970-01-02-Driver.md │ │ ├── 1970-01-03-Animations.md │ │ ├── 1970-01-03-FadeIn.md │ │ ├── 1970-01-04-FadeOut.md │ │ ├── 1970-01-05-ZoomIn.md │ │ ├── 1970-01-06-ZoomOut.md │ │ ├── 1970-01-07-Parallax.md │ │ └── 1970-01-08-CombiningAnimations.md │ ├── components/ │ │ └── _posts/ │ │ ├── 1970-01-01-Introduction.md │ │ ├── 1970-01-02-Typography.md │ │ ├── 1970-01-03-NavigationBar.md │ │ ├── 1970-01-04-DropDownMenu.md │ │ ├── 1970-01-05-ListView.md │ │ ├── 1970-01-06-GridView.md │ │ ├── 1970-01-07-Cards.md │ │ ├── 1970-01-08-Dividers.md │ │ ├── 1970-01-09-Rows.md │ │ ├── 1970-01-10-Tiles.md │ │ ├── 1970-01-11-Spinner.md │ │ ├── 1970-01-12-Buttons.md │ │ ├── 1970-01-13-Image.md │ │ ├── 1970-01-14-Icons.md │ │ ├── 1970-01-15-View.md │ │ ├── 1970-01-16-Screen.md │ │ ├── 1970-01-17-TouchableOpacity.md │ │ ├── 1970-01-18-Headers.md │ │ ├── 1970-01-19-Overlay.md │ │ ├── 1970-01-20-Video.md │ │ ├── 1970-01-21-Lightbox.md │ │ ├── 1970-01-22-RichMedia.md │ │ ├── 1970-01-22-SimpleHtml.md │ │ ├── 1970-01-23-TextInput.md │ │ ├── 1970-01-24-ImagePreview.md │ │ ├── 1970-01-25-ImageGallery.md │ │ ├── 1970-01-26-InlineGallery.md │ │ ├── 1970-01-27-Switch.md │ │ └── 1970-01-29-ImageBackground.md │ └── theme/ │ └── 1970-01-01-Theme.md ├── index.html ├── jekyll-static.sh ├── js/ │ ├── animation.js │ ├── docs.js │ ├── flourish.js │ ├── main.js │ └── prism.js ├── lib/ │ └── codemirror/ │ ├── codemirror.css │ ├── codemirror.js │ └── mode/ │ ├── javascript/ │ │ ├── index.html │ │ ├── javascript.js │ │ ├── json-ld.html │ │ ├── test.js │ │ └── typescript.html │ └── jsx/ │ ├── index.html │ ├── jsx.js │ └── test.js ├── static/ │ └── localization/ │ └── en.json └── video/ ├── examples/ │ ├── 01 parallax.webm │ ├── 02 hero header.webm │ ├── 03 fade in and out.webm │ └── 04 zoom in and out.webm ├── header.webm └── header@2x.webm ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ### Node ### # Logs logs *.log npm-debug.log* # Dependency directories node_modules jspm_packages # Optional npm cache directory .npm ### OSX ### .DS_Store ### Jekyll ### _site/ .sass-cache/ .jekyll-metadata ### Sublime ### *.sublime-* ### WebStorm ### .idea ================================================ FILE: Gemfile ================================================ source 'https://rubygems.org' gem 'github-pages', group: :jekyll_plugins gem 'wdm', '>= 0.1.0' if Gem.win_platform? ================================================ FILE: README.md ================================================ # Shoutem Shoutem is a platform for building beautiful React Native mobile apps. The easiest way to understand what Shoutem is to think of it as the WordPress for mobile apps. Apps are divided into smaller modules, called `extensions`. On WordPress you build a website out of plugins; on Shoutem you build a mobile app out of extensions. This architecture helps us achieve big development efficiency. ## Community Join [our community](https://www.facebook.com/groups/shoutem.community/) on Facebook. Also, feel free to ask a question on Stack Overflow using ["shoutem" tag](http://stackoverflow.com/tags/shoutem). ## Feature requests We would like to know what you want to do with Shoutem extensions. Feel free to raise issues with `feature requests` and upvote the ones labeled as a `feature request`, so we can know what you need the most. We're going to put label `in progress` for the features that we decide to implement. ## React Native Shoutem app is a React Native app. We designed extensions’ architecture with the goal not to add overhead over pure React Native. If you know React Native, you will know how to write Shoutem extensions. If you're just starting with React Native, check out our [React Native School](https://school.shoutem.com/). ## Marketplace Shoutem prepared and open sourced a lot of extensions that you can use in your apps. Don’t reinvent the wheel: reuse extensions which are suitable for you, customize them or create new ones. You can find the full list of extensions [here](https://github.com/shoutem/extensions). ## Customization All our extensions are open sourced, so you can modify anything you want by simply forking them. You can also write your own ones from scratch. ## Tools and docs We've prepared tools to speed up your extension development like [Shoutem CLI](https://shoutem.github.io/docs/extensions/reference/cli), [Shoutem UI toolkit](https://shoutem.github.io/ui/) for faster component development and documented all the concepts on this [developer's portal](http://shoutem.github.io/). If you find some bug or have a suggestion on how to improve anything, feel free to contribute or raise an issue! ## Start creating your apps! If you’re not already using Shoutem, you can create your account [here](https://new.shoutem.com), and start developing beautiful mobile apps. ================================================ FILE: _config.yml ================================================ title: Shoutem Developers description: Supercharge your React Native development with Shoutem tracking_id: UA-807293-5 repository: shoutem/shoutem.github.io sass: style: compressed # Build settings markdown: kramdown kramdown: syntax_highlighter_opts: disable : true url: "https://shoutem.github.io" # Variables shoutem: builderURL: "https://builder.shoutem.com" previewAppiOS: "https://itunes.apple.com/us/app/shoutem-preview/id1211732978" previewAppAndroid: "https://play.google.com/store/apps/details?id=com.shoutem.extensions.preview" cli: "https://www.npmjs.com/package/@shoutem/cli" school: "http://school.shoutem.com/" pricing: "https://new.shoutem.com/pricing/" support: "https://new.shoutem.com/support/" external: appleAppStore: "https://itunes.apple.com/ca/genre/ios/id36?mt=8" appleDeveloperAccount: "https://developer.apple.com/programs/enroll/" googlePlayStore: "https://play.google.com/store" googlePlayDeveloperAccount: "https://play.google.com/apps/publish/signup/" xcode: "https://developer.apple.com/xcode/" androidStudio: "https://developer.android.com/studio/index.html" # Examples example: devName: tom devEmail: tom@shoutem.com extensionName: restaurants appId: 8074 qrCodeLink: http://shoutem.app.link/?host=df4d8960.ngrok.io&port=80&dev=false ================================================ FILE: _includes/cards.html ================================================

40+ full-featured extensions

Main navigation

App-level navigation

News

Show news articles

Events

Show items with location and time

Products

Show products with purchase link

Photos

Show a photo gallery

Videos

Show a video gallery

About

Show info about your app or your business

Places

Show items with location

Restaurant menu

Show a restaurant menu

Books

Show books and authors

RSS News

Show news articles from RSS feed

People

Show people and contact details

Web view

Show a web page in-app or in a browser

Favorites

Extensions that are using Shoutem Favorites extensions are able to store and retrieve items that that app user has bookmarked in local app storage.

Navigation

Shows sub-navigation for the nested screen

Radio

Stream a radio station

RSS Videos

Show a video gallery from RSS feed

Vimeo videos

Show Vimeo video gallery

YouTube videos

Show YouTube video gallery

Mobile backend services

Analytics

Track Shoutem events

User authentication

Show user profile, sign out user

CMS

Shoutem CMS extension

Code push

Provides CodePush support for over the air code updates

Firebase

Firebase integration for sending push notifications, storage…

Google Analytics

Enables Google Analytics

Layouts

Shoutem Layout extension

Push notifications

Base extension for push notifications

RSS feeds

Shoutem RSS extension

Shopify

Shopify integration

Themes

Resolve and store theme related configuration

Carefully crafted themes for you to use and customize

================================================ FILE: _includes/footer.html ================================================ ================================================ FILE: _includes/ga.html ================================================ ================================================ FILE: _includes/head.html ================================================ {% if page.title %} {{ page.title }} {% if page.section %} - {{ page.section }}{% endif %} - {% endif %} {{ site.title }} ================================================ FILE: _includes/home-header-video.html ================================================ ================================================ FILE: _includes/navbar.html ================================================ ================================================ FILE: _includes/overview-content.html ================================================

Develop native apps with Shoutem and React Native

Build apps with Shoutem extensions

Shoutem apps are made of extensions. We’ve prepared 40+ extensions that cover most of the things you’ll need in any app. Customize them, or create literally anything by writing a new extension.

Javascript + React Native

P.S. CLI, app publishing, cloud storage and more, included.

Get started with tutorials

Tutorial
Get started

Create your first Shoutem extension

45 min simple
Tutorial
Modifying extensions

Learn how to customize Shoutem extensions

30 min simple
Tutorial
Creating themes

Learn how to style your app using themes

30 min simple
Tutorial
Using native code, native SKDs and JS libraries
20 min simple
Tutorial
Creating an integation with a third-party service
20 min simple
Tutorial
Shoutem automated and manual app publishing
10 min simple

Development stack

JavaScript and React Native only

Extensions are written in modern JavaScript and React Native. Use an editor of your choice and start coding!
Not a JavaScript and React Native developer? Get a quick-start with our tutorial.

FAQ

What do I need on my system?
Just a text editor and terminal (Windows, macOS or Linux). You don’t need XCode or Android Studio, and you don’t need a Mac to build iOS apps.
How much advanced JavaScript knowledge is required?
Just a text editor and terminal (Windows, macOS or Linux). You don’t need XCode or Android Studio, and you don’t need a Mac to build iOS apps.
Does Shoutem build and publish my apps?
Just a text editor and terminal (Windows, macOS or Linux). You don’t need XCode or Android Studio, and you don’t need a Mac to build iOS apps.
Do I need to pay for this?
Just a text editor and terminal (Windows, macOS or Linux). You don’t need XCode or Android Studio, and you don’t need a Mac to build iOS apps.
================================================ FILE: _includes/sidebar-nav.html ================================================ ================================================ FILE: _includes/signup-modal.html ================================================ ================================================ FILE: _layouts/doc.html ================================================ {% include head.html %} {% include navbar.html %}
{{ content }}
{% include signup-modal.html %} {% include ga.html %} ================================================ FILE: _layouts/home.html ================================================ {% include head.html %} {% include navbar.html %}

A platform to build, publish, and manage native apps with ease

Build beautiful native apps with Shoutem and React Native in no time

How does it work?

  1. 1. Get a head start
    Backend, 40+ extensions, push, analytics
  2. 2. Customize
    No limits, powered by React Native
  3. 3. Automate maintenance
    Automated publishing and updates

Get a head start with Shoutem extensions

{% include cards.html %}

Customize without limits, it’s open source

  • Customize our extensions or create your own
  • Code, test and debug locally
  • 100% JavaScript and React Native
  • Extensive documentation and developer tools

Publish and update regularly with ease

A CMS backend anyone can use

Publish to stores

Publish automatically to App Store and Google Play

Manage content

Grant moderator access to content managers

Analyse and engage

Analyze usage. Send push notifications

{% include footer.html %} {% include ga.html %} ================================================ FILE: _layouts/overview.html ================================================ {% include head.html %} {% include navbar.html %}
{% include overview-content.html %}
{% include signup-modal.html %} {% include ga.html %} ================================================ FILE: _sass/animation.scss ================================================ $imagesPath: '../img/'; /* animation container */ .shoutem-ani { position: absolute; z-index: 1; left: 0; top: 0; width: 100%; height: 100%; overflow: hidden; pointer-events: none; .particle { position: absolute; pointer-events: none; user-select: none; -webkit-touch-callout: none; touch-callout: none; } .particle-1 { @include size(14px, 14px); @include background-png-3x(particle-1); } .particle-2 { @include size(24px, 24px); @include background-png-3x(particle-2); } .particle-3 { @include size(15px, 15px); @include background-png-3x(particle-3); /*@include rotating();*/ } .particle-4 { @include size(15px, 15px); @include background-png-3x(particle-4); /*@include rotating();*/ } .particle-5 { @include size(75px, 95px); @include background-png-3x(particle-5); .thrust-line { position: absolute; top: 77px; @include size(3px, 15px); @include border-radius(2px); background: white; animation: ani-thrust-line 1.1s linear infinite; } .thrust-line-1 { left: 19px; } .thrust-line-2 { left: 24px; animation-delay: 0.25s; } .thrust-line-3 { left: 29px; animation-delay: 0.65s; } .thrust-line-4 { left: 43px; animation-delay: 0.35s; } .thrust-line-5 { left: 48px; animation-delay: 0.85s; } .thrust-line-6 { left: 53px; animation-delay: 0.55s; } } } ================================================ FILE: _sass/base.scss ================================================ /* Bootstrap overrides */ body { color: $primary; line-height: 30px; } blockquote { border-width: 1px; font-size: 17px; padding: 1px 22px; } blockquote p { color: $primary } blockquote .small, blockquote footer, blockquote small { display: block; font-size: 16px; font-weight: normal; } /*Remove line from blockqute*/ blockquote .small:before, blockquote footer:before, blockquote small:before { content: ""; } pre { border-color: $code; margin-bottom: 24px; } hr { display: none; } hr.menu-hr { display: block; border-top: 1px solid #ccc; margin-top: 15px; margin-bottom: 12px; } p { margin-bottom: 20px; color: $text; } h1 { @include headline(48px, 48px); } h2 { @include headline(24px, 32px); } h4 { font-weight: bold; margin-bottom: 25px; } h5 { @include headline(20px, 32px); font-weight: normal; } a:focus, a:hover { color: $accent; text-decoration: underline; outline: none; } .row { position: relative; } ::-moz-selection { color: white; background: #00aadf; text-shadow: none; } ::selection { color: white; background: #00aadf; text-shadow: none; } input[type=text], input[type=tel], input[type=email], input[type=submit], textarea { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-appearance: none; appearance: none; resize: none; outline: none; } ================================================ FILE: _sass/cards.scss ================================================ .list-cards { display: flex; justify-content: space-between; margin-top: 40px; @include medium { flex-direction: column; .card { margin-left: auto; margin-right: auto; margin-top: 48px; } } } .card { display: inline-block; &.rounded { border-radius: 4px; .card-image { border-top-left-radius: 4px; border-top-right-radius: 4px; } } &.shadowed { box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1), 0 0 2px 0 rgba(0, 0, 0, 0.1); transition: box-shadow ease-out 0.2s; &:hover, &:active { box-shadow: 0 19px 38px 0 rgba(0, 0, 0, 0.3), 0 15px 12px 0 rgba(0, 0, 0, 0.2); } } img { max-width: 100%; } .card-content { .card-title { margin-top: 16px; font-size: 15px; font-weight: normal; line-height: 24px; text-transform: none; color: #444f6c; @include medium { margin-top: 0; line-height: normal; } } p { color: #888fa1; font-size: 14px; line-height: 24px; @include medium { margin-top: 4px; } } } } ================================================ FILE: _sass/colors.scss ================================================ /*Documentation*/ $accent: #00aadf; $primary: #444f6c; $primaryhover: #4a5b85; $secondary: rgba(68, 79, 108, 0.2); $secondaryhover: rgba(68, 79, 108, 0.3); $text: #888fa1; $code: #f3f3f3; $lightaccent: rgba(0, 170, 223, 0.08); $lightaccent-2: rgba(0, 170, 223, 0.16); /*Landing page*/ $hometext: #697289; $homeheading: #222b35; $lightbackground: #efefef; $footercolor: #7b7f8c; $footerbackground: #232b40; $muted: #858585; $green: #6db81d; $graybackground: #ebebeb; $bluebackground: #e5f7fc; $darkbluebackground: #2e3854; $chapterfooter: #969cac; ================================================ FILE: _sass/documentation-layout.scss ================================================ .container-fluid, .navbar .container-fluid { max-width: 1920px; } #wrapper { width: 100%; background: #FFFFFF; } .documentation-wrapper { max-width: 1920px; padding-left: 280px; margin: 0 auto; @media screen and (max-width: 991px) { padding-left: 0; } } #documentation { max-width: 870px; width: 75%; margin: 0 auto; @include mobile { width: auto; margin: 0 20px; } } .image { margin-top: 20px; margin-bottom: 40px; img { margin: 0 auto; display: block; width: 100%; border: 1px solid #bdc0cb; border: none; } } .navbar { margin-bottom: 0; } ================================================ FILE: _sass/footer.scss ================================================ .footer { position: fixed; bottom: 0; width: 100%; z-index: -1; @include small { position: static; } .signup { text-align: center; background-color: #00AADF; padding: 0 24px; .signup-title { @include headline(45px, 60px, #fff); font-weight: 100; text-align: center; padding-top: 80px; margin-bottom: 15px; @include mobile { font-size: 36px; line-height: 46px; max-width: 240px; margin: 0 auto 15px; padding-top: 60px; } } .signup-description { color: rgba(#ffffff, 0.8); line-height: 30px; max-width: 570px; margin: 0 auto 32px; @include mobile { width: 100%; margin-bottom: 30px; } } .signup-email { width: 370px; height: 56px; border-radius: 0; background-color: transparent; padding: 1px 0 0; margin-bottom: 80px; margin-right: 8px; border: none; border-bottom: 1px solid rgba(#ffffff, 0.7); color: #FFF; &::-webkit-input-placeholder { color: rgba(#ffffff, 0.7); } &::-moz-placeholder { /* Firefox 19+ */ color: rgba(#ffffff, 0.7); opacity: 1; } &:-ms-input-placeholder { color: rgba(#ffffff, 0.7); } &:-moz-placeholder { color: rgba(#ffffff, 0.7); opacity: 1; } @include easeLinear; &:focus { color: #ffffff; border-color: #ffffff; background: none; } @include small { margin-bottom: 12px; margin-left: auto; margin-right: auto; width: 100%; max-width: 280px; display: block; } } .signup-button { width: 188px; height: 56px; border: 1px solid #fff; border-radius: 4px; background-color: #fff; text-transform: uppercase; margin-bottom: 40px; font-weight: bold; font-size: 14px; letter-spacing: 1px; padding: 0; @include easeLinear; &:hover, &:focus, &:active { background-color: #f4f4f4; border-color: #f4f4f4; } @include small { width: 100%; max-width: 280px; margin-bottom: 60px; } } } .bottom-footer { background-color: $footerbackground; height: 90px; color: $footercolor; padding-top: 22px; @include small { height: 326px; } .container-fluid { max-width: 1210px; } .footer-logo { @include logoimage('../img/shoutem_logo_footer.png', 85px, 30px); opacity: 0.4; float: left; margin-top: 9px; @include easeLinear; @include small { float: none; display: block; margin-bottom: 24px; } @include small { margin: 8px 0 12px; } &:hover, &:active { opacity: 1; } } .footer-menu { float: right; opacity: 1; color: $footercolor; list-style: none; margin-top: 10px; margin-left: 0; padding-left: 0; @include small { float: none; padding: 0; } @include small { margin-top: 0; } li { float: left; @include small { float: none; display: block; } &.icon-twitter a, &.icon-github a { background-image: url(/img/icon-twitter.svg); background-repeat: no-repeat; background-size: 22px; background-position: center; text-indent: -100px; width: 24px; height: 24px; overflow: hidden; display: inline-block; margin-left: 48px; margin-top: 3px; opacity: 0.4; &:hover, &:active { opacity: 1; } @include small { margin-top: 0; } } &.icon-twitter a { @include small { margin-left: 24px; } } &.icon-github { @include small { &:before { content: "Follow"; text-transform: uppercase; font-size: 13px; letter-spacing: 1px; display: block; color: $text; opacity: 0.5; margin: 23px 0 17px; } } a { background-image: url(/img/icon-github.svg); background-size: 20px; margin-left: 37px; @include small { margin-left: 0; float: left; } } } } a { color: $text; @include easeLinear; font-size: 14px; padding: 10px 11px; margin-left: 22px; &:hover, &:active { color: white; text-decoration: none; } @include small { margin-left: 0; display: block; padding: 5px 0; } } } } } ================================================ FILE: _sass/github.scss ================================================ /* Copyright 2014 GitHub Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ .pl-c /* comment */ { color: #969896; } .pl-c1 /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */, .pl-s .pl-v /* string variable */ { color: #0086b3; } .pl-e /* entity */, .pl-en /* entity.name */ { color: #795da3; } .pl-s .pl-s1 /* string source */, .pl-smi /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ { color: #333; } .pl-ent /* entity.name.tag */ { color: #63a35c; } .pl-k /* keyword, storage, storage.type */ { color: #a71d5d; } .pl-pds /* punctuation.definition.string, string.regexp.character-class */, .pl-s /* string */, .pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */, .pl-sr /* string.regexp */, .pl-sr .pl-cce /* string.regexp constant.character.escape */, .pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */, .pl-sr .pl-sre /* string.regexp source.ruby.embedded */ { color: #183691; } .pl-v /* variable */ { color: #ed6a43; } .pl-id /* invalid.deprecated */ { color: #b52a1d; } .pl-ii /* invalid.illegal */ { background-color: #b52a1d; color: #f8f8f8; } .pl-sr .pl-cce /* string.regexp constant.character.escape */ { color: #63a35c; font-weight: bold; } .pl-ml /* markup.list */ { color: #693a17; } .pl-mh /* markup.heading */, .pl-mh .pl-en /* markup.heading entity.name */, .pl-ms /* meta.separator */ { color: #1d3e81; font-weight: bold; } .pl-mq /* markup.quote */ { color: #008080; } .pl-mi /* markup.italic */ { color: #333; font-style: italic; } .pl-mb /* markup.bold */ { color: #333; font-weight: bold; } .pl-md /* markup.deleted, meta.diff.header.from-file */ { background-color: #ffecec; color: #bd2c00; } .pl-mi1 /* markup.inserted, meta.diff.header.to-file */ { background-color: #eaffea; color: #55a532; } .pl-mdr /* meta.diff.range */ { color: #795da3; font-weight: bold; } .pl-mo /* meta.output */ { color: #1d3e81; } ================================================ FILE: _sass/helpers.scss ================================================ .center-v { position: relative; top: 50%; transform: translateY(-45%); } ================================================ FILE: _sass/menu-overlay.scss ================================================ @import "variables"; @import "mixins"; .mobile-menu-overlay { position: fixed; visibility: hidden; top: 0; right: 0; width: 100%; height: 100%; z-index: 1060; transition: visibility 0.5s; @include medium-2 { &.open { visibility: visible; .navigation { transform: translate(0, 0); transform: translate3d(0, 0, 0); } .close-menu-overlay { opacity: 1; } } } .navigation { position: absolute; right: 0; padding-top: 24px; width: 286px; height: 100%; overflow: auto; background: #FFF; transform: translate(100%, 0); transform: translate3d(100%, 0, 0); transition: transform .5s; ul li a { padding-left: 40px; } @include tiny { width: 256px; } } .close-menu-overlay { position: absolute; height: 100%; width: 100%; top: 0; left: 0; background: rgba(68, 79, 108, 0.5); opacity: 0; transition: opacity 0.5s } } ================================================ FILE: _sass/mixins.scss ================================================ @mixin headline ($size, $lineheight, $color: $primary) { font-size: $size; line-height: $lineheight; color: $color; } @mixin logoimage ($imageurl, $width, $height) { background-image: url($imageurl); width: $width; height: $height; background-size: 100%; background-repeat: no-repeat; } @mixin flexcontainer () { display: flex; align-items: center; justify-content: center; } /// Mixin to manage responsive breakpoints /// @author Hugo Giraudel /// @param {String} $breakpoint - Breakpoint name /// @require $breakpoints @mixin respond-to($breakpoint) { // If the key exists in the map @if map-has-key($breakpoints, $breakpoint) { // Prints a media query based on the value @media (min-width: map-get($breakpoints, $breakpoint)) { @content; } } // If the key doesn't exist in the map @else { @warn "Unfortunately, no value could be retrieved from `#{$breakpoint}`. " + "Available breakpoints are: #{map-keys($breakpoints)}."; } } @mixin tiny { @media only screen and (max-width: 359px) { @content; } } @mixin mobile { @media only screen and (max-width: 480px) { @content; } } @mixin small { @media only screen and (max-width: 640px) { @content; } } @mixin medium { @media only screen and (max-width: 768px) { @content; } } @mixin medium-2 { @media only screen and (max-width: 992px) { @content; } } @mixin large { @media only screen and (max-width: 1280px) { @content; } } @mixin large-2 { @media only screen and (max-width: 1440px) { @content; } } @mixin background-png-3x ($image) { background-repeat: no-repeat; background-image: url("#{$imagesPath}#{$image}.png"); @media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { background-image: url("#{$imagesPath}#{$image}@2x.png"); background-size: 100%; } @media (-webkit-min-device-pixel-ratio: 2.5), (min-resolution: 216dpi) { background-image: url("#{$imagesPath}#{$image}@3x.png"); background-size: 100%; } } @mixin size ($w, $h) { width: $w; height: $h; } @mixin border-radius ($r) { -webkit-border-radius: $r; -moz-border-radius: $r; -o-border-radius: $r; -ms-border-radius: $r; border-radius: $r; } @mixin easeLinear ($time: 0.2s, $easing: linear, $props: all) { transition: $props $time $easing 0s; } @mixin rotating ($time: 9s) { animation-name: ani-rotate; animation-duration: $time; animation-iteration-count: infinite; animation-timing-function: linear; } @keyframes ani-rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes ani-thrust-line { 0% { height: 3px; } 20% { height: 15px; } 100% { height: 3px; } } ================================================ FILE: _sass/navbar.scss ================================================ .headroom { will-change: transform; transition: transform 200ms linear; } .headroom--pinned { transform: translateY(0%); } .headroom--unpinned { transform: translateY(-100%); } .navbar-right { margin-right: 0; } .navbar-default .navbar-nav { font-size: 15px; .btn { font-size: 15px; } .active { .navbar-nav--a--link { color: $accent; background: none; transition: none; &:hover, &:focus { background: none; color: $accent; } &::after, &::before { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } &::before { -webkit-transition-delay: 0s; transition-delay: 0s; } } } .navbar-nav--a--link { position: relative; color: $hometext; @include easeLinear(0.4s, linear, color); &:hover, &:focus { color: $accent; } &:hover::before, &:focus::before { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); -webkit-transition-delay: 0s; transition-delay: 0s; } &::before, &::after { content: ''; position: absolute; bottom: -1px; left: 0; width: 100%; height: 4px; -webkit-transform: scale3d(0, 1, 1); transform: scale3d(0, 1, 1); -webkit-transform-origin: center left; transform-origin: center left; } &::after { background: $accent; } &::before { -webkit-transition: transform 0.4s cubic-bezier(0.22, 0.61, 0.36, 1); transition: transform 0.4s cubic-bezier(0.22, 0.61, 0.36, 1); background: #00BEF7; } } .active>.navbar-nav--a--link, .navbar-nav--a--link:focus, .navbar-nav--a--link:hover { } .btn-primary { margin-top: 18px; border-radius: 4px; height: 48px; padding-top: 12px; color: #fff; font-weight: bold; background: $accent; @include easeLinear(); &:focus, &:hover { color: #fff; background-color: #00BEF7; border: 1px solid #00BEF7; } } } .navbar .container-fluid { max-width: 1210px; } .navbar .navbar-brand { padding: 0; height: auto; margin-top: 20px; margin-left: 0; margin-right: 0; } .navbar .navbar-brand--logo { background-image: url('../img/logo-shoutem-for-developers.svg'); background-position: left center; background-repeat: no-repeat; background-size: 100%; width: 215px; height: 34px; text-indent: -1000%; overflow: hidden; display: block; } .home .navbar-brand--logo { @media (min-width: 768px) { margin-left: 14px; } } .documentation .navbar-brand { @include easeLinear(); &:hover { background: #00AADF; } svg { display: block; } #documentation-logo { @include easeLinear(); } &:hover #documentation-logo { fill: #FFF; } } #wrapper { padding-top: 80px; @include small { margin-bottom: 0 !important; } } @media (min-width: 768px) { .navbar-nav>li>a { padding: 30px 0 28px; margin-right: 48px; } .navbar-nav>li>a.btn-primary { padding: 12px 16px 0; margin-right: 0px; } } .navbar-default .navbar-toggle { border: none; padding: 18px 15px; margin: 16px 0 0; &:hover { background: none; .icon-bar { background-color: #00AADF; } } } .navbar-toggle .icon-bar { width: 17px; height: 1px; } .home .breadcrumb { display: none; } .breadcrumb { margin-top: 19px; margin-bottom: 0; margin-left: 24px; background: none; color: $primary; float: left; li:first-child { opacity: 0.7; } li + li { &:before { content: ""; display: inline-block; background: url(/img/icon-descriptive-next.svg) center center no-repeat; width: 24px; height: 12px; margin: 0 17px 0 13px; } @include small { display: none; } } @include small { margin-top: 14px; } } ================================================ FILE: _sass/pager.scss ================================================ #pager-wrapper { max-width: 1440px; margin: 80px auto 0; .pager { background: #FFFFFF; margin: 0 0 0 280px; @include medium-2 { margin-left: 0; } a { width: 50%; border: none; border-radius: 0; height: 88px; padding-top: 31px; @include small { height: 72px; padding-top: 28px; line-height: normal; } } .previous a { background-color: $lightaccent; color: $accent; transition: background 0.2s linear; &:hover { background-color: rgba(0,170,223,0.14); } &::before { content: " "; display: inline-block; width: 13px; height: 13px; margin-right: 24px; background: url(/img/icon-arrow-left.svg) center center no-repeat; vertical-align: middle; margin-bottom: 1px; } @include small { color: transparent; width: 72px !important; overflow: hidden; } } .next a { background-color: $accent; color: #fff; transition: background 0.2s linear; &:hover { background-color: #00bef7; } &::after { content: " "; display: inline-block; background: url(/img/icon-arrow-right.svg) center center no-repeat; width: 15px; height: 13px; vertical-align: middle; @include small { height: 72px; line-height: 72px; background-size: 15px 13px; vertical-align: middle; margin-left: -24px; } } span { display: inline-block; vertical-align: middle; margin-right: 24px; @include small { margin-right: 36px; } } @include small { width: calc(100% - 72px); padding: 0 10px 0 0; } } .previous { @include small { overflow: hidden; width: 72px !important; } } img { position: relative; width: 13px; height: 13px; bottom: 1px; } .docs-pager-icon-left { right: 24px; } .docs-pager-icon-right { left: 24px; } .inactive { background-color: $lightaccent; float: left; width: 50%; height: 88px; padding-top: 32px; &.next { background-color: $accent; @include small { width: calc(100% - 72px); } } a { display: none; } @include small { height: 72px; } } } } ================================================ FILE: _sass/sidebar.scss ================================================ .navigation { list-style: none; margin: 0; padding: 0; background: #FFF; ul { list-style: none; padding: 0; margin: 0; position: relative; li a { padding-left: 40px; } &.level-2 { .menu-group-title { font-size: 14px; font-weight: 700; } } &.level-3 { &::before { content: ""; width: 1px; position: absolute; background-color: #000; opacity: 0.08; left: 58px; top: 11px; bottom: 11px; height: initial; } } } li { font-size: 14px; &.primary > a { height: 48px; line-height: 48px; font-size: 15px; font-weight: 700; } .active { color: $accent; &:not(.menu-group-title) { background-color: $lightaccent; } } a { height: 40px; width: 100%; display: inline-block; line-height: 40px; vertical-align: middle; color: $primary; text-decoration: none; padding-left: 24px; font-size: 14px; @include easeLinear(); &:hover, &:active, &:focus { background-color: $lightaccent-2; } } } &.level-1 { > li > a { font-size: 15px; font-weight: 700; height: 48px; line-height: 48px; } } .sidelink a { font-weight: 500 !important; font-size: 14px !important; height: 40px !important; line-height: 40px !important; } .button-signup { color: #00aadf; font-weight: 700; font-size: 15px; } .menu-button-signup { padding: 15px 0; border-top: 1px solid #DDD; border-bottom: 1px solid #DDD; margin: 15px 0 !important; } } ================================================ FILE: _sass/typography.scss ================================================ @font-face { font-family: 'SourceCode'; src: url('../fonts/sourcecodepro-medium-webfont.eot'); src: url('../fonts/sourcecodepro-medium-webfont.eot?#iefix') format('embedded-opentype'), url('../fonts/sourcecodepro-medium-webfont.woff2') format('woff2'), url('../fonts/sourcecodepro-medium-webfont.woff') format('woff'), url('../fonts/sourcecodepro-medium-webfont.ttf') format('truetype'); font-weight: normal; font-style: normal; } @font-face { font-family: 'MuseoSans'; src: url('../fonts/museosans-100-webfont.eot'); src: url('../fonts/museosans-100-webfont.eot?#iefix') format('embedded-opentype'), url('../fonts/museosans-100-webfont.woff2') format('woff2'), url('../fonts/museosans-100-webfont.woff') format('woff'), url('../fonts/museosans-100-webfont.ttf') format('truetype'); font-weight: 100; font-style: normal; } @font-face { font-family: 'MuseoSans'; src: url('../fonts/museosans-300-webfont.eot'); src: url('../fonts/museosans-300-webfont.eot?#iefix') format('embedded-opentype'), url('../fonts/museosans-300-webfont.woff2') format('woff2'), url('../fonts/museosans-300-webfont.woff') format('woff'), url('../fonts/museosans-300-webfont.ttf') format('truetype'); font-weight: 300; font-style: normal; } @font-face { font-family: 'MuseoSans'; src: url('../fonts/museosans_500-webfont.eot'); src: url('../fonts/museosans_500-webfont.eot?#iefix') format('embedded-opentype'), url('../fonts/museosans_500-webfont.woff2') format('woff2'), url('../fonts/museosans_500-webfont.woff') format('woff'), url('../fonts/museosans_500-webfont.ttf') format('truetype'); font-weight: normal; font-style: normal; } @font-face { font-family: 'MuseoSans'; src: url('../fonts/museosans_700-webfont.eot'); src: url('../fonts/museosans_700-webfont.eot?#iefix') format('embedded-opentype'), url('../fonts/museosans_700-webfont.woff2') format('woff2'), url('../fonts/museosans_700-webfont.woff') format('woff'), url('../fonts/museosans_700-webfont.ttf') format('truetype'); font-weight: bold; font-style: normal; } body { /* makes font rendering the same on Windows/OSX/iOS/Android */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } ================================================ FILE: _sass/variables.scss ================================================ $breakpoints: ( 'small': 768px, 'medium': 992px, 'large': 1200px ) !default; ================================================ FILE: css/documentation.scss ================================================ --- --- @import "colors"; @import "mixins"; @import "documentation-layout"; @import "sidebar"; @import "pager"; @import "variables"; @import "footer"; @import "menu-overlay"; @import "cards"; h1{ margin-top: 70px; margin-bottom: 33px; @include mobile { font-size: 36px; margin-top: 34px; } } h2 { font-weight: 300; margin-top: 64px; margin-bottom: 16px; @include mobile { font-size: 18px; line-height: 32px; margin-top: 48px; font-weight: 500; } } h2[id^=api], h2[id^=example], h2[id^=examples] { margin-top: 8px; } h3 { @include mobile { font-size: 16px; line-height: 30px; font-weight: 500; } } h4 { font-size: 15px; } code { border-radius: 2px; } h3 code, h4 code { background: inherit; font-weight: inherit; color: inherit; font-size: inherit; } .docs-component-image { max-width: 100%; margin-bottom: 24px; } .docs-codeblock-path { color: #888fa1; font-size: 15px; } pre code.language-shellsession { color: #697289 !important; display: block; } pre code.language-shellsession::first-line { color: #343D59; } pre code.language-shellsession .dollar-sign { color: #697289; } h4[id^=jsx-declaration] { margin-top: -28px; } h4[id^=jsx-declaration], h4[id^=style] { margin-bottom: 16px; } h4[id^=jsx-declaration] + pre, h4[id^=style] + pre{ margin-bottom: 44px; } h4[id^=props] + ul, h4[id^=style] + ul{ margin-bottom: 44px; } code[class*="language-"], pre[class*="language-"] { font-family: SourceCodePro-Medium, Menlo, Monaco, Consolas,"Courier New", monospace; text-shadow: none; color: #444f6c; line-height: 24px; } :not(pre) > code[class*="language-"], pre[class*="language-"] { background-color: #f3f3f3; } pre code { color: #444f6c; } pre { font-size: 14px; border: none; } pre, pre[class*="language-"] { margin-bottom: 40px; } blockquote p { color: #444f6c; font-size: 16px; line-height: 28px; } blockquote h4 + p { margin-top: -16px; } blockquote ol:last-child, blockquote p:last-child, blockquote ul:last-child { margin-bottom: 9px; } blockquote { border-left: 1px solid #C4C7D0; } blockquote h4 { margin-top: 20px; } blockquote p { margin-top: 13px; } blockquote { font-size: 18px; padding: 1px 38px; } p > a { border-bottom: 1px solid rgba(0, 170, 223, 0); transition: border-bottom-color 0.2s linear; } p > a:hover { text-decoration: none; border-bottom-color: rgba(0, 170, 223, 1); } pre[data-line] { padding-left: 1em; } .line-highlight { background: #00aadf; opacity: 0.2; &:before, &[data-end]:after { content: ""; } } #documentation { opacity: 1; transition: opacity 0.2s; } .loading #documentation { opacity: 0; } /* Navbar */ .navbar { border-bottom: 1px solid #ececec; @include small { min-height: 0; height: 72px; } .navbar-header { @media (min-width: 768px) and (max-width: 991px) { width: 100%; } } &.navbar-default .navbar-toggle { margin-right: 20px; @media (min-width: 768px) { margin-right: 5px; } } .navbar-brand { padding: 31px; margin-top: 0; border-right: 1px solid #ececec; @include small { padding: 27px; } } .navbar-brand--logo { background-image: url('../img/icon-shoutem-mark.svg'); width: 18px; height: 18px; } .navbar-toggle { @media (min-width: 768px) { display: block; } @media (min-width: 992px) { display: none; } @include small { margin-top: 12px; } } .navbar-search-form.navbar-form.navbar-form-border { margin: 0; margin-right: 24px; border-left: 1px solid #ececec; border-right: 1px solid #ececec; padding: 24px 16px; .search-icon { right: 8px; width: 24px; height: 24px; opacity: 0.5; vertical-align: middle; } } #navbar-search-input { background-color:transparent !important; border:none !important; box-shadow: none !important; transition: all .5s; width: 70px; } #navbar-search-input:focus { width: 165px; } /* Highlighted search terms */ .algolia-docsearch-suggestion--highlight { color: #00aadf; } /* Highligted search terms in the main category headers */ .algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--highlight { background-color: #00aadf; } /* Currently selected suggestion */ .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content { background-color: rgba(0,170,223,0.16) !important; } } .navbar-collapse { &.collapse { @media (min-width: 768px) { display: none !important; } @media (min-width: 992px) { display: block !important; } } } #documentationTab { display: none; } /* Navigation */ #sidebar-wrapper { position: fixed; left: 0; bottom: 0; height: calc(100% - 81px); width: 280px; border-right: 1px solid #ececec; overflow: auto; padding: 28px 0; background: #FFF; z-index: 1070; @media screen and (min-width: 1921px) { left: calc(50% - 950px); } @media screen and (max-width: 991px) { left: auto !important; right: 0; transform: translate(100%, 0); transform: translate3d(100%, 0, 0); transition: transform 0.5s, visibility 0.5s; height: 100%; visibility: hidden; padding: 0; &.open { transform: translate(0, 0); transform: translate3d(0, 0, 0); visibility: visible; } } .home-link { padding: 16px 0; border-bottom: 1px solid #DDD; margin: 0 0 15px; @include small { padding: 11px 0 12px; } } a { padding-left: 48px; } ul ul a { padding-left: 56px; } ul ul ul a { padding-left: 74px; } .btn-primary { display: none; } } .menu-group-wrapper { display: none; &.open { display: block; } } /* Signup: modal version */ #signup { display: none; } #signup-modal { position: fixed; width: 100%; height: 100%; top: 0; left: 0; opacity: 0; visibility: hidden; background-color: rgba(68,79,108,0.5); z-index: 1080; transition: opacity 0.35s, visibility 0.35s; &.open { opacity: 1; visibility: visible; } .content { position: absolute; top: 50%; left: 50%; width: 648px; height: auto; max-width: calc(100% - 48px); max-height: calc(100% - 48px); padding: 55px 48px 0; background-color: #FFF; overflow: hidden; box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.2); border-radius: 2px; transform: scale3d(0.95, 0.95, 0.95) translate3d(-50%, -50%, 0); transform-origin: left; transition: transform 0.35s; @include mobile { max-width: calc(100% - 40px); max-height: calc(100% - 40px); padding: 35px 20px 0; } } &.open .content { transform: scale3d(1, 1, 1) translate3d(-50%, -50%, 0); } .signup-title { font-size: 24px; font-weight: 300; } .signup-description { font-weight: 500; font-size: 14px; line-height: 24px; color: #7b8397; margin: 20px 0 0 0; } .signup-email { height: 48px; background-color: rgba(228, 230, 233, 0.4); box-shadow: inset 0 -1px 0 0 rgba(68, 79, 108, 0.3); border: none; border-radius: 0; color: $primary; font-weight: 500; &:focus { box-shadow: inset 0 -2px 0 0 #00aadf; } &::-webkit-input-placeholder { color: #444f6c; } &::-moz-placeholder { /* Firefox 19+ */ color: #444f6c; opacity: 1; } &:-ms-input-placeholder { color: #444f6c; } &:-moz-placeholder { color: #444f6c; opacity: 1; } } label { font-size: 13px; color: $text; font-weight: 500; line-height: 1; margin: 25px 0 0; } .control-buttons { padding: 16px 48px; margin: 56px -48px 0; background-color: #f6f7f8; text-align: right; @include mobile { margin-top: 32px; } } .signup-button, .cancel-button { width: 120px; height: 40px; line-height: 38px; border: 1px solid #fff; border-radius: 4px; color: #FFF; background-color: $primary; text-transform: uppercase; font-weight: 700; font-size: 13px; letter-spacing: 1px; padding: 1px 0 0; @include easeLinear; @include mobile { width: auto; padding: 1px 16px 0; } } .signup-button { &:hover, &:focus, &:active { background-color: $primaryhover; } } .cancel-button { color: $primary; background-color: $secondary; margin-right: 10px; &:hover, &:focus, &:active { background-color: $secondaryhover; outline: none; } } } .video-screen { width: 100%; background-color: #EEE; padding: 40px; border-radius: 4px; margin-bottom: 40px; video { display: block; width: 280px; margin: 0 auto; } } .edit-link { padding: 30px 0 28px; a { font-size: 15px; font-weight: 500; padding: 0 0 0 32px !important; display: inline-block; color: #444f6c; background: url(../img/icon-github-dark.svg) left center no-repeat; transition: opacity 0.25s; &:hover { opacity: 0.65; } } } .overview { h1 { /*margin-top: 96px;*/ margin-bottom: 0; line-height: 65px; } .section { width: 100%; max-width: 870px; &.section-intro { margin-top: 48px; } &::after { content: ""; display: table; clear: both; } } h2 { font-size: 13px; font-weight: bold; line-height: 32px; letter-spacing: 1px; color: rgba(#444f6c, 0.7); text-transform: uppercase; padding: 27px 0 32px; border-bottom: 1px solid rgba(#ededed, 0.7); } h3 { line-height: 32px; font-weight: 300; margin-top: 48px; } p { font-size: 15px; font-weight: normal; line-height: 25px; color: #888fa1; } .intro-video { float: left; width: 50%; /* 432/870 = 49.65% */ height: 270px; background: url(../img/overview/video-bg@2x.jpg) center center no-repeat; background-size: cover; position: relative; &::after { content: ""; width: 100%; height: 100%; position: absolute; top: 0; left: 0; background: url(../img/overview/icon-play.svg) center center no-repeat; } p { color: #ffffff; font-size: 14px; line-height: 20px; font-weight: bold; position: absolute; bottom: 0; left: 0; width: 100%; padding: 0 139px 0 24px; .time { position: absolute; right: 24px; top: 8px; text-align: right; font-weight: normal; width: 70px; line-height: 24px; background: url(../img/overview/icon-time-white.svg) 0 center no-repeat; } } } .intro-text { float: right; /*width: 45%;*/ /* 390/870 = 44.82 */ width: 100%; /*margin-left: 5%;*/ margin-left: 0; } .intro-title { font-size: 16px; line-height: 32px; font-weight: bold; color: #444f6c; } .intro-content { p { &:first-child { margin-top: 16px; } small { font-size: 13px; } } img { display: block; margin: 32px 0 16px; } } .card { text-decoration: none; max-width: 260px; .card-title { margin-top: 12px; } .card-image { min-height: 148px; background: #444f6c; text-align: center; img { margin-top: 24px; } &.extensions img { margin-top: 32px; margin-left: 18px; } &.themes img { margin-top: 36px; } } .card-content { padding: 24px; p:last-child { margin-bottom: 0; } } .card-category { font-size: 12px; font-weight: bold; color: #888fa1; text-transform: uppercase; letter-spacing: 1px; line-height: initial; } .card-time { @include headline(14px, 24px, $chapterfooter); @include flexcontainer(); justify-content: initial; color: #969CAC; font-size: 14px; font-weight: normal; .icon-time { display: inline-block; @include logoimage('../img/icon-time.svg', 19px, 19px); margin-right: 8px; position: relative; top: -2px; } .difficulty { padding-left: 19px; position: relative; &::before { content: ""; position: absolute; width: 4px; height: 4px; left: 8px; top: calc(50% - 3px); background: #969CAC; border-radius: 50%; } } } } .list-cards.row-2 { .card { .card-title { font-size: 15px; } .card-time { margin-top: 48px; } } } .logotypes { margin: 44px 0 0; padding: 0; list-style: none; li { margin: 0; padding: 88px 0 0; width: 25%; float: left; font-size: 14px; font-weight: normal; line-height: 22px; background: url(../img/overview/logo-js.svg) 0 0 no-repeat; &.react-native { background-image: url(../img/overview/icon-react-native.svg); } &.shoutem-ui-toolkit { background-image: url(../img/overview/icon-ui-toolkit.svg); } &.code-editor { background-image: url(../img/overview/icon-code-editor.svg); } } } .section-faq { padding-bottom: 120px; dt, dd { font-size: 15px; line-height: 32px; font-weight: 500; color: #444f6c; } dd { color: #888fa1; } } } ================================================ FILE: css/home.scss ================================================ --- --- @import "colors"; @import "variables"; @import "mixins"; @import "footer"; @import "animation"; @import "helpers"; @import "sidebar"; @import "menu-overlay"; .container, .container-fluid { padding-left: 20px; padding-right: 20px; } .row { margin-left: -20px; margin-right: -20px; } .container-fluid>.navbar-collapse, .container-fluid>.navbar-header, .container>.navbar-collapse, .container>.navbar-header { margin-left: 0; margin-right: 0; } p { color: $hometext; } .group { display: inline-block; } .section-header { @include headline(32px, 32px, $homeheading); font-weight: 300; text-align: center; padding-top: 80px; margin-bottom: 26px; color: #444f6c; letter-spacing: -0.5px; @include mobile { font-size: 28px; line-height: 1.45; padding: 60px 25px 0; margin-bottom: 24px; } } .section-header--big { font-weight: 100; margin-bottom: 60px; font-size: 48px; letter-spacing: normal; @include mobile { font-size: 36px; line-height: 46px; margin-left: auto; margin-right: auto; margin-bottom: 0; padding: 0; } } .section-sub-header { width: 55%; margin: 0 auto 32px; text-align: center; font-size: 16px; font-weight: 500; line-height: 30px; color: #888fa1; @include mobile { width: 100%; max-width: 280px !important; } } .section-footer { @include headline(15px, 26px); color: rgba(#444f6c, 0.7); text-align: center; margin-bottom: 30px; } .sub-text { @include headline(14px, 26px); } .section { width: 100%; max-width: 1170px; margin: 0 auto; padding-left: 20px; padding-right: 20px; .row { @include small { flex-direction: column; } } .bullet-points { img { max-width: 100%; margin: 0 auto; display: block; } @include small { width: 100%; margin-top: 33px; margin-bottom: 54px; } } } .section.narrow { max-width: 1024px; position: relative; @include small { padding: 0 15px; } } .section.wider { max-width: 1172px; .section-content { overflow: hidden; } } .flex-container { max-width: 1000px; margin: 0 auto 80px; display: flex; flex-direction: row; justify-content: space-between; align-items: center; flex-wrap: wrap; @include medium { flex-direction: column; } } .section-get-started { .flex-container { max-width: 1030px; margin-bottom: 60px; @include mobile { margin-bottom: 15px; } } .section-header { padding-top: 90px; margin-bottom: 70px; @include mobile { padding-top: 0; margin-top: 60px; margin-bottom: 30px; } } } .gray-background { background-color: $graybackground; } .blue-background { background-color: $bluebackground; } .dark-blue-background { background-color: $darkbluebackground; } .left-align { text-align: left; } .header { height: 600px; background-image: url("../img/shoutem-developers-header.jpg"); background-repeat: no-repeat; background-position: center; background-size: cover; @include mobile { height: 475px; } } .header-wrapper { max-width: 1440px; width: 100%; margin: 0 auto; display: flex; align-items: center; z-index: 2; height: 100%; } .video-screen { width: 279px; height: calc(279px * (650 / 368)); right: 269px; top: 142px; z-index: 100; transform: perspective(1983px) rotateY(-35.5deg) rotateX(11.9deg) rotateZ(16.15deg) scaleY(1.00075); position: absolute; backface-visibility: hidden; transform-style: preserve-3d; @include large-2 { right: 98px; top: 182.5px; } @include medium-2 { display: none; } } .main { color: #fff; // test! height: 100%; &.with-arrow:after { @media screen and (min-width: 480px) { bottom: 20px; } } .header-text { margin: 44px auto 0; @include medium { padding: 0; } @include mobile { max-width: 280px; br { display: none; } } } .with-image .header-text { text-align: left; margin-left: 5%; @media (min-width: 1440px) { margin-left: 235px; } @include large-2 { margin-left: 110px; max-width: 55%; } @include medium-2 { margin: 20px auto 0; max-width: 600px; } @include mobile { margin: -22px auto 0; max-width: 280px; } } .main-heading { color: #FFFFFF; font-size: 54px; line-height: 61px; font-weight: 100; margin-top: 0; margin-bottom: 0; text-align: center; max-width: 800px; @include medium-2 { font-size: 48px; } @include small { font-size: 36px; font-weight: 300; } @include mobile { font-size: 24px; font-weight: 500; line-height: 38px; } } .with-image .main-heading { text-align: left; @include medium-2 { text-align: center; } } .p, .sub-heading { color: #EFEFF2; font-size: 16px; font-weight: normal; line-height: 26px; margin-bottom: 0; margin-left: auto; margin-right: auto; text-align: center; max-width: 550px; margin-top: 27px; } .with-image .sub-heading { margin-top: 23px; text-align: left; font-size: 16px; font-weight: 500; max-width: 380px; @include large-2 { br { display: none; } } @include medium-2 { text-align: center; max-width: 360px; margin-left: auto; margin-right: auto; } @include mobile { text-align: center; font-weight: 500; margin-top: 12px; } } .os-icons { text-align: center; margin-top: 27px; font-size: 0; img:not(:last-child) { margin-right: 32px; } .icon-android { position: relative; margin-top: 3px; } } } .with-arrow:after { content: ""; position: absolute; bottom: 50px; left: 50%; transform: translateX(-50%); background: url("../img/icon-scroll-down.png") no-repeat center / 100%; display: block; height: 78px; width: 33px; } .separator { width: 100%; height: 1px; background: rgba(49, 60, 89, 0.1); margin: 0 auto 21px; } .step-label { display: block; margin-bottom: 19px; strong { font-size: 14px; border: rgba(49, 60, 89, 0.2) solid 1px; border-radius: 4px; padding: 7px 14px 5px; color: #313c59; line-height: 1.4; letter-spacing: 1.2px; color: #444f6c; font-weight: 700; text-transform: uppercase; position: relative; top: -6px; text-align: center; } @include medium-2 { margin: 5px auto 20px; } } .steps { padding-left: 0; list-style: none; } .steps li { text-align: center; min-height: 130px; .step-title { font-weight: bold; text-transform: uppercase; margin-bottom: 16px; font-size: 13px; line-height: 20 px; letter-spacing: 1.08333px; color: #444F6C; @include medium-2 { margin: 20px 0; } @include medium-2 { margin: 20px 0 15px; } } .step-button { text-transform: uppercase; color: #fff; font-weight: bold; font-size: 13px; padding: 0 24px; height: 90px; width: 260px; border-top-left-radius: 4px; border-bottom-left-radius: 4px; box-shadow: none; letter-spacing: 1px; line-height: 24px; &.publish-button { background-color: $green; border: 1px solid $green; border-radius: 4px; position: relative; @include medium-2 { height: 70px; } } span { display: block; } @include medium-2 { margin-right: 0; left: 0; border-radius: 4px; width: 280px; height: 75px; } } .arrow-box { position: relative; background: $accent; border: 1px solid $accent; padding: 0 16px 0 24px; &:after, &:before { left: 100%; top: 50%; border: solid transparent; content: " "; height: 0; width: 0; position: absolute; pointer-events: none; @include medium-2 { border-top-color: #00aadf; border-left-color: transparent !important; border-width: 138px !important; border-radius: 4px; transform: scaleY(0.1); left: 0; bottom: -153px; top: auto; margin-top: 0 !important; } } &:after { border-left-color: $accent; border-width: 44px; margin-top: -44px; } &:before { border-left-color: $accent; border-width: 45px; margin-top: -45px; margin-left: 1px; @include medium-2 { left: 1px; bottom: -152px; } } @include medium-2 { margin-bottom: 20px; } } } .section-step0 { padding-bottom: 100px; .section-header { padding-top: 90px; @include small { padding-top: 40px; } } .section-footer { width: 60%; margin: 0 auto; font-weight: normal; @include small { max-width: 280px; width: 100%; line-height: 26px; } } .flex-container { background: linear-gradient(to bottom, transparent 45px, rgba(0, 170, 223, 0.1) 45px); max-width: 970px; margin-bottom: 48px; @include medium-2 { flex-direction: column; overflow: hidden; background: none; } @include small { margin-bottom: 42px; background: none; } } &.with-arrow:after { height: 88px; background-image: url("../img/image-arrow-blue.svg"); background-size: contain; bottom: -32px; z-index: 3; @include small { bottom: -44px; } } @include small { padding-bottom: 48px; } } .section-customization { background-color: white; padding-bottom: 100px; .section-header { padding-top: 82px; @include mobile { font-size: 24px; } } .separator { margin-bottom: 8px; width: 299px; @include medium { max-width: 100%; } } .builder-image { align-self: flex-end; margin-right: 0; img { max-width: 100%; @include small { width: 100%; } @include mobile { max-width: 335px; } } @include medium-2 { width: calc(100% - 360px); } @include small { width: 100%; padding: 0; text-align: center; position: relative; &:before { content: " "; height: 1px; width: calc(100% - 40px); max-width: 335px; background: #000; opacity: 0.1; position: absolute; bottom: 0; z-index: 1050; } } } .row { display: flex; align-items: center; margin-left: -65px; margin-bottom: -30px; @include small { margin: 0; } } .bullet-points { padding: 0; margin: -30px 0 0 8.3333333333%; img { max-width: 100%; margin: 0 auto; display: block; } .integrations { padding-top: 5px; @include medium-2 { img { margin: 0; } } } li:not(.integrations) { margin-right: 0; } @include large { margin-left: 45px; } @media (max-width: 1120px) { margin-left: 15px; } @include large { width: 420px; margin-top: 0; } @include small { width: 100%; margin-top: 33px; margin-bottom: 54px; margin-left: 0; } } &.with-arrow:after { height: 88px; background-image: url("../img/image-arrow-blue.svg"); background-size: contain; bottom: -5px; margin-left: -225px; z-index: 3; @include small { bottom: -44px; margin-left: 0; } } .dragdealer { .handle { cursor: move; cursor: -webkit-grab; cursor: grab; transition: transform 0.5s ease-out; } &.active .handle { cursor: move; cursor: -webkit-grabbing; cursor: grabbing; } } .screen-type-cards, .visual-style-cards, .layout-cards { display: block; position: relative; max-width: 1170px; padding-bottom: 3px; padding-left: 0; padding-right: 0; overflow: hidden; border-radius: 4px; margin-top: 60px; .subtitle { font-size: 18px; font-weight: normal; line-height: 26px; text-align: center; margin: 0 0 32px; } &::after { content: ""; position: absolute; top: 0; right: 0; width: 13px; height: 100%; background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.05)); pointer-events: none; } .card { float: left; margin: 0 0 20px; width: 190px; text-align: left; .inner { margin: 0; width: 190px; height: 214px; border-radius: 4px; overflow: hidden; box-shadow: 0 0 2px 0 rgba(0,0,0,0.03), 0 2px 2px 0 rgba(0,0,0,0.06); } &:not(:last-child) { width: 214px; .inner { margin-right: 24px; } } img { display: block; max-width: 100%; } .card-title { margin: 16px 0 0; padding: 0 16px; font-size: 16px; line-height: 20px; font-weight: normal; text-transform: none; color: $primary; letter-spacing: 0; } p { font-size: 14px; margin: 3px 0 0; padding: 0 16px; line-height: 22px ; height: 3em; overflow: hidden; } } } .visual-style-cards { margin-top: 60px; .card { width: 154px; .inner { width: auto; height: auto; padding: 0; background-color: transparent; margin: 0; border-radius: 2px; box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.12); } &:not(:last-child) { width: 186px; .inner { margin-right: 32px; } } } } } .section-step2 { .row { display: flex; align-items: center; @include small { margin: 0; } } .section-header { padding-top: 89px; margin-bottom: 107px; @include medium-2 { margin-bottom: 48px; padding-top: 48px; } } .wider { max-width: 1184px; } .image-rn-code { max-width: 642px; margin-left: 10px; margin-bottom: 100px; padding: 0; &.with-arrow:after { height: 88px; background-image: url("../img/image-arrow-blue.svg"); background-size: contain; bottom: -83px; z-index: 3; @include small { bottom: -335px; } } img { max-width: 100%; } @include medium-2 { width: calc(100% - 420px); margin-bottom: 48px; } @include small { order: -1; margin-left: -20px; margin-right: -20px; width: 100%; } } .builder-screenshot { @include logoimage('../img/image-builder-screenshot@2x.png', 669px, 372px); @include medium { display: none; } } .bullet-points { margin: -106px 0 0 78px; .bullet-icon { margin-right: 20px; } @include medium-2 { width: 360px; margin-top: 0; } @include small { width: 100%; margin: 0 0 50px; } } } .section-step3 { background-color: #313C59; min-height: 636px; color: #ffffff; .col-md-6 { @include small { width: 100%; margin-left: 0; } } @include small { min-height: 1165px; text-align: center; } .step-label { strong { color: #7ed321; border: solid 1px #7ed321; } @include small { margin: 10px auto 15px; } } .bullets-wrapper { display: block; text-align: left; .bullet-points { margin-left: auto; margin-right: -3px; max-width: 380px; width: 100%; .bullet { h4 { color: #ffffff; font-size: 22px; line-height: 26px; font-weight: normal; margin: 0; } p { margin: 8px 0 0; max-width: 260px; font-size: 15px; line-height: 24px; color: rgba(#BDC0CB, 0.7); } &:not(:first-child) { h4 { margin-top: 40px; } } @include medium-2 { padding-left: 106px; } @include mobile { padding-left: 72px; } &.bullet-publish { background: url(../img/publish.svg) 100% 6px no-repeat; @include medium-2 { background-position: 0% 6px; } @include mobile { background-size: 45px; } } &.bullet-manage { background: url(../img/manage.svg) calc(100% - 19px) 12px no-repeat; @include medium-2 { background-position: 0% 6px; } @include mobile { background-size: 45px; } } &.bullet-analyse { background: url(../img/analyse.svg) calc(100% - 14px) 25px no-repeat; @include medium-2 { background-position: 0% 25px; } @include mobile { background-size: 45px; } } } @include medium-2 { margin: auto; } } } .section-header { padding-top: 75px; text-align: center; margin-bottom: 0; color: #F2F2F5; @include small { padding-top: 40px; margin-bottom: 20px; text-align: center; } } .section-sub-header { width: 100%; margin-top: 20px; margin-bottom: 0; text-align: center; @include small { text-align: center; margin: 0 auto 30px; max-width: 450px; } } .col { margin-top: 52px; &.text { margin-top: 147px; margin-bottom: 191px; @include medium-2 { margin-bottom: 64px; margin-top: 48px; } } &.carousel { background: url(../img/mbp-bezel.svg) left top no-repeat; height: 580px; overflow: hidden; @include medium-2 { background-position: calc(50% + 3px) 0; padding: 0; margin-top: 0; } @include small { background-position: calc(50% + 2px) 0; background-size: 641px auto; } .images { position: relative; top: 57px; left: 66px; @include medium-2 { text-align: center; left: 0; margin-left: auto; margin-right: auto; } @include small { width: 521px; top: 40px; img { max-width: 100%; } } } } } p { font-size: 18px; } .group { @include small { display: block; margin-left: 30px; margin-top: 10px; } } .section.fullwidth { max-width: 100%; padding: 0 0 0 46px; @include medium-2 { padding: 0; } } } .bullet-points { margin: 0; padding: 0; list-style: none; } .bullet-points li { line-height: 1.5; margin-bottom: 23px; padding: 0; } .bullet-icon { display: inline-block; margin-right: 20px; vertical-align: middle; width: 24px; height: 24px; padding: 4px; background-position: 0 0; @include logoimage('../img/icon-bullet-circle.svg', 24px, 24px); &.react { @include logoimage('../img/icon-react-logo.svg', 30px, 30px); background-position: center; margin: -2px 0 0 8px !important; } } .bullet-text { vertical-align: top; font-size: 15px; line-height: 24px; width: calc(100% - 44px); max-width: 245px; display: inline-block; &.react-native { max-width: 295px; a { display: inline-block; } } @include mobile { font-size: 15px; line-height: 24px; } } .arrow { @include logoimage('../img/image-arrow-blue.svg', 28px, 88px); position: absolute; bottom: -40px; z-index: 1; &.first, &.second { left: 50%; } &.third { left: 20%; } } .app-store-logo { position: relative; bottom: 2px; padding: 0 12px; } .section-step4 { background: #ffffff; padding-bottom: 100px; .section-header { padding-top: 90px; margin-bottom: 23px; font-size: 45px; @include medium-2 { font-size: 36px; padding-top: 48px; } } } .section-featured { .featured-apps { text-align: center; margin-top: 64px; a { width: 145px; height: auto; display: inline-block; vertical-align: top; &:not(:last-child) { margin-right: 48px; } img { width: 100%; max-width: 85px; } p { font-size: 14px; font-weight: normal; line-height: 20px; color: #222B35; margin-top: 26px; } } } p { text-align: center; font-size: 15px; font-weight: normal; color: #888fa1; line-height: 25px; max-width: 380px; margin: 0 auto; } } .chapter { width: 310px; min-height: 400px; background-color: #fff; box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1), 0 0 2px 0 rgba(0, 0, 0, 0.1); transition: box-shadow ease-out 0.2s; cursor: pointer; margin-bottom: 20px; overflow: hidden; @include border-radius(4px); @include medium-2 { width: 270px; } /* just until Elvis figures something better :) */ @media only screen and (max-width: 882px) { width: 240px; } @include medium { width: 100%; max-width: 480px; } @include mobile { width: calc(100% - 18px); max-width: 280px; margin-bottom: 40px; } &:hover, &:active { box-shadow: 0 19px 38px 0 rgba(0, 0, 0, 0.3), 0 15px 12px 0 rgba(0, 0, 0, 0.2); text-decoration: none; } &:last-child .chapter-content { padding-right: 15px; } .chapter-content { padding: 24px; .chapter-title { line-height: 140%; margin-bottom: 10px; } .chapter-description { font-size: 15px; line-height: 24px; } .chapter-time { @include headline(14px, 24px, $chapterfooter); @include flexcontainer(); justify-content: initial; .time-icon { display: inline-block; @include logoimage('../img/icon-time.svg', 19px, 19px); margin-right: 8px; } } } .chapter-image { width: 100%; height: 180px; @include flexcontainer(); &.blue { background-color: #58c6fb; } &.orange { background-color: #fe9827; } &.purple { background-color: #bd10e0; } &.darkblue { background-color: #0f75fb; } &.greyblue { background-color: #444f6c; } .getting-started-icon { @include logoimage('../img/icon-tutorial-getting-started.svg', 75px, 93px); } .restaurant-app-icon { @include logoimage('../img/icon-tutorial-restaurant-app.svg', 80px, 82px); } .shopping-app-icon { @include logoimage('../img/icon-tutorial-shopping-app.svg', 104px, 58px); margin-left: -50px; } .loyalty-app-icon { @include logoimage('../img/icon-tutorial-loyalty-app.svg', 104px, 51px); margin-left: -30px; } .ui-toolkit-icon { @include logoimage('../img/icon-tutorial-ui-toolkit.svg', 47px, 77px); } .shoutem-icon { @include logoimage('../img/icon-tutorial-shoutem.svg', 113px, 35px); } } } .documentation-category, .chapter-category { font-size: 12px; font-weight: bold; color: $muted; letter-spacing: 1px; text-transform: uppercase; line-height: 1; margin-top: 0; } .documentation-group { width: 270px; margin-bottom: 80px; @include medium { margin: 0 auto 30px; } .documentation-link { position: relative; padding-left: 40px; color: $homeheading; line-height: 30px; display: block; padding-top: 20px; cursor: hover; &:hover { color: $homeheading; text-decoration: none; } .link-icon { @include logoimage('../img/icon-reference.svg', 24px, 30px); position: absolute; left: 0; opacity: 0.6; } } } .feature { width: 270px; min-height: 230px; @include medium { margin: 0 auto 30px; } .feature-title { font-size: 17px; opacity: 0.8; margin-bottom: 10px; } .feature-icon { margin-bottom: 30px; &.extensions { @include logoimage('../img/icon-features-extensions.svg', 24px, 24px); } &.styling { @include logoimage('../img/icon-features-styling.svg', 24px, 24px); } &.command-line { @include logoimage('../img/icon-features-command-line.svg', 9px, 16px); } &.analytics { @include logoimage('../img/icon-features-analytics.svg', 24px, 24px); } &.push-notifications { @include logoimage('../img/icon-features-push-notifications.svg', 24px, 24px); } &.monetization { @include logoimage('../img/icon-features-monetization.svg', 14px, 24px); } &.extendable { @include logoimage('../img/icon-features-extendable.svg', 24px, 24px); } &.app-configuration { @include logoimage('../img/icon-features-app-configuration.svg', 16px, 24px); } &.auto-updated { @include logoimage('../img/icon-features-auto-updated.svg', 24px, 24px); } &.scalable { @include logoimage('../img/icon-features-scalable.svg', 24px, 24px); } &.cloud-storage { @include logoimage('../img/icon-features-cloud-storage.svg', 28px, 20px); } &.rest-json-api { @include logoimage('../img/icon-features-rest-json-api.svg', 24px, 24px); } } } .app { width: 170px; min-height: 120px; text-align: center; .app-icon { height: 85px; width: 85px; background-repeat: no-repeat; background-size: 100%; margin: 0 auto 25px; &.leadership { background-image: url('../img/image-app-leadership-conference.png'); } &.jax-xplrr { background-image: url('../img/image-app-jax-xplrr.svg'); } &.melbourne-central { background-image: url('../img/image-app-melbourne-central.png'); } &.firmsconsulting { background-image: url('../img/image-app-firmsconsulting.svg'); } &.seln { background-image: url('../img/image-app-seln-social-network.svg'); } &.laughing-place { background-image: url('../img/image-app-laughing-place.png'); } } } .company { width: 170px; height: 80px; @include flexcontainer(); .company-logo { &.usa-today { @include logoimage('../img/logo-usa-today.svg', 100px, 53px); } &.t-mobile { @include logoimage('../img/logo-t-mobile.svg', 129px, 23px); } &.tnw { @include logoimage('../img/logo-tnw.svg', 68px, 31px); } &.techcrunch { @include logoimage('../img/logo-techcrunch.svg', 154px, 23px); } &.mashable { @include logoimage('../img/logo-mashable.svg', 126px, 23px); } &.entrepreneur { @include logoimage('../img/logo-entrepreneur.svg', 144px, 29px); } } } ================================================ FILE: css/prism.css ================================================ /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+bash+json+jsx&plugins=line-highlight */ /** * prism.js default theme for JavaScript, CSS and HTML * Based on dabblet (http://dabblet.com) * @author Lea Verou */ code[class*="language-"], pre[class*="language-"] { color: black; background: none; text-shadow: 0 1px white; font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; word-wrap: normal; line-height: 1.5; -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; -webkit-hyphens: none; -moz-hyphens: none; -ms-hyphens: none; hyphens: none; } pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { text-shadow: none; background: #b3d4fc; } pre[class*="language-"]::selection, pre[class*="language-"] ::selection, code[class*="language-"]::selection, code[class*="language-"] ::selection { text-shadow: none; background: #b3d4fc; } @media print { code[class*="language-"], pre[class*="language-"] { text-shadow: none; } } /* Code blocks */ pre[class*="language-"] { padding: 1em; margin: .5em 0; overflow: auto; } :not(pre) > code[class*="language-"], pre[class*="language-"] { background: #f5f2f0; } /* Inline code */ :not(pre) > code[class*="language-"] { padding: .1em; border-radius: .3em; white-space: normal; } .token.comment, .token.prolog, .token.doctype, .token.cdata { color: slategray; } .token.punctuation { color: #999; } .namespace { opacity: .7; } .token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol, .token.deleted { color: #905; } .token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: #690; } .token.operator, .token.entity, .token.url, .language-css .token.string, .style .token.string { color: #a67f59; background: hsla(0, 0%, 100%, .5); } .token.atrule, .token.attr-value, .token.keyword { color: #07a; } .token.function { color: #DD4A68; } .token.regex, .token.important, .token.variable { color: #e90; } .token.important, .token.bold { font-weight: bold; } .token.italic { font-style: italic; } .token.entity { cursor: help; } pre[data-line] { position: relative; padding: 1em 0 1em 3em; } .line-highlight { position: absolute; left: 0; right: 0; padding: inherit 0; margin-top: 1em; /* Same as .prism’s padding-top */ background: hsla(24, 20%, 50%,.08); background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); pointer-events: none; line-height: inherit; white-space: pre; } .line-highlight:before, .line-highlight[data-end]:after { content: attr(data-start); position: absolute; top: .4em; left: .6em; min-width: 1em; padding: 0 .5em; background-color: hsla(24, 20%, 50%,.4); color: hsl(24, 20%, 95%); font: bold 65%/1.5 sans-serif; text-align: center; vertical-align: .3em; border-radius: 999px; text-shadow: none; box-shadow: 0 1px white; } .line-highlight[data-end]:after { content: attr(data-end); top: auto; bottom: .4em; } ================================================ FILE: css/style.scss ================================================ --- --- @import "colors"; @import "mixins"; @import "base"; @import "navbar"; @import "typography"; @import "github"; ================================================ FILE: csv/restaurants.csv ================================================ Name,Address,Description,Website,Image,E-mail "Gaspar Brasserie","185 Sutter St, San Francisco, CA 94109","Expect an intimate venue with the ambience of a private club. The mood is casual, the guests sublime.","gasparbrasserie.com","http://shoutem.github.io/static/getting-started/restaurant-1.jpg","info@gasparbrasserie.com" "Chalk Point Kitchen","527 Broome St, New York, NY 10013","Stylish restaurant serving market-to-table American fare in modern farmhouse digs with cellar bar.","http://www.chalkpointkitchen.com/","http://shoutem.github.io/static/getting-started/restaurant-2.jpg","feedme@chalkpointkitchen.com" "Kyoto Amber Upper East","225 Mulberry St, New York, NY 10012","Amber Upper East is located on the corner of 80th and 3rd Avenue. We serve Japanese and Asian cuisines.","https://www.opentable.com/amber-upper-east","http://shoutem.github.io/static/getting-started/restaurant-3.jpg","-" "Sushi Academy","1900 Warner Ave. Unit A Santa Ana, CA","The educational philosophy of the California Sushi Academy crosses borders, race and gender to bring greater Japanese cultural understanding and appreciation through the culinary experience.","http://www.sushi-academy.com/","http://shoutem.github.io/static/getting-started/restaurant-4.jpg","email@sushi-academy.com" "Sushibo","35 Sipes Key, New York, NY 10012","We are a catalan family company. From the beginning, on September 2010, Sushibo has been the operating brand of Harakirifish SL in Barcelona. Our passion for authentic, fresh Japanese cooking has brought us to connect with an extensive base of clients, as well as hotels and several businesses all around the city.","http://www.sushibo.cat/en/","http://shoutem.github.io/static/getting-started/restaurant-5.jpg","info@sushibo.cat" "Mastergrill","550 Upton Rue, San Francisco, CA 94109","Master Grill by Marko is a longtime brand owned by Mark Stojkanovic, a grill specialist trained in Leskovac, on a notable international school of top grill.","http://www.master-grill.com/master-grill.html","http://shoutem.github.io/static/getting-started/restaurant-6.jpg","-" ================================================ FILE: docs/cloud/_posts/1970-01-01-ShoutemCloud.md ================================================ --- layout: doc permalink: /docs/cloud/introduction title: Introduction section: Shoutem Cloud --- # Shoutem Cloud Shoutem Extensions can be used with any server. For easier integration with backend, we've prepared a library called [@shoutem/redux-io](https://github.com/shoutem/redux-io), a layer on top of [redux](http://redux.js.org/docs/introduction/) which makes it easy to manage data fetching lifecycle. If you don't have a server to connect with, you can use Shoutem Cloud. Shoutem Cloud is one of the main components of the Shoutem platform. Using Shoutem Cloud, you don't need to worry about developing your own backend with all the problems that come along: optimisations for typical CRUD operations, scaling and security. [//]: # (Add picture of Shoutem Cloud) To use Shoutem Cloud with extensions, we need to create [data schemas]({{ site.url }}/docs/extensions/my-first-extension/using-cloud-storage) which describe structure of the data that should be stored on Shoutem Cloud. When used in extensions, the `@shoutem/redux-io` library is by default configured to Shoutem Cloud, but it can be configured for use with any API. We've also prepared a [data schema reference]({{ site.url }}/docs/cloud/data-schemas). You can see an implementation of how a custom extension uses Shoutem Cloud storage in our [My First Extension]({{ site.url }}/docs/extensions/my-first-extension/introduction) tutorial in the Using Cloud Storage and Working with Data sections. However, we recommend going through the entire tutorial as a whole. ================================================ FILE: docs/cloud/_posts/1970-01-02-DataSchemas.md ================================================ --- layout: doc permalink: /docs/cloud/data-schemas title: Introduction section: Data Schemas --- # Data Schemas Data Schema describes what the data will look like. Schemas are a `type` of data. The format of the data schema is nothing more than a Shoutem flavored [JSON schema](https://spacetelescope.github.io/understanding-json-schema/UnderstandingJSONSchema.pdf). ## Usage in extensions Create a data schema with: ```ShellSession $ shoutem schema add ``` where you should replace `` with the name of your Data Schema name. Example: ```ShellSession $ shoutem schema add Restaurants Schema `Restaurants` is created in file `server/schemas/Restaurants.json`! File `extension.json` was modified. ``` Data schema is created in the `server/data-schemas` folder. Its default content is: ```JSON { "title": "Restaurants", "properties": { "name": { "format": "single-line", "title": "Name", "type": "string", "displayPriority": 1 } }, "titleProperty": "name", "type": "object" } ``` Root JSON fields that are immediately included are: - `title`: Title of the schema shown on the CMS page - `properties`: Properties of each object created from that data schema - `titleProperty`: Property used as title of object in the list - `type`: Type of data in JavaScript. It can only be `object` Field `properties` is an object containing _keys_ as names of object properties and _values_ as descriptors of property's value. Shoutem flavored `properties` can't have children. Below is the reference for value descriptor. ## Value descriptor reference With the value descriptor Shoutem builder knows which input fields to render on the CMS page. These input fields along with the `title` property explain to the application owner which kind of data they expect. ### Value types Each value type has a combination of `type`, `format` and sometimes additional properties in the value descriptor which define type of the value. We call them _value signatures_. Referencing other data schemas is enabled by using `referencedSchema` field. Below are the signatures and examples for each value type that can be created with data schemas. > #### Note > JSON schema defines the types that can be used. It also provides some built-in formats. However, Shoutem uses its own flavored formats. #### Single-line string Signature: ```JSON "type": "string", "format": "single-line" ``` Example: ```JSON "name": { "type": "string", "format": "single-line", "title": "Name", "required": true } ``` #### Multi-line string Signature: ```JSON "type": "string", "format": "multi-line" ``` Example: ```JSON "description": { "type": "string", "format": "multi-line", "title": "Description", "minLength": 10, "maxLength": 1000 }, ``` #### Integer Signature: ```JSON "type": "integer", "format": "integer" ``` Example: ```JSON "rating": { "type": "number", "format": "number", "title": "Rating", "minimum": 0, "maximum": 10 } ``` #### Boolean Signature: ```JSON "type": "boolean", "format": "boolean" ``` Example: ```JSON "offersWifi": { "type": "boolean", "format": "boolean", "title": "Offers WIFI" } ``` #### Array Signature: ```JSON "type": "array", "format": "array" ``` Example: ```JSON "genericArray": { "type": "array", "format": "array", "title": "Generic JS Array" }, ``` #### Generic object Signature: ```JSON "type": "object", "format": "object" ``` Example: ```JSON "genericObject": { "type": "object", "format": "object", "title": "Generic JS Object" } ``` #### Date time Signature: ```JSON "type": "object", "format": "date-time" ``` Example: ```JSON "openedSince": { "type": "object", "format": "date-time", "title": "Opened Since" } ``` #### Location Signature: ```JSON "type": "object", "format": "geolocation" ``` Example: ```JSON "placeOfBirth": { "type": "object", "format": "geolocation", "title": "Place of birth" } ``` #### Image Signature: ```JSON "type": "object", "format": "attachment", "referencedSchema": "shoutem.core.image-attachments" ``` Example: ```JSON "image": { "type": "object", "format": "attachment", "title": "Restaurant's image", "referencedSchema": "shoutem.core.image-attachments" } ``` #### Rich media Signature: ```JSON "type": "string", "format": "html" ``` Example: ``` "info": { "type": "string", "format": "html", "title": "Info", "maxLength": 10000 } ``` #### Custom referenced schema - single object Signature: ```JSON "type": "object", "format": "entity-reference", "referencedSchema": "<>" ``` Example: ```JSON "Restaurant": { "type": "object", "format": "entity-reference", "title": "Best restaurant", "referencedSchema": "shoutem.restaurants.Restaurants" } ``` > #### Note > The absolute data schema reference is formated following structure {developerName}.{extensionName}.{extensionPartName} as explained in [Creating shortcut and screen]({{ site.url }}/docs/extensions/my-first-extension/shortcut-and-screen) tutorial #### Custom referenced schema - array Signature: ```JSON "type": "object", "format": "entity-reference-array", "referencedSchema": "<>" ``` Example: ```JSON "News": { "type": "object", "format": "entity-reference-array", "title": "News", "referencedSchema": "shoutem.news.News" } ``` ### Property order As `properties` are a dictionary and dictionaries are by nature unordered, we added a `displayPriority` property in the value descriptor which you can use to define the order in which properties are shown on the Shoutem CMS interface. A valid value of `displayPriority` property is an integer - the lower the integer, the higher the property will be shown in the interface. Display priority is an optional property. Properties which omit it will be rendered in an arbitrary order after all the properties with `displayPriority` defined. ### Additional descriptor properties The value descriptor along with _value type_ can also describe additional information for a particular value. These fields are inherited from the JSON Schema specification: - `properties.pattern` - regex pattern constraint, applicable only to `string` primitive type - `properties.required` - required constraint - `properties.minLength` and `properties.maxLength` - `string` length constraints, applicable only to `string` primitive type - `properties.maximum` and `properties.minimum` - value range constraints, applicable only to number and integer primitive types These additional descriptor properties allow us to create arbitrary types, such as generally used e-mail: ```JSON "email": { "type": "string", "format": "single-line", "title": "Email", "minLength": 3, "maxLength": 100, "pattern": "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" }, ``` ================================================ FILE: docs/extensions/my-first-extension/_posts/1970-01-01-Introduction.md ================================================ --- layout: doc permalink: /docs/extensions/my-first-extension/introduction title: Introduction section: My First Extension --- # My First Extension
This tutorial will show you how to write custom **Shoutem extensions** on the Shoutem platform. It introduces the most important concepts. After completing it, you will have a running **mobile app** that uses the your brand new **custom extension** with components from the [Shoutem UI Toolkit]({{ site.url }}/docs/ui-toolkit/introduction) and retrieves content from the [Shoutem Cloud]({{ site.url }}/docs/cloud/introduction). The final result of this tutorial-made extension is [open sourced](https://github.com/shoutem/extension-examples/tree/master/restaurants-getting-started). Before you start the My First Extension tutorial series, you should go through [Getting Started]({{ site.url }}/docs/extensions/tutorials/getting-started), because this tutorial series builds on top of the simple extension you created there. Here's a preview of what the completed app will look like.

## What are Extensions? Extensions represent features in the app. The app owner picks extensions that he wants to use in his app through the Shoutem Builder. Shoutem prepared a bunch of [open sourced](https://github.com/shoutem/extensions) extensions which you can easily customize to fit your needs.

You can develop extensions both within apps made by `shoutem clone` and as stand-alone extensions that you plan on using within multiple apps. In this tutorial, we'll be working on the Restaurants extension we made in Getting Started, so it'll be treated as one within a _cloned_ app. ## About the Technology
Shoutem uses [React](https://facebook.github.io/react/) and [React Native](https://facebook.github.io/react-native/) as frameworks for building cross-platform apps. React is an open source JavaScript library that provides a way to build user interfaces (UIs), while React Native exposes iOS and Android **native** components so they can be used in React environment. If you haven't used these technologies before, our [React Native school](http://school.shoutem.com/) can help you get started! We use [JSX](https://facebook.github.io/react/docs/introducing-jsx.html) as a simple way to write UI components with tags. Building an app screen with JSX is as easy as:

On top of React and React Native, we're using [Redux](http://redux.js.org/), a library which simplifies state management.

Even though _we_ use Redux, _you_ can use any other state management library (like [MobX](https://github.com/mobxjs/mobx), or just don't use any at all! Again, we don't want to restrict you on how you use React Native. ## Create an Extension The best way to understand the power of extensions is to get your hands dirty. Let's write some code! ================================================ FILE: docs/extensions/my-first-extension/_posts/1970-01-03-InitializingExtension.md ================================================ --- layout: doc permalink: /docs/extensions/my-first-extension/initializing-extension title: Creating an Extension section: My first extension --- # Creating an Extension Here's the mockup of the Restaurants extension that we saw in the [Introduction]({{ site.url }}/docs/extensions/my-first-extension/introduction). Through the course of this tutorial, we'll turn the **Restaurants** extension from [Getting Started]({{ site.url }}/docs/extensions/tutorials/getting-started) into this list of restaurants.

The left app screen lists the restaurants and the right one shows the details of each specific restaurant when you tap on it. ## Initialization Let's revise what we did in [Getting Started]({{ site.url }}/docs/extensions/tutorials/getting-started) (which you should go through before starting this tutorial series). We cloned the app we made on the Builder and initialized a new extension in the app with basic information using `shoutem init`, which created a folder and bootstrapped it with extension files. ```ShellSession $ shoutem init restaurants Enter information about your extension. Press `return` to accept (default) values. Title: Restaurants Version: 0.0.1 Description: A restaurants extension. ``` This information is stored in the `extension.json` file. > #### Note > In case you can't remember the structure of some command, type `shoutem -h` or `shoutem -h` where you should replace `` with one of the [CLI commands]({{ site.url }}/docs/extensions/reference/cli). ## Folder Structure The initialization process will generate the skeleton with folders and files. Our new extension's structure looks like this: ``` restaurants/ ├ app/ | ├ node_modules/ | ├ extension.js | ├ index.js | └ package.json ├ server/ | ├ node_modules/ | └ package.json └ extension.json ``` Let's explain the structure: - `app/`: Folder where you keep your mobile app side code (this will be bundled into the app) - `server/`: Folder where you keep your server side code and assets - `extension.json`: File that describes your extension Specific parts will be explained soon. In `extension.json` you can see: ```JSON #file: extension.json { "name": "restaurants", "version": "0.0.1", "title": "Restaurants", "description": "A restaurants extension.", "platform": "1.0.*" } ``` Brief property explanations: - `name` uniquely identifies the extension when combined with your developer name (e.g. `{{ site.example.devName }}.restaurants`) - `version` is the extension version - `platform` indicates the version of the [plaform]({{ site.url }}/docs/extensions/reference/platform) (versions of React, React Native and other packages available to all extensions by default) - `title` and `description` are extension descriptors We also uploaded our extension to Shoutem: ```ShellSession $ shoutem push Uploading `Restaurants` extension to Shoutem... Success! ``` And installed it into our app: ```ShellSession $ shoutem install Extension installed. See it in the builder: {{ site.shoutem.builderURL }}/app/{{ site.example.appId }} ``` Uploading the extension is self-explanatory, but let's elaborate on installing and uninstalling extensions. In the Builder, you can go to the `Extensions` tab to see which extensions are installed in your app. If you successfully installed your Restaurants extension from [Getting Started]({{ site.url }}/docs/extensions/tutorials/getting-started), you should see it there under the `Custom` category.

Extensions are installed into specific apps, not all apps on your account. Now let's elaborate on [screens and shortcuts]({{ site.url }}/docs/extensions/my-first-extension/shortcut-and-screen). ================================================ FILE: docs/extensions/my-first-extension/_posts/1970-01-04-CreatingShortcutAndScreen.md ================================================ --- layout: doc permalink: /docs/extensions/my-first-extension/shortcut-and-screen title: Creating a Screen and Shortcut section: My first extension --- # Creating a Screen and Shortcut Extensions can have multiple screens in the app. Screens are [React components](https://facebook.github.io/react/docs/react-component.html) that represent a mobile screen. We want our Restaurants extension to have 2 screens; one for the list of the restaurants (which we already made in [Getting Started]({{ site.url }}/docs/extensions/tutorials/getting-started)) and another for the details of each particular restaurant the user taps. Since the app needs to know which screen to open first for some extension, we need to create a ***shortcut*** when creating that screen. A shortcut is a link to the starting screen of an extension. It's the item in the Main Navigation which opens the starting screen when a user taps on it. We created the List screen with a shortcut in [Getting Started]({{ site.url }}/docs/extensions/tutorials/getting-started) using: ```ShellSession $ shoutem screen add List ? Screen name: List ? Create a shortcut (so that screen can be added through the Builder)? Yes ? Shortcut name: {{ site.example.extensionName }} ? Shortcut title: Restaurants ? Shortcut description: A shortcut for List ... Success ``` The CLI modified the `extension.json` to include the screen and it's shortcut: ```json{7-14} #file: extension.json { "name": "restaurants", "version": "0.0.1", "platform": "1.0.*", "title": "Restaurants", "description": "List of restaurants", "screens": [{ "name": "List" }], "shortcuts": [{ "name": "Restaurants", "title": "Restaurants" "screen": "@.List" }] } ``` They were added inside arrays. The `name` property uniquely identifies these extension parts. A shortcut's `title` is what will be shown in the Main Navigation (in the Builder and in the app). The `screen` property inside `shortcuts` references the screen that will open when a user taps on that shortcut in navigation. When referencing any extension part, we need to say which extension it came from. The full name of extension part follows this structure: `..` (e.g. `{{ site.example.devName }}.restaurants.List)`. For extension parts within the same extension, you can just use `@.` (e.g. `@.List`). `@.` stands for `..` of the current extension. The Shoutem CLI also created `app/screens/` folder with a `List.js` file, which you edited in [Getting Started]({{ site.url }}/docs/extensions/tutorials/getting-started). ## How do Extensions fit into an App? The `app` folder from your extension will be bundled (along with the rest of the extensions your app uses) into the full React Native app. For your extension to use an `npm` package, just install it inside the `app` folder. Below is an example of installing [React Native swiper](https://github.com/leecade/react-native-swiper) (just an example, no need to execute following 2 commands). Locate to the `app` folder and install the package with saving the dependency in the `package.json`: ```ShellSession $ cd app/ $ npm install --save react-native-swiper ``` This package would be installed when bundling your extension into the app. You would be able to access it in any file in the `app` folder. ## Exporting Extension Parts The app expects extensions to export their parts (e.g. screens) in `app/index.js` (that's standard JS practice). Extensions are like libraries and other extensions can reuse what they export from `app/index.js`. The convention is that `app/index.js` is the public API of an extension and shouldn't be changed often. The current `index.js` looks like this: ```JSX #file: app/index.js // Reference for app/index.js can be found here: // http://shoutem.github.io/docs/extensions/reference/extension-exports import * as extension from './extension.js'; export const screens = extension.screens; export const themes = extension.themes; ``` On the other hand, `app/extension.js` is managed by the CLI and you should not change it. When creating screens, the CLI writes their location in `app/extension.js` which are exported in `app/index.js`. ## Previewing Extension Code Changes We already did this in [Getting Started]({{ site.url }}/docs/extensions/tutorials/getting-started), but let's elaborate on it. Since the app is managed through the Builder, we needed to `push` the extension to Shoutem and `install` it into our app so we can use and preview it in the Builder. We then opened the app in the Builder and added the extension's screen to Main navigation. Installing new extensions and adding their shortcuts to the app requires you to reconfigure your local clone, which we also did using `shoutem configure`. Let's preview your app again. We can preview it in the Builder, but it might take some time while the Builder bundles the entire app again. Every time you change an extension, you'd have to _push_ it again and then the Builder would need to re-bundle the whole app to add the changes. It's much faster to [set up your local environment]({{ site.url }}/docs/extensions/tutorials/setting-local-environment) and simply use `react-native run-ios` or `react-native run-android`. Let's preview the app and see where we stopped in [Getting Started]({{ site.url }}/docs/extensions/tutorials/getting-started). ```ShellSession $ react-native run-ios Scanning folders for symlinks in /path/to/Restaurants/node_modules ... ```

> #### Note > In the documentation the preview you see is from the Builder, instead of a screenshot from the Shoutem Preview app or a local emulator. This way you'll see the state of the web interface as well. Now let's make a quick change to the app code so you can see it change in real time on the emulator. Open your `restaurants` extension's `List.js` screen file and add another line of text: ```JavaScript{6} #file: app/screens/List.js export default class List extends Component { render() { return ( Let's eat! Can't do anything on an empty stomach! ); } } ``` After reloading the emulator, your new line of text should be visible immediately:

Your extension only has a simple screen right now, let's add some [UI components]({{ site.url }}/docs/extensions/my-first-extension/using-ui-toolkit). ================================================ FILE: docs/extensions/my-first-extension/_posts/1970-01-05-UsingUIToolkit.md ================================================ --- layout: doc permalink: /docs/extensions/my-first-extension/using-ui-toolkit title: Using UI toolkit section: My first extension --- # Using the UI Toolkit React Native exposes plain iOS and Android native components that you can use, but there's usually a lot of work left to do just to make them look beautiful. Instead, you can use [@shoutem/ui](https://github.com/shoutem/ui), a set of customizable UI components. There are [plenty of components]({{ site.url }}/docs/ui-toolkit/components/typography) that you can use out of the box. ## Creating a Restaurants List Let's create a list of restaurants. Start by importing UI components from the toolkit. ```javascript{9-17,19} #file: app/screens/List.js import React, { PureComponent } from 'react'; import { StyleSheet } from 'react-native'; import { NavigationBar } from 'shoutem.navigation'; import { ImageBackground, ListView, Tile, Title, Subtitle, Overlay, Screen } from '@shoutem/ui'; ``` Notice that you didn't need to install the `@shoutem/ui` package into the `app` folder of your extension. That's because this package will be installed in the extension by the app into which your extension is bundled. All packages installed by the app by default can be found in `peerDependencies` of `app/package.json`. Also, we removed `View` and `Text` from the `react-native` import. We prepared some mockup restaurants data for you. Download [this compressed file](/static/getting-started/restaurants.zip), extract it and copy the extracted `assets` folder into your `app` folder. The `assets` folder contains static restaurants data in `restaurants.json`. Define a method in your `List` class that returns an array of restaurants. ```javascript{3-5} #file: app/screens/List.js export default class List extends PureComponent { getRestaurants() { return require('../assets/restaurants.json'); } ``` Implement a `render` method that will use a `ListView` component. [ListView]({{ site.url }}/docs/ui-toolkit/components/list-view) accepts `data` in the form of an `array` to be shown in the list and `renderRow` is a callback function that defines how each row in the list should look. Add the `renderRow` method and change the implementation of the `render` method: ```JSX{3-14,17-25} #file: app/screens/List.js getRestaurants() {...} // defines the UI of each row in the list renderRow(restaurant) { return ( {restaurant.name} {restaurant.address} ); } render() { return ( this.renderRow(restaurant)} /> ); } ``` Since we've only changed the app code now, we don't need to upload the extension. However, in case you're checking the changes in the Builder, do: ```ShellSession $ shoutem push Uploading `Restaurants` extension to Shoutem... Success! ``` The app preview will be shown after Shoutem bundles the new app. `List` is now showing the list of restaurants.

This looks exactly how we wanted. Try clicking on a one of the restaurants. Nothing happens! We want to open up the screen with a restaurant's details when the user touches a row in the list. ## Creating a Details Screen When a restaurant in the list is touched, we will open the details screen for that restaurant. To make components respond to touches, use the [TouchableOpacity](https://facebook.github.io/react-native/docs/touchableopacity.html) component from React Native. We'll also import Shoutem's `navigateTo` action creator to navigate to another screen and the `ext` function for the name of screen we're navigating to. Let's import these things (find the complete code below): ```javascript{1-5} #file: app/screens/List.js import { TouchableOpacity } from 'react-native'; import { navigateTo } from 'shoutem.navigation'; import { ext } from '../const'; ``` [Connect](https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options) `navigateTo` action creator to redux store. ```javascript{1,3-9,14-18} #file: app/screens/List.js import { connect } from 'react-redux'; export class List extends PureComponent { constructor(props) { super(props); // bind renderRow function to get the correct props this.renderRow = this.renderRow.bind(this); } getRestaurants() {...} } // connect screen to redux store export default connect( undefined, { navigateTo } )(List); ``` > #### Note > Make sure that you remove the `default` from `export default class List extends PureComponent` because there can only be one default export and we want `export default connect` to be it. Now create the Details screen: ```ShellSession $ shoutem screen add Details ? Screen name: Details ? Create a shortcut (so that screen can be added through the Builder)? No Success ``` We didn't create a `shortcut` since this screen isn't the starting screen your extension. Open the restaurants details screen in the `renderRow` function. The `navigateTo` action creator accepts Shoutem `route object` as the only argument with `screen` (full name of screen to navigate to) and `props` (passed to screen) properties. To get the full name of the screen, we'll use the `ext` function, which returns the full name of the extension part passed as its first argument (e.g. returns `tom.restaurants.Details` for `Details`) or the full extension name (e.g. `tom.restaurants`) if no argument is passed. ```JSX{2,5-8,16} #file: app/screens/List.js renderRow(restaurant) { const { navigateTo } = this.props; return ( navigateTo({ screen: ext('Details'), props: { restaurant } })}> {restaurant.name} {restaurant.address} ); } ``` This is what you should end up with in `app/screens/List.js`: ```JSX #file: app/screens/List.js import React, { PureComponent } from 'react'; import { TouchableOpacity } from 'react-native'; import { connect } from 'react-redux'; import { navigateTo, NavigationBar } from 'shoutem.navigation'; import { ImageBackground, ListView, Tile, Title, Subtitle, Overlay, Screen } from '@shoutem/ui'; import { ext } from '../const'; export class List extends PureComponent { constructor(props) { super(props); // bind renderRow function to get the correct props this.renderRow = this.renderRow.bind(this); } getRestaurants() { return require('../assets/restaurants.json'); } // defines the UI of each row in the list renderRow(restaurant) { const { navigateTo } = this.props; return ( navigateTo({ screen: ext('Details'), props: { restaurant } })}> {restaurant.name} {restaurant.address} ); } render() { return ( this.renderRow(restaurant)} /> ); } } // connect screen to redux store export default connect( undefined, { navigateTo } )(List); ``` For the `Details` screen just copy the following code. We're not introducing any new concept here, just using some additional components. ```JSX #file: app/screens/Details.js import React, { PureComponent } from 'react'; import { ScrollView } from 'react-native'; import { Icon, Row, Subtitle, Text, Title, View, ImageBackground, Divider, Tile, } from '@shoutem/ui'; export default class Details extends PureComponent { render() { const { restaurant } = this.props; return ( {restaurant.name} {restaurant.address} {restaurant.description} Visit webpage {restaurant.url} Address {restaurant.address} Email {restaurant.mail} ); } } ``` Now when you reload the app and tap on a restaurant in the list, this is what you get:

If you aren't previewing with an [emulator on your local machine]({{ site.url }}/docs/extensions/tutorials/setting-local-environment), you will have to _push_ the extension: ```ShellSession $ shoutem push Uploading `Restaurants` extension to Shoutem... Success! ``` Looking at the preview, that's exactly what we wanted. However, your app is using static data. Let's connect it to the **Shoutem Cloud**. ================================================ FILE: docs/extensions/my-first-extension/_posts/1970-01-06-UsingCloudStorage.md ================================================ --- layout: doc permalink: /docs/extensions/my-first-extension/using-cloud-storage title: Using Cloud Storage section: My first extension --- # Using Cloud Storage Shoutem Cloud Storage is a CMS solution for mobile apps. We made the [@shoutem/redux-io](https://github.com/shoutem/redux-io) package to simplify the communication with Shoutem CMS. Define a `data schema` to describe your data model, do this in your Restaurant extension directory: ```ShellSession $ shoutem schema add Restaurants Schema `Restaurants` is created in file `server/data-schemas/Restaurants.json`! File `extension.json` was modified. ``` The CLI just created a `data-schemas` folder inside the `server` folder and put `Restaurants.json` in it, which contains the following: ```JSON #file: server/data-schemas/Restaurants.json { "title": "Restaurants", "properties": { "name": { "format": "single-line", "title": "Name", "type": "string", "displayPriority": 1 }, }, "titleProperty": "name", "type": "object" } ``` We're using the `server` folder because data schemas are not part of the app code, but rather the server side of an extension. Data schemas are nothing more than Shoutem-flavored [JSON Schemas](http://json-schema.org/). They describe the data being stored on Shoutem Cloud Storage. All fields are explained in the [data schema reference]({{ site.url }}/docs/cloud/data-schemas). This schema is exported in `extension.json`: ```JSON{18-21} #file: extension.json { "name": "restaurants", "version": "0.0.1", "platform": "1.0.*", "title": "Restaurants", "description": "A restaurants extension.", "screens": [{ "name": "List" }, { "name": "Details" }], "shortcuts": [{ "name": "Restaurants", "title": "Restaurants", "description": "Allow users to browse through list of restaurants" "screen": "@.List", }], "dataSchemas": [{ "name": "Restaurants", "path": "server/data-schemas/Restaurants.json" }] } ``` Currently, your schema only has the `name` property, which we'll use for each restaurants name. Let's add additional properties which we want to have for each restaurant, such as: `address`, `description`, website `url`, `image` and `mail`. ```JSON{4-40} #file: server/data-schemas/Restaurants.json { "title": "Restaurant", "properties": { "name": { "format": "single-line", "title": "Restaurant's name", "type": "string", "displayPriority": 1 }, "address": { "format": "single-line", "title": "Address", "type": "string", "displayPriority": 2 }, "description": { "format": "multi-line", "title": "Description", "type": "string", "displayPriority": 3 }, "url": { "format": "uri", "title": "Website", "type": "string", "displayPriority": 4 }, "image": { "format": "attachment", "title": "Image", "type": "object", "referencedSchema": "shoutem.core.image-attachments", "displayPriority": 5 }, "mail": { "format": "single-line", "title": "E-mail", "type": "string", "displayPriority": 6 } }, "titleProperty": "name", "type": "object" } ``` To enter data for your schema, you need to use settings page. Basically, the [settings pages]({{ site.url }}/docs/extensions/tutorials/settings-pages-introduction) are web pages on the Builder. Extension developers write them to enable app owners to manage their extensions. Shoutem prepared a CMS settings page inside the [shoutem.cms](https://github.com/shoutem/extensions/tree/master/shoutem-cms) extension that you can use to manage data for your `schema` on the Shoutem Cloud. Reference that settings page in the `Restaurants` shortcut and pass it the `Restaurants` schema. The page will appear when an app owner selects the `Restaurants` shortcut on the Builder: ```JSON{17-23} #file: extension.json { "name": "restaurants", "version": "0.0.1", "platform": "1.0.*", "title": "Restaurants", "description": "List of restaurants", "screens": [{ "name": "List" }, { "name": "Details" }], "shortcuts": [{ "name": "Restaurants", "title": "Restaurants", "description": "Allow users to browse through list of restaurants", "screen": "@.List", "adminPages": [{ "page": "shoutem.cms.CmsPage", "title": "Content", "parameters": { "schema": "@.Restaurants" } }] }], "dataSchemas": [{ "name": "Restaurants", "path": "server/data-schemas/Restaurants.json" }] } ``` Let's upload the extension now, since we want to customize the web interface and the extension server side: ```ShellSession $ shoutem push Uploading `Restaurants` extension to Shoutem... Success! ``` Go to the [Builder]({{ shoutem.builderURL }}), open your app and select `Restaurants` under `Main navigation` to see the Shoutem CMS page.

Click on `Create Items` to start adding content. This will open a modal that contains the `CMS` interface, where you can manage the content for your extension. Apps that get content from the Shoutem CMS will immediately show new content once you edit or add it.

Click on `Add item`. This will open a modal for inserting data for the `Restaurants` model, which you defined with your data schema.

Add at least one restaurant. Now you can see the data in the CMS settings page of your Restaurants extension:

Although you've added some restaurants in the Builder, your extension is still coded to use static data from the `assets` folder you set up earlier in the tutorial series. Let's change that and start fetching the data from Shoutem Cloud Storage using the `@shoutem/redux-io` package. ================================================ FILE: docs/extensions/my-first-extension/_posts/1970-01-07-WorkingWithData.md ================================================ --- layout: doc permalink: /docs/extensions/my-first-extension/working-with-data title: Working with Data section: My first extension --- # Working with Data Let's fetch data from the Shoutem Cloud storage to the extension. First, remove the `app/assets` folder, we don't need it anymore. Also remove the `getRestaurants()` function from `List.js`. ```JavaScript{2-4} //remove this: getRestaurants() { return require('../assets/restaurants.json'); } ``` Now create a `reducer.js` file in the `app` folder. ```ShellSession $ cd app $ touch reducer.js ``` This file will contain a `reducer` defining the initial app state and how the state changes. Our [@shoutem/redux-io](https://github.com/shoutem/redux-io) package has `reducers` and `actions` that communicate with the Shoutem CMS. The `storage` reducer retrieves data (eg. restaurants) into a dictionary, while `collection` stores data ID's in an array to persist its order. ```javascript{1-9} #file: app/reducer.js import { storage, collection } from '@shoutem/redux-io'; import { combineReducers } from 'redux'; import { ext } from './const'; // combine reducers into one root reducer export default combineReducers({ restaurants: storage(ext('Restaurants')), allRestaurants: collection(ext('Restaurants'), 'all') }); ``` We've used the `ext` function to get the full schema name (`{{ site.example.devName }}.restaurants.Restaurants`). The root reducer needs to be exported from `app/index.js` as `reducer`, so your app can find it: ```javascript{4,11} #file: app/index.js // Reference for app/index.js can be found here: // http://shoutem.github.io/docs/extensions/reference/extension-exports import reducer from './reducer'; import * as extension from './extension.js'; export const screens = extension.screens; export const themes = extension.themes; export { reducer }; ``` Find more information about extension parts [here]({{ site.url }}/docs/extensions/reference/extension-exports). We will fetch restaurants from **Shoutem Cloud Storage** in the `List` screen with the `find` action creator. Also, we'll use three helper functions from our `@shoutem/redux-io` package: ```javascript{1-6} #file: app/screens/List.js import { find, isBusy, shouldRefresh, getCollection } from '@shoutem/redux-io'; ``` - `isBusy` - data is being fetched, - `shouldRefresh` - should data be (re)fetched, - `getCollection` - merges `storage` dictionary and `collection` ID array into an `array` of objects. The complete code is for `app/screens/List.js` is available below. Fetch data in the `componentDidMount` lifecycle method. ```javascript{2-10} #file: app/screens/List.js export class List extends PureComponent { componentDidMount() { const { find, restaurants } = this.props; if (shouldRefresh(restaurants)) { find(ext('Restaurants'), 'all', { include: 'image', }) } } ... } ``` Implement rendering with fetched data. ```JSX{2,8-9} #file: app/screens/List.js render() { const { restaurants } = this.props; return ( this.renderRow(restaurant)} /> ); } ``` Once fetched, restaurants will go into the app state. Convert them to an array with `getCollection` and then connect `find` to redux store. ```javascript{2-6} #file: app/screens/List.js export default connect( (state) => ({ // get an array of restaurants from allRestaurants collection restaurants: getCollection(state[ext()].allRestaurants, state) }), { navigateTo, find } )(List); ``` This is the final result of `List` screen: ```JSX #file: app/screens/List.js import React, { PureComponent } from 'react'; import { TouchableOpacity } from 'react-native'; import { connect } from 'react-redux'; import { navigateTo, NavigationBar } from 'shoutem.navigation'; import { find, isBusy, shouldRefresh, getCollection } from '@shoutem/redux-io'; import { ImageBackground, ListView, Tile, Title, Subtitle, Overlay, Screen } from '@shoutem/ui'; import { ext } from '../const'; export class List extends PureComponent { constructor(props) { super(props); // bind renderRow function to get the correct props this.renderRow = this.renderRow.bind(this); } componentDidMount() { const { find, restaurants } = this.props; if (shouldRefresh(restaurants)) { find(ext('Restaurants'), 'all', { include: 'image', }) } } // defines the UI of each row in the list renderRow(restaurant) { const { navigateTo } = this.props; return ( navigateTo({ screen: ext('Details'), props: { restaurant } })}> {restaurant.name} {restaurant.address} ); } render() { const { restaurants } = this.props; return ( this.renderRow(restaurant)} /> ); } } // connect screen to redux store export default connect( (state) => ({ // get an array of restaurants from allRestaurants collection restaurants: getCollection(state[ext()].allRestaurants, state) }), { navigateTo, find } )(List); ``` >#### Note >Make sure you remove the `default` from `export default class List extends Component` and only have `default` in `export default connect`, because there can only be one default export. Let's check how it works: ```ShellSession $ shoutem push Uploading `Restaurants` extension to Shoutem... Success! ```

Works like a charm! You just made your first extension using the **Shoutem UI Toolkit** and **Shoutem Cloud Storage**. Great job! ================================================ FILE: docs/extensions/my-first-extension/_posts/1970-01-08-Publish.md ================================================ --- layout: doc permalink: /docs/extensions/my-first-extension/publish title: Publish your extension and app section: My first extension --- # Publish Your Extension and App! Once you're satisfied with your extension, you can publish it. Publishing an extension freezes that version, so no changes can be made to that version, only to the new versions. At the moment, every extension that you publish to the market is only visible to you. To publish an app to Google Play and App Store, every extension in the app needs to be published. So, let's publish our extension! ```ShellSession $ shoutem publish Publishing `Restaurants` extension to Shoutem... Version `0.0.1` of `Restaurants` extensions was published! ``` Publish your app now to the stores. You can let Shoutem publish it for you to both stores (premium feature) or publish it manually following [our tutorial]({{ site.url }}/docs/extensions/tutorials/publish-your-app). Shoutem has developed an easy and intuitive process of app publishing. Simply insert information at one place and Shoutem will take care of the rest.

## Using the app dashboard! Once the app hits the stores, the dashboard really starts to shine. You can manage your mobile app and resubmit the changes over the wire. You can even grant your clients access to the dashboard, so they can manage the app for themselves. To send push notifications, navigate to the _Push_ tab in the Builder and customize the push notification you want to send.

Tracking what users are doing within your app is also in the palm of your hand with our _Stats_ tab.

## What's next? Get to work making gorgeous native apps! Check out these resources: - Read the [technical overview]({{ site.url }}/docs/extensions/tutorials/architecture-and-best-practises) for Shoutem extensions - Build beautiful apps with the Shoutem [UI toolkit]({{ site.url }}/docs/ui-toolkit/introduction)! - Use our [React Native school]({{ site.shoutem.school }}) to learn more, it has lectures for everyone! Happy coding! ================================================ FILE: docs/extensions/reference/_posts/1970-01-01-ExtensionFile.md ================================================ --- layout: doc permalink: /docs/extensions/reference/extension title: Extension file format section: Reference --- # Extension file format The main file that describes every extension is `extension.json`, which is located in the root folder of the extension. ## Structure of extension.json Following structure shows only `root` fields of the extension.json. Detailed description about each of those fields is below. ```json { // required "name": "restaurants", "version": "0.0.1", "platform": "1.0.*", // recommended "title": "Restaurants", "description": "List restaurants in your app", "website": "https://www.shoutem.com/restaurants", "icon": "server/assets/extension/icon.png", // optional "settingsPages": [{...}], "settings": {...}, // optional exports (extension parts) "shortcuts": [{...}], "screens": [{...}], "dataSchemas": [{...}], "pages": [{...}], "themes": [{...}], "themeVariables": [{...}] } ``` ## Defining and referencing extension parts As you see in _Structure of extension.json_ chapter, extension exports multiple extension parts (shortcuts, screens, dataSchemas, pages, themes, themeVariables). In order to be able to use these extension parts, we need to define them, so we can later reference them in other parts. Defining is done in `name` field which value needs to be unique for that extension part (name `List` can be only used for 1 shortcut, but also for 1 screen, etc.). On the other hand, when referencing extension parts, fully qualified name needs to be used. Fully qualified name of **extension** is done by prefixing `.` to `name` field (for `restaurants` extension developed by `shoutem`, extension would have unique identifier `shoutem.restaurants`). Fully qualified name of **extension parts** is done by suffixing `..` with the unique identifier for that extension part, e.g. `shoutem.restaurants.List` for shortcut. If you're referencing the extension part from within the same extension, use `@.` instead of `..` (e.g. `@.List`). ## Fields Here you can find field explanations in the same order fields appeared in the upper example: #### name Required field. Defines extension's identity. Must be unique among your extensions and not longer than 32 characters. #### version Required field. Version of your extension. #### platform Required field. Version of [Shoutem platform]({{ site.url }}/docs/extensions/reference/platform), which defines versions of React, React Native, Redux and some other packages. #### title Title of your extension. #### description Description of your extension. #### website Website that promotes your extension. #### icon Path to extension's icon that will be present in Shoutem Extension Market. Store the icon in `server` asset's folder, as it will be used on Shoutem's server side. #### settingsPages Array of [extension settings pages]({{ site.url }}/docs/extensions/reference/settings-types) used to manage the global settings of the extension. ```json [{ // required "page": "@.Settings", // recommended "title": "Settings", // optional "parameters": { "any-parameter": "any-value" } }] ``` Each object in settings pages array, settings page object, consist of these fields: - `page`: Required field, references the extension page - `title`: Title of extension page - `parameters`: Dictionary of arbitrary key/value pairs that will be passed to extension settings page #### settings Dictionary of arbitrary key/value pairs that represent **default** extensions's settings passed to settings pages objects. ```json { "any-parameter": "any-value" } ``` #### shortcuts [Shortcuts]({{ site.url }}/docs/extensions/my-first-extension/shortcut-and-screen) are links to the starting screen of your extension. Format: ```json [{ // required "name": "List", // required (pick one) "screen": "@.List", "action": "@.visitRestaurants" // recommended "title": "Restaurants", "description": "Allow users...", "icon": "server/assets/shortcuts/restaurants-list.png", // optional "type": "navigation", "adminPages": [{ // required "page": "@.CmsPage", // recommended "title": "Content", // optional "parameters": { "schema": "@.Restaurants" }, }], "settings": { "any-parameter": "any-value" } }] ``` Each object in shortcuts array, shortcut object, consists of these fields: - `name`: Required field, defines shortcut's identity - `screen/action`: Shortcut can either open a `Screen` or call an `Action` (see example in [Shoutem Auth](https://github.com/shoutem/extensions/blob/master/shoutem-auth/extension.json) extension) - `title`: Shortcut's title - `description`: Shortcut's description - `icon`: Path to shortcut's icon that will be shown in builder. Store in `server` asset's folder - `type`: Indicates the type of shortcut. It can be `navigation` or `undefined`. If `navigation`, it will be possible to nest other shortcuts below the current - `adminPages`: Array of shortcut's admin pages. Admin page object inside of array consists of: - `page`: Required field, references a [settings page]({{ site.url }}/docs/extensions/tutorials/settings-pages-introduction) - `title`: Title of admin page - `parameters` Dictionary of arbitrary key/value pairs that will be passed to admin page instance - `settings`: Dictionary of arbitrary key/value pairs that represent default Shortcut's settings passed to admin pages #### screens Screens are nothing more than React components which represent full mobile screen. Format: ```json [{ // required "name": "List", // recommended "title": "List", "image": "server/assets/screens/restaurants-list.png", // optional "navigatesTo": [{ "details": "@.Details" }], "settingsPage": { // required "page": "@.List", // optional "parameters": { "any-parameter": "any-value" } }, "settings": { "any-parameter": "any-value" } }, { "name": "Grid", "title": "Grid", "image": "server/assets/screens/restaurants-grid.png", "extends": "@.List", "settingsPage": { "page": "@.List", "parameters": { "any-parameter": "any-value" } }, }] ``` Each object in screens array, screen object, consists of these fields: - `name`: Required field, defines screen's identity - `title`: Screen's title that will be shown in [layout selector]({{ site.url }}/docs/extensions/tutorials/screen-layouts) - `image`: Path to screen's image that shows it's layout - `navigatesTo`: Array of key/value pairs that indicates to which screens the current one can navigate to - `settingsPage`: Screen's settings page. Object consists of: - `page`: Required field, references an [settings page]({{ site.url }}/docs/extensions/tutorials/settings-pages-introduction) - `parameters`: Dictionary of arbitrary key/value pairs that will be passed to settings page instance - `settings`: Dictionary of arbitrary key/value pairs that represent default Shortcut's settings passed to admin pages - `extends`: References screen that the current one is extending In the example above, we included 2 screen objects inside of the `screens` array. We wanted to show you the usage of `extends` field. Extending makes it possible to [switch between multiple screen layouts]({{ site.url }}/docs/extensions/tutorials/screen-layouts). #### dataSchemas [Data Schemas]({{ site.url }}/docs/extensions/my-first-extension/using-cloud-storage) are Shoutem-flavored [JSON Schemas](http://json-schema.org/) which describe data stored on Shoutem's CMS. ```json [{ // required "name": "Restaurants", "path": "server/data-schemas/restaurants.json" }] ``` Each object in data schemas array, data schema object, consists of these fields: - `name`: Required field, defines data schema's identity - `path`: Required field, path to actual schema implementation. Should be stored in `server` folder #### pages [Settings pages]({{ site.url }}/docs/extensions/reference/settings-types) are web pages written by extension developers. They can be used to manage 3 different types of settings: - global settings of the extensions (referenced from `settingsPages` in the root of extension.json) - settings of the shortcut instance (referenced from `adminPages` in the shortcut object) - settings of the screen (referenced from `settingsPage` in the screen object) ```json [{ // required "name": "List", "type": "html", "path": "server/assets/pages/tab-bar/index.html" }], ``` Each object in pages array, extensions page object, consists of these fields: - `name`: Required field, defines extension page's identity - `type`: Required field, defines type of an extension. Only `html` available for now - `path`: Required field, path to actual extension page implementation. Should be stored in `server` folder #### themes [Themes]({{ site.url }}/docs/extensions/tutorials/writing-a-theme) represent files where you can provide set of styles for your UI components. ```json [{ // required "name": "Rubicon", // recommended "title": "Rubicon", "description": "Rubicon is a beautiful template built...", "showcase": ["server/assets/theme/rubicon.mp4","server/assets/theme/rubicon1.jpg", "server/assets/theme/rubicon2.jpg", "server/assets/theme/rubicon3.jpg"], // optional "icons": "app/themes/Rubicon/assets/icons/", "themeVariables": "@.Rubicon" }] ``` Each object in themes array, theme object, consists of these fields: - `name`: Required field, defines theme's identity - `title`: Theme's title - `description:` Theme's description - `showcase`: Array of strings which represent paths to multimedia files in `server` folder, such as videos and images, which present your theme. Dimensions for @2x quality resolution are 750 × 1334. - `icons`: Path to icons of theme, should be stored in `app` asset's folder - `variables`: Reference to variables used by theme #### themeVariables Theme variables are used to define the structure of the variables used by theme. These variables can be used to customize the theme. ```json [{ // required "name": "Rubicon", "path": "server/themes/rubiconVariables.json" }] ``` Each object in theme variables array, theme variables object, consists of these fields: - `name`: Required field, defines theme variables name - `path`: Required field, path to actual theme variables implementation. Should be stored in `server` folder ## Full example of extension.json Finally, here's the full example of extension.json: ```json { "shoutem": "1.0", "name": "restaurants", "version": "0.0.1", "title": "Restaurants", "website": "https://extensions.shoutem.com/shoutem.navigation", "description": "Make your users rate products.", "icon": "server/assets/extension/icon.png", "defaultLocale": "en", "settingsPages": [{ "page": "@.settings", "title": "Settings", }], "settings": { "any-parameter": "any-value" }, "shortcuts": [{ "name": "List", "title": "Restaurants", "description": "Allow users...", "screen": "@.list", "icon": "theme://events.png", "adminPages": [{ "page": "@.CmsPage", "title": "Content", "parameters": { "schema": "@.Restaurants" }, }], "settings": { "any-parameter": "any-value" } }], "screens": [{ "name": "List", "title": "List", "image": "server/assets/screens/restaurants-list.png", "navigatesTo": [{ "details": "@.Details" }] }, { "name": "Grid", "title": "Grid", "image": "server/assets/screens/restaurants-grid.png", "extends": "@.List", }, { "name": "Details", "title": "Details", }], "pages": [{ "name": "settings", "path": "server/assets/settings/settings/index.html", }], "dataSchemas": [{ "name": "Restaurants", "path": "server/data-schemas/restaurants.json" }], "themes": [{ "name": "Rubicon", "title": "Rubicon", "variables": "@.Rubicon", "description": "Rubicon is a beautiful template built...", "showcase": ["server/assets/theme/rubicon.mp4","server/assets/theme/rubicon1.jpg", "server/assets/theme/rubicon2.jpg", "server/assets/theme/rubicon3.jpg"], "icons": "app/themes/Rubicon/assets/icons/" }, { "name": "Arno", "title": "Arno", "variables": "@.Rubicon", "description": "Arno is a beautiful template built...", "showcase": ["server/assets/theme/arno1.jpg", "server/assets/theme/arno2.jpg"], "icons": "app/themes/Arno/assets/icons/" }], "themeVariables": [{ "name": "Rubicon", "path": "server/themes/rubiconVariables.json" }] } ``` ================================================ FILE: docs/extensions/reference/_posts/1970-01-01-Overview.md ================================================ --- layout: doc permalink: /docs/extensions/reference/overview title: Technical overview section: Extensions --- # Technical overview of Shoutem Extensions This document explains the overall architecture of Shoutem extensions in more details. ## What is an extension? Each Shoutem extension represents a complete, self-contained functionality, e.g. an integration with a third party e-commerce provider. This extension would define multiple screens that will be available in the mobile app, any custom logic for purchasing products, and web pages that allow app owners to configure this integration through the Shoutem Builder. ## Extension segments To accomplish that, we divided extension into two main segments: `app`, and `server`. The `app` segment contains everything that will be bundled within the RN mobile app: this can be anything from simple RN screens to complete native modules implemented [using platform specific native technologies]({{ site.url }}/docs/extensions/tutorials/using-native-api). The `server` segment contains web pages that will be hosted within the Shoutem builder, and allow users to configure and manage extensions while creating their apps. Besides those two segments, extensions also have an `extension.json`, file that contains extension metadata. It is used by our server to discover the functionalities that will become available to users in the Shoutem builder after the extension is installed. ## Naming Each extension has a canonical name (full name) that consists of two sections separated by a single dot. The first section represents a developer name, and the second one represents the name of the extension itself. For example `shoutem.news`. This allows us to avoid collisions between the components provided by different extensions. Each component exported from the extension has a canonical name as well. The canonical name of a component always has an extension canonical name as a prefix, e.g., `shoutem.news.ArticleDetailsScreen`. ## Mobile app segment The `app` segment of the extension is an **npm package** that will be hosted within the Shoutem mobile app. Our mobile app represents an environment in which all extensions are executed, and it provides certain dependencies out of the box. The extension environment is called the [platform]({{ site.url }}/docs/extensions/reference/platform) (`@shoutem/platform`), and each extension must define a range of platform versions in its `extension.json` that it's compatible with. The platform defines certain global dependencies like the version of React, React Native, Redux, Shoutem UI, etc. More information about dependencies provided by the platform can be found in `package.json` in the [platform repository](https://github.com/shoutem/platform). Our app architecture is completely modular. At the center of everything is only a simple **extension loader** that discovers, loads, and initializes all extensions installed in the app. Everything else is implemented through extensions. Even the core Shoutem functionalities (e.g. navigation, push notifications, analytics, ...) are implemented as regular extensions no different from any other third party extension. Some of our extensions are `system` ones. They're hidden from our non technical app owners (who don't have a developer account registered). Only `shoutem` developer can create `system` extensions.

##### Extension lifecycle When the app is started, we simply load all extensions and run their lifecycle. At that point, [shoutem.application](https://github.com/shoutem/extensions/tree/master/shoutem-application) system extension navigates to the initial screen of the app, and all other user interaction starts from there. The primary interface between extension and the rest of the app, **extension's public API**, is defined by exports in its `app/index.js`.
Predefined extension exports There are several predefined exports that are used by other parts of the system: - **lifecycle methods** - methods that extensions can implement to be notified when the entire app is mounted or unmounted. This can be useful to initialize the extension or clean up when the app is closing. Each of those methods receives an `app` parameter that represents the current **app instance**. Each of those methods may also return a promise. If a promise is returned, the next lifecycle method of any extension will not be called until that (any every other) promise is resolved. This is the list of lifecycle methods in order of their invocation: - **appWillMount** - invoked immediately before the mounting of the root app component occurs. - **appDidMount** - invoked after the root app component is mounted and after all promises from a previous lifecycle method are resolved. - **appDidFinishLaunching** - invoked after the app is mounted and after all promises from `appDidMount` have finished. This is the place to perform any final work before the first screen is rendered. - **appWillUnmount** - invoked immediately before the root app component is unmounted and destroyed. Perform any necessary cleanup in this method. - **screens** - the screens that will be available for navigation. Must have the same name as in `extension.json` - **actions** - actions that can be attached to shortcuts (see [shoutem.auth](https://github.com/shoutem/extensions/tree/master/shoutem-auth) extension). Must have the same name as in `extension.json` - **reducer** - the extension reducer that will be mounted under the extension namespace in the state - **middleware** - Redux `middleware` to register in the Redux `store` - **enhancers** - Redux `enhancers` to register in the Redux `store`
Other exports Besides the exports mentioned above, in `app/index.js` you may also export anything else you want to expose to other extensions. All extensions are installed as node modules, so importing from other extensions is done by simply importing from a package with the canonical name of the extension. For example, you can import the `getExtensionSettings` selector from the [shoutem.application](https://github.com/shoutem/extensions/tree/master/shoutem-application) extension like this: ``` import { getExtensionSettings } from 'shoutem.application'; ``` ##### Communication between extensions The app initializes the Redux `store` before loading the extensions. Redux is used for state management, and to facilitate easier **communication** between the extensions. Communication between extensions can be accomplished in a few ways: - reading from the state of other extensions - dispatching `actions` from other extensions - intercepting actions by using redux `middleware` - directly importing public classes, functions, etc. from other extensions The preferred way to read the data from the state is to export redux `selectors`, so that extensions don't directly depend on the internal state organization of other extensions.
App state To simplify state management, the entire state of the app is divided into extension `namespaces`: ``` { 'shoutem.application': { // state of the "application" extension made by shoutem }, '{{ site.example.devName }}.restaurants': { // state of the restaurants extension made by a third party developer {{ site.example.devName }} }, ... } ``` Each extension namespace is completely managed by the extension through the (root) reducer exported from its `app/index.js`. ##### Modifying extensions All our extensions are [open sourced](https://github.com/shoutem/extensions), so you can modify anything you want by simply forking them. Although this is probably the easiest way to modify extensions, it may not always be the best way to do it. If you fork an extension, you have to make sure to maintain it yourself. In other words, you lose automatic updates and bugfixes implemented by the extension owner. A better approach may be to perform minimal changes to the extension by creating a new extension. This can be accomplished in several ways: - modifying styling by [creating a new theme]({{ site.url }}/docs/extensions/tutorials/writing-a-theme) - creating completely new [screen layouts]({{ site.url }}/docs/extensions/tutorials/screen-layouts) - creating new screen layouts by [extending existing screens and overriding certain methods]({{ site.url }}/docs/extensions/tutorials/modifying-extensions) - reading data from the state of other extensions - intercepting actions from other extensions by registering a redux `middleware`. With this approach you can modify, suppress, or just examine any action dispatched in the app. Read more in [Modifying extensions]({{ site.url }}/docs/extensions/tutorials/modifying-extensions) tutorial.
## Server segment Server segment is used for customizing 3 server sides of extensions: - Settings pages - Data schemas - Theme variables **Settings pages** that are used to configure the extension through the Shoutem builder. Those pages can be implemented in several ways. They can be a standalone web site implemented using a web framework of your choice. With this approach you have complete freedom to do whatever you want. Pages like that are always be hosted within an iframe in the Shoutem builder. Another way to implement those pages is to use React components. This approach to settings pages is still in development, but it will become a preferred way to implement settings pages. Learn more on how to create [settings pages]({{ site.url }}/docs/extensions/tutorials/settings-pages-introduction). **Data schemas** allow you to define a data type to be stored on the Shoutem backend using a standard JSON Schema format. When a app owner installs your extension in the Shoutem builder, all schemas defined within it and connected with **content settings page**. become available through web interface. The app owner is able to manually enter data in the Shoutem builder, or import data to the cloud storage from various supported sources. The web interface for data management is created automatically based on the JSON Schema from your extensions. Learn more on how to use [Shoutem Cloud]({{ site.url }}/docs/cloud/introduction) **Theme variables** schema is used for themes. It also uses the JSON Schema standard to define the theme variables that can be customized by app owners through the Shoutem builder. Using those variables, owners are able to customize themes by changing colors, fonts, sizes, etc. Web interface for customizing theme variables is automatically created in the Shoutem builder based on the theme variables schema when theme from your extension is activated in the app. Learn more about creating [theme variables]({{ site.url }}/docs/extensions/reference/theme-variables). ================================================ FILE: docs/extensions/reference/_posts/1970-01-02-Platform.md ================================================ --- layout: doc permalink: /docs/extensions/reference/platform title: Platform section: Reference --- # Platform
Shoutem platform defines an environment in which extensions are executed. This environment defines versions of React, React Native and other packages available to your extension by default. Upon initializing your extension with `shoutem init` command, CLI defines those packages as peer dependencies in your `app/package.json` file. They are available to all extensions without needing to install them, but their version is managed by the platform. That's why all the packages have `*` as the version. Here's an example of what `app/package.json` might look like after initialization: ```JSON #file: app/package.json { "name": "{{ site.example.devName }}.restaurants", "version": "0.0.1", "description": "List of restaurants!", "peerDependencies": { "@shoutem/animation": "*", "@shoutem/core": "*", "@shoutem/redux-composers": "*", "@shoutem/redux-io": "*", "@shoutem/theme": "*", "@shoutem/ui": "*", "@shoutem/ui-addons": "*", "lodash": "*", "moment": "*", "whatwg-fetch": "*", "react": "*", "react-native": "*", "react-native-browser-polyfill": "*", "react-redux": "*", "redux": "*", "redux-action-buffer": "*", "redux-persist": "*", "redux-api-middleware": "*", "redux-logger": "*", "redux-thunk": "*" } } ``` > #### Note > File package.json always includes developer name alongside with the extension name, while the `name` in extension.json doesn't include the developer name. The specific versions of current platform can be found in the `package.json` file in [platform repository]({{ site.url }}/docs/extensions/reference/platform). Versions included in previous platforms can be browsed in the `git` history. ================================================ FILE: docs/extensions/reference/_posts/1970-01-03-ExtensionExports.md ================================================ --- layout: doc permalink: /docs/extensions/reference/extension-exports title: Extension exports section: Reference --- # Extension exports Each extension can export multiple values in: - app/index.js - used by an app and other extensions - server/index.js - used by Shoutem Builder ## _app/index.js_ The `app` folder is npm package that represents segment of extension bundled in the app. The `app/index.js` file is what is accessible from the current extension to the app and other extensions. Since `app` expects some exported parts (e.g.) there are some predefined extension exports. Here only those will be listed, but you can read more about the whole architecture in [Technical overview]({{ site.url }}/docs/extensions/reference/overview). These are the predefined extension exports: - **lifecycle methods** - methods that extensions can implement to be notified when the entire app is mounted or unmounted. This can be useful to initialize the extension or clean up when the app is closing. Each of those methods receives an `app` parameter that represents the current **app instance**. Each of those methods may also return a promise. If a promise is returned, the next lifecycle method of any extension will not be called until that (any every other) promise is resolved. This is the list of lifecycle methods in order of their invocation: - **appWillMount** - invoked immediately before the mounting of the root app component occurs. - **appDidMount** - invoked after the root app component is mounted and after all promises from a previous lifecycle method are resolved. - **appDidFinishLaunching** - invoked after the app is mounted and after all promises from `appDidMount` have finished. This is the place to perform any final work before the first screen is rendered. - **appWillUnmount** - invoked immediately before the root app component is unmounted and destroyed. Perform any necessary cleanup in this method. - **screens** - the screens that will be available for navigation. Must have the same name as in `extension.json` - **themes** - themes available for app customization. Must have the same name as in `extension.json` - **actions** - actions that can be attached to shortcuts (see [shoutem.auth](https://github.com/shoutem/extensions/tree/master/shoutem-auth) extension). Must have the same name as in `extension.json` - **reducer** - the extension reducer that will be mounted under the extension namespace in the state - **middleware** - Redux `middleware` to register in the Redux `store` - **enhancers** - Redux `enhancers` to register in the Redux `store`
## _server/index.js_ Same as for `app` the `server` folder is npm package that represents segment of extension in Shoutem Builder. The `server/index.js` file is what is accessible from the current extension to the Shoutem Builder. These are the predefined extension exports: - **lifecycle methods** - methods that extensions can implement to be notified when the entire extension is mounted in Shoutem Builder. This can be useful to initialize the extension. Each of those methods receives an `page` parameter that represents the current **page instance**. Page instance contains methods `getPageContext` and `getPageParameters`. This is the list of lifecycle methods in order of their invocation (we plan to support more methods): - **pageWillMount** - invoked immediately before the mounting of the root page component occurs. - **pages** - the settings pages that will be available for Shoutem Builder. Must have the same name as in `extension.json` - **reducer** - the extension reducer that will be mounted under the extension namespace in the state ================================================ FILE: docs/extensions/reference/_posts/1970-01-04-SettingsTypesInExtension.md ================================================ --- layout: doc permalink: /docs/extensions/reference/settings-types title: Settings types in extension section: Reference --- # Settings types in extension Each extension has 3 different types of settings. App owners manage these settings in _Settings pages_ to customize the functionality of the behaviour of an extension in the app. ##### Extension concepts Before we dive into settings types, let’s refresh our memory on extension concepts: - Each extension can have multiple versions, but only one can be installed at a time - that's what we refer to as an `extension installation`. - An extension can expose multiple shortcuts, and each of them can be added more than once to the app, called `shortcut instances`. - Each shortcut instance opens a starting screen, which can then open the next screen, and so on. - Each screen can have multiple representations, called `screen layouts`. ##### Settings pages Settings pages are web pages written by extension developers, and they appear in Shoutem builder. Their purpose is to enable app owners extension customizations through settings. Check the [tutorial on how to create a settings page]({{ site.url }}/docs/extensions/tutorials/settings-pages-introduction) for more info. Settings pages are exported via the `pages` field in `extension.json` and can be used on 3 different places which determine settings type. ##### Settings types - `Extension settings` - Single global settings for the extension installation, placed in `settingsPages` in the root of `extension.json` - `Shortcut settings` - Shortcut instance settings, placed in `adminPages` in the shortcut object - `Screen settings` - Settings of the layout presented by the screen, placed in `settingsPage` in the screen object. ##### Default settings Each settings can have its default value. The default value is defined in `settings` field, which is adjacent to `settingsPage(s)` or `adminPages` fields, for every settings type. Applying default settings is determined by settings type: - default `extension settings` are applied when installing extension into the app - default `shortcut settings` are defined when adding a shortcut (screen) into the app structure - default `screen settings` are defined when adding a shortcut (screen) into the app structure Updating the default settings in `extension.json` **will not** update the default settings in that extension part if those were aready applied. For instance, if you already added shortcut instance (created from some shortcut), default settings of that shortcut won't be updated if you update them in `extension.json` and push the new version to Shoutem. However, if you add another shortcut instance, that instance will have the latest default settings from `extension.json`. ##### Manipulation of settings Settings can be manipulated using the [@shoutem/api-sdk]({{ site.url }}/docs/coming-soon) package. See [Writting settings pages]({{ site.url }}/docs/extensions/tutorials/settings-pages-introduction) tutorial on how to do that. > #### Note > The below documentation is outdated and will be updated once `api-sdk` tool is finished. This is in progress. ## Extension settings Extension settings are global settings shared throughout all extension parts within an extension installation. ##### Place ```JSON{6-21} #file: extension.json { "name": "restaurants", "version": "0.0.1", "title": "Restaurants", "description": "List of restaurants", "platform": "1.0.*", "settingsPages": [{ "page": "@.General", "title": "General settings", }, { "page": "@.Sounds", "title": "Sounds" }], "settings": { "website": "www.example.com", "onRowTapSound": false }, "pages": [{ "name": "General" }, { "name": "Sounds" }] } ``` Settings pages meant for manipulating extension settings can be found in the `Extensions` tab for that extension. We call them `extension settings pages`.

##### Server side Properties received to the root extension settings page component are: - `extension`: Extension object with fields: - `name`: Extension name - `version`: Extension version - `title`: Extension title - `settings`: Extension global settings To set extension settings, use `setExtensionSettings` from `@shoutem/builder-sdk`. Although extension settings can be manipulated from any settings page, for maximum user experience, do it only in extension settings pages. ##### Client side Each screen that is connected to the state can access extension settings. They can be found in `props`, specifically in `props.extension.settings`. ## Shortcut settings Shortcut settings are settings shared throughout all the screens that were navigated to from the starting screen of the shortcut instance. ##### Place ```JSON{10-22,28-30} #file: extension.json { "name": "restaurants", "version": "0.0.1", "title": "Restaurants", "description": "List of restaurants", "platform": "1.0.*", "shortcuts": [{ "name": "List", "title": "Restaurants", "screen": "@.list", "adminPages": [{ "page": "shoutem.cms.CmsPage", "title": "Content", "parameters": { "schema": "@.Restaurants" }, }, { "page": "@.RestaurantsPage", "title": "Settings" }], "settings": { "headerTitle": "RESTAURANTS" } }], "dataSchemas": [{ "name": "Restaurants", "path": "server/schemas/Restaurants.json" }], "pages": [{ "name": "RestaurantsPage" }], } ``` Settings pages meant for manipulating shortcut settings can be found next to app structure in the `Screens` tab. We call them `shortcut settings pages`. Namely, for this example, there should be `Content` and `Settings`.

##### Server side Properties received to root shortcut settings page component are: - `extension`: Extension object with fields: - `name`: Extension name - `version`: Extension version - `title`: Extension title - `settings`: Extension global settings - `shortcut`: Shortcut instance object with fields: - `name`: A shortcut's name - `title`: A shortcut's title - `settings`: A shortcut instance settings - `screens`: Array of screen objects, each containing: - `type`: Type of screen that has layouts (that's original screen's name) - `name`: Name of the layout that will be shown - `settings`: Screen settings (shared among all layouts) To set extension settings, use `setShortcutSettings` from `@shoutem/builder-sdk`. Although shortcut settings can be manipulated from both the shortcut and the screen settings page, for maximum user experience, do it only from the shortcut settings pages. ##### Client side Each screen that is connected to the state can access shortcut settings. They can be found in `props`, specifically in `props.shortcut.settings`. ## Screen settings Screen settings are layouts settings that hold information specific for that layout. Check the [tutorial for screen layouts]({{ site.url }}/docs/extensions/tutorials/screen-layouts) to get a better understanding on difference between screen and layouts. ##### Place ```JSON{14-20,25-31,33-37} #file: extension.json { "name": "restaurants", "version": "0.0.1", "title": "Restaurants", "description": "List of restaurants", "platform": "1.0.*", "shortcuts": [{ "name": "List", "title": "Restaurants", "screen": "@.list" }], "screens": [{ "name": "list", "title": "List of restaurants", "settingsPage": { "title": "Settings", "page": "@.ListSettings" }, "settings": { "groupByStartingLetter": false } }, { "name": "grid", "extends": "@.list", "title": "Grid of restaurants", "settingsPage": { "title": "Settings", "page": "@.GridSettings" }, "settings": { "gridCellsOfSameHeight": true } }] "pages": [{ "name": "ListSettings" }, { "name": "GridSettings" }], } ``` There's only 1 settings page per screen for manipulating screen settings. It's located in the `Layout` shortcut settings page, under the layout selector, when that screen is selected as the desired layout.

##### Server side Properties received to root shortcut settings page component are: - `extension`: Extension object with fields: - `name`: Extension name - `version`: Extension version - `title`: Extension title - `settings`: Extension global settings - `shortcut`: Shortcut instance object with fields: - `name`: A shortcut's name - `title`: A shortcut's title - `settings`: Shortcut instance settings - `screens`: Array of screen objects, each containing: - `type`: Type of screen that has layouts (that's original screen's name) - `name`: Name of the layout that will be shown - `settings`: Screen settings (shared among all layouts) - `screen`: Screen object with fields: - `name`: Screen's name (which is also currently active layout screen) - `title`: Screen's title - `settings`: Screen settings (shared among all layouts) To set screen settings in settings page, use `setScreenSettings` from `@shoutem/builder-sdk`. Screen settings can only be manipulated in screen settings page and using these functions elsewhere will fail. Notice that `settings` inside of `screen` are in shared namespace, which means that multiple screens which act as different layouts share these settings. If keeping separate namespace per screen is important for you, you can save the settings under key of screen `name`. ##### Client side Each screen connected to the state can access shortcut layouts settings. They can be found in `props`, specifically in `props.screen.settings`. ================================================ FILE: docs/extensions/reference/_posts/1970-01-05-ThemeVariables.md ================================================ --- layout: doc permalink: /docs/extensions/reference/theme-variables title: Theme variables section: Reference --- # Theme variables schema reference Adjustment of theme is done through theme variables. These variables can be set through Shoutem builder, which interprets the variables schema.

## Structure of variables schema file Variables schema file is nothing else than Shoutem flavored [JSON Schema](https://spacetelescope.github.io/understanding-json-schema/UnderstandingJSONSchema.pdf). Example: ```JSON { "formats": { "font": { "title": "Font", "default": { "fontFamily": "Rubicon", "fontStyle": "normal", "fontWeight": "normal", "fontSize": 20, "color": "rgba(255,255,255,1)" }, "constraints": { "fontFamily": { "enum": [ "normal", "Rubicon"] }, "fontStyle": { "enum": ["normal", "italic"] }, "fontWeight": { "enum": ["normal", "bold", "100", "200", "300", "400", "500", "600", "700", "800", "900"] }, "fontSize": { "minimum": 12, "maximum": 42 } } } }, "properties": { "primaryColor": { "type": "string", "format": "color", "title": "Primary color", "default": "rgba(12, 111, 34, 0.5)" }, "textFont": { "type": "object", "format": "font", "title": "Text font", "default": { "fontFamily": "Rubicon", "fontStyle": "normal", "fontWeight": "regular", "fontSize": 15, "color": "rgba(255,255,255,1)" } } }, "layout": { "sections": [{ "title": "Colors", "properties": ["primaryColor"] }, { "title": "Text", "properties": ["textFont"] }] } } ``` It's `properties` are the variable descriptors - they describe the variable to the Shoutem builder. For now, there are only 2 types of variables: - Color - `"type": "string", "format": "color"` - Font - `"type": "object", "format": "font"` Based on what the type is, descriptor has different fields. However, some fields are shared: - **title**: Title of the variable on builder interface. - **default**: Default value of the interface control. Value depends on the type. - **disabled**: Whether admin can set the variable or not. Defaults to `false`. There is also field `formats`. It is used to describe default values and constraints of specific format. Each variable of the same format thus _inherits_ values defined in `formats`, but can also override each field with its own value. #### Color Variable of type color will result in color picker in interface for customizing theme.

###### Default value String. One of the React Native supported [Color formats](https://facebook.github.io/react-native/docs/colors.html). ###### Fields Currently, there are no additional properties variable descriptor supports. #### Font Variable of type font will result in complex control in interface for customizing theme.

###### Default value Object with following fields: ```JSON { "fontFamily": "Rubicon", "fontStyle": "normal", "fontWeight": "normal", "fontSize": 20, "color": "rgba(255,255,255,1)" } ``` - **fontFamily** - String. One of the font families listed in `constraints.fontFamily` field. Defaults to "Rubicon". - **fontStyle** - String. One of the font styles listed in `constraints.fontSize` field. Defaults to "normal". - **fontWeight** - String. One of the font weights listed in `constraints.fontWeight` field. Defaults to "normal". - **fontSize** - Number. Defaults to 12 - **color** - String. One of the React Native supported [Color formats](https://facebook.github.io/react-native/docs/colors.html). Defaults to `"rgba(0,0,0,1)"` ###### Fields Font variable descriptor defines additional property `constraints`, which describes values that are available for each field: - **fontFamily**: enum of Strings. All available font families should be listed here. Default values: `"normal", "Rubicon"`. - **fontStyle**: enum of Strings. All available font styles should be listed here. Default values: `"normal", "italic"`. - **fontWeight**: enum of Strings. All available font weights should be listed here. Default values: `"normal", "bold", "100", "200", "300", "400", "500", "600", "700", "800", "900"`. - **fontSize**: object that defines minimal and maximal font size. It has two fields of type Number: `"minimum"` - defaults to 12 and `"maximum"` - defaults to 42. ================================================ FILE: docs/extensions/reference/_posts/1970-01-06-CLI.md ================================================ --- layout: doc permalink: /docs/extensions/reference/cli title: Shoutem CLI section: Extensions --- # Shoutem CLI Shoutem Command Line Interface (CLI) is a tool that helps you build extensions. Using it, you can create extensions, generate code snippets, upload extensions to your Shoutem account and install them on your apps. ## Installation Download and install Shoutem CLI through `npm`, the package manager for `Node.js`. ```ShellSession $ npm install -g @shoutem/cli ``` If the previous command fails because of permission issues, you need to run it with `sudo` permission: `sudo npm install -g @shoutem/cli`.
In case you don't have `npm` installed, the best way to install it is with [installing](https://nodejs.org/en/download/) `Node.js`, which includes `npm`. ## Commands Here is the list of available commands with their arguments and flags. Some of the arguments and flags are not documented, which we only found useful for the Shoutem team internally (they can still be seen when using `help` commands).
- ##### shoutem Lists all the commands. ```ShellSession $ shoutem Usage: shoutem [command] [-h] Commands: ... ``` - ##### shoutem [\--version|-v] Prints out the CLI version. ```ShellSession $ shoutem -v 1.0.0 ``` - ##### shoutem \ [\--help|-h] Every command's description and arguments list can be invoked using -h flag. So for example, the command `shoutem init -h` prints usage info for the init command and `shoutem screen add -h` prints usage info for the `shoutem screen add` command: ```ShellSession $ shoutem init -h Usage: shoutem init Create a scaffold of all files and folders required to build an extension. ``` - ##### shoutem login Authenticates you as a developer. Enter your Shoutem credentials. If you're not a Shoutem user yet, sign up [here]({{ site.shoutem.builderURL }}). ```ShellSession $ shoutem login Enter your Shoutem credentials (obtained at {{ site.shoutem.builderURL }}): E-mail: {{ site.example.devEmail }} Password: ******** ``` Each Shoutem user can become a Shoutem developer. If you're not a developer yet, you'll be prompted to create a developer name after entering your credentials. ```ShellSession Enter new developer name: Name: {{ site.example.devName }} You are registered as `{{ site.example.devName }}`! ``` - ##### shoutem logout Logs you out from the CLI. ```ShellSession $ shoutem logout Successfully logged out. ``` - ##### shoutem init \ Initializes an extension project. ```ShellSession $ shoutem init {{ site.example.extensionName }} Enter information about your extension. Press `return` to accept (default) values. Title: Restaurants Version: 0.0.1 Description: List of {{ site.example.extensionName }} ... Extension initialized! ``` After initialization, your extension folder structure will look as follows: ``` {{ site.example.devName }}.{{ site.example.extensionName }}/ ├ app/ | ├ node_modules/ | ├ extension.js | ├ index.js | └ package.json ├ server/ | ├ node_modules/ | └ package.json └ extension.json ``` > #### Note > `{{ site.example.devName }}` and `{{ site.example.extensionName }}` are just used as an example. Run this command from the extension folder. - ##### shoutem push Pushes extension to Shoutem server. ```ShellSession $ shoutem push Uploading `Restaurants` extension to Shoutem... Success! ``` As long as extension is not published, every `push` will overwrite current development version of extension. Once you `publish` extension, that version of extension will be unchangeable and you will be able to `push` only higher version numbers. Run this command from the extension folder. - ##### shoutem publish Publishes extension to Shoutem server. If the extension folder is inside of a cloned app folder, it will also offer you to install the extension into that cloned app. Furthermore, if the extension is already published, it lets you choose it's new version. If the extension is both published and installed, it will ask you whether or not you want to update the extension that's installed in the app to the newly published version. ```ShellSession $ shoutem publish WARNING: shoutem publish command is deprecated. Use shoutem extension publish instead Checking the extension code for syntax errors... [OK] ? Version 0.0.2 is already published. Specify another version: 0.0.2 Building the server part... [OK] Building the app part... [OK] Packing extension... [OK] Processing upload... [OK] Uploading Restaurants extension to Shoutem... [OK] Publishing {{ site.example.extensionName }} version 0.0.2... [OK] ? Update the version used in the current app ({{ site.example.devName }}.{{ site.example.extensionName }}@0.0.1 => @0.0.2)? Yes Success ``` Run this command from the extension folder. - #### shoutem extension publish \ Publishes the named extension. It also offers you the choice whether or not you want to install the extension into the cloned app. Furthermore, if the extension is already published, it lets you choose it's new version. If the extension is both published and installed, it will ask you whether or not you want to update the extension that's installed in the app to the newly published version. ```ShellSession $ shoutem extension publish {{ site.example.extensionName }} Checking the extension code for syntax errors... [OK] Building the server part... [OK] Building the app part... [OK] Packing extension... [OK] Processing upload... [OK] Uploading Restaurants extension to Shoutem... [OK] Publishing {{ site.example.extensionName }} version 0.0.1... [OK] ? Do you want to install {{ site.example.devName }}.{{ site.example.extensionName }} extension to the app {{ site.example.appId }}? Yes Success ``` Run this command from any directory inside your cloned app. - ##### shoutem install [\--new [\]] [\--app \] Installs extension to the app. If no flags or arguments are passed, the CLI will offer an interactive menu. ```ShellSession $ shoutem install Select app to install your extension: > RestaurantsApp -------------- Create a new app ``` Pass a flag `--new` to install it on a new app. ```ShellSession $ shoutem install --new Restaurants Installing `Restaurants` extension to the new app... Extension successfully installed to the new app. Check it here: {{ site.shoutem.builderURL }}/app/{{ site.example.appId }} ``` Also, you can install it on specific a app by providing an app ID. To find out ID of the app, go to **Settings** and scroll down to **Advanced information** section of the **App info**. ```ShellSession $ shoutem install --app {{ site.example.appId }} Installing `Restaurants` extension to `Restaurants` app... Extension successfully installed to the app. Check it here: {{ site.shoutem.builderURL }}/app/{{ site.example.appId }} ``` Installation can be done through Builder as well by going to the Extensions tab in the sidebar, clicking on the `+` button next to `Extensions` to open the Extension Marketplace and then selecting the `My extensions` tab. - #### shoutem platform create [--url ] When run from app folder, publishes a custom platform with all your non-extension (i.e. native) changes. Previously to the existence of this command, developers needed to use file anchors, but with this you can make any and all changes to everything in your app, including our core code. It also offers you the choice whether or not you want to publish the platform and install the platform into the cloned app. You can also run it by giving it an --url parameter to an archived platform. For example, you can link to a tag archive in your github repository (needs to be public). - #### shoutem platform publish [--platform ] Publishes the selected platform, making it visible to you in the Shoutem builder. Options can also be left out and an interactive dialogue offers you a list of platforms. - #### shoutem platform install [--app --platform ] Installs selected platform to selected app. Options can also be left out and an interactive dialogue offers you a list of apps and platforms. - ##### shoutem screen add \ Adds a [screen]({{ site.url }}/docs/extensions/my-first-extension/shortcut-and-screen) to the extension. Furthermore, it lets you choose whether or not the screen should also have a shortcut added to it, allowing it to be added to the app via the Builder as a screen via the Add Screen modal. ```ShellSession $ shoutem screen add List ? Screen name: List ? Create a shortcut (so that screen can be added through the Builder)? Yes ? Shortcut name: {{ site.example.extensionName }} ? Shortcut title: Restaurants ? Shortcut description: A shortcut for List ... Success ``` Run this command from the extension folder. - ##### shoutem shortcut add \ Adds a [shortcut]({{ site.url }}/docs/extensions/my-first-extension/shortcut-and-screen) to the extension, either to a specific screen or a standalone shortcut that you can later connect a screen to. ```ShellSession $ shoutem shortcut add ? Shortcut name: MyShortcut ? Shortcut title: My Shortcut ? Shortcut description: A shortcut for WithoutShortcut ? Which screen would you like to connect with this shortcut? WithoutShortcut ... Success! ``` In the example above, `WithoutShortcut` is a screen added with `shoutem screen add` that had no shortcut automatically made for it. Run this command from the extension folder. - ##### shoutem schema add \ Adds a [data-schema]({{ site.url }}/docs/extensions/my-first-extension/using-cloud-storage) to the extension. ```ShellSession $ shoutem schema add Restaurants Schema `Restaurants` is created in file `server/data-schemas/Restaurants.json`! File `extension.json` was modified. ``` Run this command from the extension folder. - ##### shoutem page add \ Adds a settings [page]({{ site.url }}/docs/extensions/tutorials/settings-pages-introduction) to the extension. You can choose whether you want to create a React or HTML settings page and whether that page is a shortcut or extension settings page. It will even allow you to add a new screen and shortcut that the settings page is meant for. ```ShellSession $ shoutem page add ? Settings page type: react ? Settings page name: MyPage ? Settings page title: My Page ? This settings page controls settings for: an existing screen ? Select existing screen: List ... Success ``` Run this command from the extension folder. - ##### shoutem theme add \ Adds a [theme]({{ site.url }}/docs/extensions/tutorials/writing-a-theme) to the extension. ```ShellSession $ shoutem theme add Argo Theme `Argo` is created in file `app/themes/Argo.js`. Variables for the theme are created in file `server/themes/ArgoVariables.json` File `extension.json` was modified. ``` - ##### shoutem show Shows the currently logged in user and their Shoutem home directory. ```ShellSession $ shoutem show Registered as `{{ site.example.devName }}`. Home directory: `/path/to/.shoutem` (customizable through SHOUTEM_CLI_HOME env variable) ``` - ##### shoutem clone [\] Clones a complete app project which includes the [Shoutem platform](https://github.com/shoutem/platform) and all extensions installed in the app (you can see them in `Extensions` tab in the Builder). Useful for running and figuring out the details of how a complete Shoutem app works, and to help you understand how extension code integrates with the app itself. The command will create a directory with the same name as the app being pulled, with spaces being replaced with underscores. The directory will have an `extensions` directory containing the source code of all extensions installed in the given app. ```ShellSession $ shoutem clone Select your app: Restaurants ({{ site.example.appId }}) Pulling the app `Restaurants`... Cloning `Restaurants` to `Restaurants` Done. To run your app on iOS: cd path\to\Restaurants react-native run-ios To run your app on Android: cd path\to\Restaurants Have an Android simulator running or a device connected react-native run-android ``` - ##### shoutem extension add \ Initializes, publishes and installs the app into the cloned app that you're located in. ```ShellSession $ shoutem extension add {{ site.example.extensionName }} Enter information about your extension. Press `return` to accept (default) values. Title: Restaurants Version: 0.0.1 Description: List of {{ site.example.extensionName }} Checking the extension code for syntax errors... [OK] Building the server part... [OK] Building the app part... [OK] Packing extension... [OK] Processing upload... [OK] Uploading Restaurants extension to Shoutem... [OK] Publishing {{ site.example.extensionName }} version 0.0.1... [OK] Installing it in your app... [OK] Congratulations, your new extension is ready! You might try doing cd relative/path/to/vladimir-vdovic.for-reference where you can: shoutem screen add add a new screen shoutem schema add add a new data schema shoutem theme add add a new theme shoutem page add add a new settings page Success! Happy coding! ``` Run from any directory inside cloned app directory. - ##### shoutem pack Packs the extension. This command is used by `shoutem push` and with it, you can check how your extensions looks like when it's pushed to Shoutem servers. ```ShellSession $ shoutem pack Extension is packed at: ~/{{ site.example.extensionName }}.tgz ``` Find the extension packed in the root of the extension folder. By default, `shoutem pack` command will do `npm run build` in both `app` and `server` folder, which is performed also on `shoutem push`. If you don't want to get the pack without building it, use `shoutem pack --nobuild` (not documented flag). Run this command from the extension folder. - ##### shoutem configure [--release] [--production] Runs the platform's configure script to sync the local app with native changes to local extensions. The flag `--release` bundles the downloaded configuration, so that the app doesn't download it on every start. The flag `--production` is used to configure the app's bundle ID for iOS and Android to match the one provided in the Builder. It also activates the code push feature, analytics and push notifications. The `--production` configuration automatically sets the `release` flag in `config.json`. Furthermore, you can customize the bundle ID for your app via the `iosBundleId` and `androidApplicationId` properties in the `config.json` file in the cloned app's root directory. These settings override the Builder defined values. The command should be called when: - new extension is created, installed to the current app & pushed - changing native code of any of the extensions from the extensions/ directory - changing cocoapods or gradle dependencies of any of the extensions from the extensions/ directory - switching between published (`shoutem configure --release`) and development (`shoutem configure`) configuration - preparing app for manual building and publishing (`shoutem configure --production`) ```ShellSession $ shoutem configure Starting build for app {{ site.example.appId }} ``` Run this command from the application folder. - ##### shoutem whoami Displays the username of current developer. ```ShellSession $ shoutem whoami {{ site.example.devName }} ``` ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-01-ConnectToApi.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/connecting-to-api title: Connecting to 3rd Party API section: Tutorials --- # Connecting to 3rd Party API Since Shoutem apps are plain React Native apps, you can connect to any API. It's very simple to do [networking in React Native](https://facebook.github.io/react-native/docs/network.html). Basically, React Native enables you to use [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) out of the box, a simple interface for communicating with API endpoints. If, however, you need something more sophisticated, you can use [Async Actions](https://github.com/reactjs/redux/blob/master/docs/advanced/AsyncActions.md) in Redux. In this tutorial we'll explain how to build a simple React Native app fetching the photo of the day from [NASA's APOD API](https://api.nasa.gov/index.html). We'll also use the [Shoutem UI toolkit]({{ site.url }}/docs/ui-toolkit/introduction). Here's how the completed app should look:

The complete code for this extension is available in our [GitHub repository](https://github.com/shoutem/extension-examples/tree/master/connecting-to-3rd-party-api). ## Initialize the Extension Shoutem apps are made of extensions, so let's start by creating a new extension. ```ShellSession $ shoutem init nasa Enter information about your extension. Press `return` to accept (default) values. Title: NASA Version: 0.0.1 Description: Photo of the day from Nasa ... Extension initialized! ``` Switch over to the extension's folder: ```ShellSession $ cd {{ site.example.devName }}.nasa ``` Create a new starting screen and shortcut: ```ShellSession $ shoutem screen add PhotoDay ? Screen name: PhotoDay ? Create a shortcut (so that screen can be added through the Builder)? Yes ? Shortcut name: Photo ? Shortcut title: Photo ? Shortcut description: A shortcut for PhotoDay ... Success ``` Push the extension to Shoutem: ```ShellSession $ shoutem push Uploading `NASA` extension to Shoutem... Success! ``` Install that extension to a new app. You can create a new app on the Builder and then install the extension into that app, or directly create a new app through the CLI: ```ShellSession $ shoutem install --new NasaApp Installing `NASA` extension to the new app... Extension successfully installed to the new app. Check it here: {{ site.shoutem.builderURL }}/app/{{ site.example.appId }} ``` Once this is done, go to the [Builder]({{ site.shoutem.builderURL }}) and add the screen inside the app. Now you can preview the app:

## Fetch the Photo Now let's fetch the photo into the screen. We'll use the Fetch API from JavaScript. This is the complete code from `app/screens/PhotoDay.js`. ```JavaScript #file: app/screens/PhotoDay.js import React, { Component } from 'react'; import { Screen, View, ImageBackground, Spinner, Tile, Title, Subtitle } from '@shoutem/ui'; // public API, you can get yours on: https://api.nasa.gov const apiKey = 'NNKOjkoul8n1CH18TWA9gwngW1s1SmjESPjNoUFo'; // NASA photo API url var photoUrl = "https://api.nasa.gov/planetary/apod"; export default class PhotoDay extends Component { state = { photo: null } componentDidMount() { fetch(photoUrl + '?api_key=' + apiKey, { method: 'GET', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', } }) .then((response) => response.json()) .then((photo => { this.setState({ photo }); })); } render() { const { photo } = this.state; // render Spinner is photo is not fetched const content = photo ? ( {photo.title} {photo.copyright} ) : ; return ( {content} ); } } ``` Push the changes you made to Shoutem: ```ShellSession $ shoutem push Uploading `NASA` extension to Shoutem... Success! ``` Preview it to see the changes. This is the final result:

================================================ FILE: docs/extensions/tutorials/_posts/1970-01-01-GettingStarted.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/getting-started title: Getting Started section: Tutorials --- # Getting Started Shoutem is a platform that enables you to build, publish and manage high-quality native iOS and Android apps. It's built on top of React Native and open sourced for developers. The philosophy behind Shoutem is to let you build apps efficiently without restricting you on how to use React Native. ## Apps and Extensions The efficiency of building apps is achieved with a simple architecture: apps are built using smaller modules called `extensions`. An extension is a self-contained and complete functionality that can be reused. Everything in the app is an extension: navigation, places (list and details), push notifications, analytics, ads, etc...

Shoutem prepared and [open sourced](https://github.com/shoutem/extensions) a lot of extensions that you can use in your apps. **Don't reinvent the wheel**: reuse extensions which are suitable for your app, customize them or create new ones. It's the world-first WordPress-like solution for mobile apps. ## The Builder Shoutem apps are managed on a beautiful web interface called the **Builder**. It allows you to host your project online and make it customizable for non-technical people, which is perfect for a developer's clients. It also allows developers to save time setting up a part of their app so they can focus on their own unique features. ## Your First App #### Prerequisites Before going through this tutorial, make sure you've installed the following: - [Node.js and npm](https://www.npmjs.com/) (installing `Node.js` also installs `npm`) - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) - react-native-cli - `$ npm install -g react-native-cli` - shoutem-cli - `$ npm install -g @shoutem/cli` - yarn - `$ npm install -g yarn` - [python 3.8.0+](https://www.python.org/downloads/release/python-380/) - fonttools - `pip3 install fonttools` (after installing Python) - OSX - > Coacoapods - `$ gem install cocoapods` > #### Note > If the above commands fail because of _permission_ issues, you need to run them with `sudo`, e.g.`sudo npm install -g `. If you haven't already, go to the [Builder]({{ site.shoutem.builderURL }}) and create an account. Once signed in, create a new `Blank app`. For this quick tutorial, we'll make a Restaurants and Food app, let's call it `Restaurants`. To rename your app, click on the `Blank app` text, delete it and type in `Restaurants`. Let's say we want to add an RSS feed screen to our app. To do that, click on the + button next to Screens and select the News RSS extension. Since the app is about Restaurants, to stick to the Food theme, let's use a food related News RSS feed: http://foodengineeringmag.com/rss/topic/2639-top-100-food-beverage-companies After you add this screen, it should look something like this:

## Creating a New Extension As a developer, you use the Shoutem CLI to handle the lifecycle of extensions. Start by using the `shoutem login` command with your Shoutem credentials ("{{ site.example.devName }}" is used as a developer name in this example). ```ShellSession $ shoutem login Enter your Shoutem credentials (obtained at {{ site.shoutem.builderURL }}): Email: {{ site.example.devEmail }} Password: Logged in as {{ site.example.devEmail }}. Enter developer name. Developer name: {{ site.example.devName }} Registered as `{{ site.example.devName }}`. ``` Clone the app you made in the Builder to your machine by using `shoutem clone` and selecting your app from the list: ```ShellSession $ shoutem clone Select your app: Restaurants ({{ site.example.appId }}) Cloning `Restaurants` to `Restaurants` ... ``` `shoutem clone` turns your app you see on the Builder into a Shoutem flavored React Native app locally. Once the cloning process is done, locate to `Restaurants/extensions` (where you can find all the extensions installed into your app) and create your new extension using `shoutem init`: ```ShellSession $ cd Restaurants/extensions $ shoutem init restaurants Enter information about your extension. Press `return` to accept (default) values. Title: Restaurants Version: 0.0.1 Description: A restaurants extension. Initializing extension: ... Extension initialized! ``` The `shoutem init` command bootstrapped the `{{ site.example.devName }}.restaurants` folder with extension files. Let's add a screen that we'll use as a list of restaurants in the My first extension tutorial series, so we'll name it `List` and give it a shortcut `Restaurants`. A shortcut is the pointer to the starting screen of an extension. When you add screens to your app on the Builder, you're actually adding the shortcut to that screen. ```ShellSession $ cd {{ site.example.devName }}.restaurants $ shoutem screen add List ? Screen name: List ? Create a shortcut (so that screen can be added through the Builder)? Yes ? Shortcut name: Restaurants ? Shortcut title: Restaurants ? Shortcut description: A shortcut for List ... Success ``` The previous command created a `List` screen in `app/screens/List.js` file. Any time you make a new screen it'll be a simple _Hello World_ screen. To fit the app, let's change the screen so it says `Let's eat!` instead of `Hello World!`: ```JavaScript{5} #file: app/screens/List.js export default class List extends Component { render() { return ( Let's eat! ); } } ``` Now push what we've built to Shoutem with `shoutem push`: ```ShellSession $ shoutem push Checking the extension code for syntax errors... Uploading `Restaurants` extension to Shoutem... Success! ``` And install the extension into the Restaurants app using `shoutem install`: ```ShellSession $ shoutem install Select your app: Restaurants ({{ site.example.appId }}) Extension installed. See it in the builder: {{ site.shoutem.builderURL }}/app/{{ site.example.appId }} ``` > #### Note > You should run `shoutem configure` after installing new extensions into your app, so your local clone is synced with the Builder app! Now we need to add the screen to the app. Open the app in the Builder. Click on the `+` next to **Screens** and select the `Custom` category. You can see your `Restaurants` extension there. Click on it to add it's screen to Main navigation, just like you did with the News RSS.

Great! Let's make our newly created extension's screen the Starting Screen for the app. Just drag it to the top of Main navigation and you're done. Now when you preview the app, this is what you'll see:

**Well done!** You just built your first app using your own custom built extension! ## What's next? To leverage the full power of Shoutem, we'd suggest you go through the [My first extension]({{ site.url }}/docs/extensions/my-first-extension/introduction) tutorial, which explains the underlying concepts in more detail. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-01-PublishYourApp.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/publish-your-app title: Publish your app section: Tutorials --- # Publish, Republish, Maintain You're satisfied with how your app looks and you want to **publish** it to the store. This tutorial shows you how to publish your app to both the [App Store]({{ site.external.appleAppStore }}) and [Google Play]({{ site.external.googlePlayStore }}). We'll also describe what it means to republish and how to maintain the data in your app. There are 2 ways you can publish your app: - using the Shoutem automated app publishing tool - manually, as you would with any other React Native app Once your app is published, for any changes you do in the Builder (except for changes in data on Shoutem Cloud Storage) and want to get onto the live app, you will need to **republish** the app. Shoutem does the republishing automatically for you and most of the changes will be available seconds after you click the `Republish` button. As the data used in app might change over the time, we made it easy for you **maintain** it using the Shoutem CMS. # Using Shoutem Automated App Publishing Tool The Shoutem platform's goal is to cover the complete process of app developing: from prototyping, developing, designing, to publishing and maintaining. That's why we developed a tool for automated app publishing, which is integrated into the Shoutem Builder. #### Prerequisites The only prerequisite to publish an app using Shoutem is to have your developer accounts ready. If you don't have them, creating them is easy: - to publish apps to the App Store (for iOS devices), create an Apple [iOS Developer Account]({{ site.external.appleDeveloperAccount }}) - to publish apps to Google Play (for Android devices), create a [Google Play Developer account]({{ site.external.googlePlayDeveloperAccount }}) #### Publishing There are three simple steps to publish your app: 1. Grant Shoutem permission to publish apps using your developer accounts 2. Fill in the app details (description, screenshots, ...) inside the Builder 3. Click the `Publish` button The first two steps are only done once. ##### Granting Shoutem Permission You need to grant Shoutem permission to stores where you want to publish your app. **App Store** Go to `Settings` -> `Store metadata` and the `iOS` tab should be selected. Find the _iOS Developer Account_ field and enter your credentials there. Don't worry - our tool will only handle new apps and won't do anything that you don't want it to do. If, however, you don't want to share your credentials with us, there's a slightly more complex way to grant us permission to the Apple Developer portal and iTunes Connect separately. For that, you should contact [our support]({{ site.shoutem.support }}). **Google Play Store** Go to `Settings` -> `Store metadata` and select the `Android` tab. Find the _Google Play Android Developer Account_ field. You will find these steps to invite a new user: ``` You need to invite shoutem@shoutem.com to use your Android dev account as an administrator. Log in to your Android developer account Click 'Manage user accounts...' link Click 'Invite a new user button' Enter 'shoutem@shoutem.com' and send invitation ``` Once you do that, write your Android developer account owner and console account name. ##### Fill in the App Details Instead of going to each store to fill in app information, we've put everything in one place. Go to `Settings` and fill out information under `App info` and `Store metadata`. ##### Click the `Publish` Button After you finish the first two steps, click the `Publish` button. Your app will go into the review process in both stores and we'll be notifying you of the process.

Once your app is published, the `Publish` button will become `Republish`. ### Automatic Republishing Using Shoutem, in most cases the republishing of your app lasts a few seconds. Changes that you made in the app's colors, settings, RSS feeds, order or number of extensions being used in the app structure, all of that will be visible immediately once you click the `Republish` button. Even installing new extensions, updating existing extensions or uninstalling extensions will be visible automatically. The only two changes that will require an app to go over the reviewing process in the stores are: - updating the [Shoutem platform]({{ site.url }}/docs/extensions/reference/platform) being used in the app - installing, updating or uninstalling extensions which contain **native code** In those cases, you still just have to click the `Republish` button and we'll take care of the rest and will notify you of the process. ### Maintaining the App Data Once your app is live, you might want to change some data that's being used in the app. The data shown in Shoutem CMS (used in Shoutem Cloud Storage) is live and changes to it will be immediately visible in the apps using Shoutem Cloud Storage. This is also the case for manually published apps using Shoutem Cloud. # Manual Publishing and Republishing If you want to have a complete overview of what publishing of your app looks like, you might want to publish your app manually. The process is longer, but is functionally identical to publishing a regular React Native app. Here we describe the required steps for both stores. Also, it's not possible to manually publish to the App Store if you don't have an Apple Device (MacBook, Mac-Mini, MacPro). ## App Store This is the manual process for publishing an iOS app to the App Store. ### Prerequisites - **Apple Device:** In order to create your iOS app, an Apple computer (MacBook, Mac-Mini, MacPro) device is required since Xcode is not available on any other platforms - **[Xcode]({{ site.external.xcode }}):** Apple's program for development and distribution of iPhone apps - **Active [iOS Developer Account]({{ site.external.appleDeveloperAccount }}):** a paid iOS developer account for submitting your app onto the App Store ### Building your App To prepare your app for release, you will have to use `shoutem configure --production` in the cloned app's directory. This will bundle the assets of the app as well as configure all relevant files to utilize Builder defined values (e.g. bundle ID, Push Notifications, Analytics, etc.). Furthermore, you can customize the bundle ID for your app via the `iosBundleId` and `androidApplicationId` properties in the `config.json` file in the cloned app's root directory. These settings override the Builder defined values. After doing this short step, the build process is functionally identical to a [normal React Native app](https://medium.com/react-native-development/deploying-a-react-native-app-for-ios-pt-1-a79dfd15acb8). ### Preparing for the Store Once you have the build ready, you can start preparing your app for the store. #### Creating Certificates You will need to make an iOS distribution certificate and matching distribution provisioning profile for your app. Here's a short video tutorial on how to do it: You can also use [Apple's guide](https://developer.apple.com/library/content/documentation/IDEs/Conceptual/AppDistributionGuide/MaintainingCertificates/MaintainingCertificates.html#//apple_ref/doc/uid/TP40012582-CH31-SW1) where each step is explained in more detail. #### Resigning App Once you have an unsigned build (.ipa), you will need to resign it. You can do it quickly with the Terminal by using Fastlane's [sigh](https://github.com/fastlane/fastlane/tree/master/sigh) tool. Once sigh is installed, use: ```ShellSession $ fastlane sigh resign ... Successfully signed ShoutemApp.ipa! ``` > #### Note > Before you start this command, make sure you've added your [desired certificate into your keychain](https://superuser.com/questions/936840/add-to-my-certificates-in-keychain-access-mac-os-10-10). > #### Note > `sigh` will find the build file and the provisioning profile for you if they are located in the current folder. If they're not in the current folder, provide more information to `sigh`: `fastlane sigh resign ./path/app.ipa --signing_identity "iPhone Distribution: Dummy user" -p "my.mobileprovision"` #### Creating an app on iTunes Connect [iTunes Connect](https://itunesconnect.apple.com) is Apple’s service for uploading your app and its metadata. From there, the app will eventually become available in the App Store. Let's create an app there. Login to iTunes Connect with your iOS Developer account, click on `My Apps`, then click on the `+` in the top left corner. You should get this menu:

Fill your app details. Once you are done with that, click on `Create`. Your app is now created on iTunes Connect and you are few steps away from publishing it. Fill the details of your app's privacy policy link, category and license agreement.

In `Pricing and Availability`, choose to make your app free or paid and in which countries you want to make it available.

Upload app's metadata. Click on iOS app which is currently in “Prepare for Submission” state. On this screen, you will upload your app’s screenshots, add it’s description, keywords, support URL, content rating, app store artwork and review contact information. After doing so, upload your build file (.ipa). To upload it, you use tool called Application Loader, which is one of the tools that comes along with Xcode. Find it in Xcode: Select Xcode in the menu -> Open Developer Tools -> Application Loader. This is what you should get:

Login with your iOS Developer account, choose the “Deliver your app” option and upload your .ipa file to iTunes Connect. After uploading it, the app will go trough processing time in iTunes Connect. Processing time can vary from few minutes to few hours (even days) and it depends on the stability of the Apple’s services. If you click on the `Activity tab` in iTunes Connect, you will see this while your app is in processing time:

Once the processing of your app is done, go back to your iOS app and scroll down to the `Build section`. You'll notice that a little `+` sign has appeared next to `Build`.

Click on it and the build of your app is processed and ready for preview. After adding a build, click on the `Submit for Review` button in the top right corner. Finally, you need to declare your app's export compliance, content rights and advertising info:

Answer those questions and click the `Submit` button. Your app status will change to "Waiting for review" state which means that your app has been sent to Apple's review team. ### Review Apple usually takes 2-3 days to review apps. Once they approve (or reject) the app, you should get the confirmation e-mail to your owner's e-mail regarding the review. ### Manual Republish If you make some changes in the app (colors, titles, change which extensions you use or their order), you will need to republish your app. Manual republishing of the app requires you to go through all the steps described here except creating new developer account and certificates. ### Maintaining the App Data Once your app is live, you might want to change data used in the app. The data shown in Shoutem CMS (used in Shoutem Cloud Storage) is live and changes to it will be immediately visible in the apps using Shoutem Cloud Storage. ## Google Play Store This is a manual process of publishing an android app in the Google Play Store. ### Prerequisites - **[Android Studio]({{ site.external.androidStudio }}):** Tool for developing Android apps and is also used for app signing - Android SDK tools: can be installed within Android Studio - **[Google Play Developer account]({{ site.external.googlePlayDeveloperAccount }})** ### Building your app To prepare your app for release, you will have to use `shoutem configure --production` in the cloned app's directory. This will bundle the assets of the app as well as configure all relevant files to utilize Builder defined values (e.g. bundle ID, Push Notifications, Analytics, etc.). Furthermore, you can customize the bundle ID for your app via the `iosBundleId` and `androidApplicationId` properties in the `config.json` file in the cloned app's root directory. These settings override the Builder defined values. After doing this short step, the build process is functionally identical to a [normal React Native app](https://facebook.github.io/react-native/docs/signed-apk-android.html). ### Preparing for Store Once you have the build ready, you can start preparing your app for store. #### App Signing and Zipalign Once you have an unsigned build (.apk) you need to sign it with your own keystore. Here's the [official documentation](https://developer.android.com/studio/publish/app-signing.html) on how to do that. Basically, these are the steps you will need to do: ##### App Signing 1. Navigate to directory with the unsigned .apk file 2. Sign your app: ```ShellSession $ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore mykey.keystore myfile.apk alias_name ``` - mykey.keystore contains the path to your keystore file - myfile.apk is name of your .apk file - alias_name is the name of the key that you are using during signing process After your .apk file is signed, it's ready for the zipalign process. ##### Zipalign 1. Put your unaligned .apk file in the desired directory 2. Navigate to android sdk build-tools directory and choose your desired sdk tools (for example 22.0.1): ```ShellSession $ cd /path/to/your/android-sdk/build-tools/22.0.1/ ``` 3. Now, zipalign it: ```ShellSession $ ./zipalign -v 4 /path/to/unaligned.apk /path/for/release-aligned.apk ``` 4. The zipalign tool will start compressing your .apk file and once it's done it will print out “Verification succesful” in your terminal. Now if you look into the directory that you set for the aligned .apk file, you will find a new, aligned .apk file ready for upload to the Google Play Store. #### Prepare App for Publishing Create your app in the Google Play Developer Console. Log in with your Android Developer account credentials and select `Add New Application` in the [Google Play Developer Console panel](https://play.google.com/apps/publish/):

Once you’ve entered that information, you'll be taken to the `Store Listing` screen, where you need to update your app's short description, description, screenshots, featured graphics, category and application type and add link to your privacy policy:

#### Upload your App to Production Under the `App Releases` tab, choose how you want to publish your Android app - is it for production, beta or alpha:

The process is pretty much the same for each release, so we will cover production release. Click on `Manage production` and then on `Create New`. This will take you to:

Here you can upload your .apk file, choose the release name and "What's Next" text. Once you're done, click on `Review`. #### Content Rating of your App Answer Google's content rating questionnaire and choose your app’s content category. Add your email address and choose from one of six available categories.

Each category contains slightly different questions which need to be answered mostly as Yes/No questions. Click `Save questionnaire`, then `Calculate rating`. You will see the ratings for the various locales where your app may be on sale. Scroll to the bottom of the list and click `Apply rating`. The `Content Rating` check mark is now green.

#### Pricing and Distribution Select the `Pricing & Distribution` check mark for the final step: setting the price of your app and in which countries it will be available.

Below this, there are a few checkbox questions regarding your app distribution. Check the boxes by Content guidelines and US export laws to indicate your app’s compliance with relevant rules, then scroll to the top of the page and click `Save draft`.

### Release the App Select your `App Release` tab again and select `Edit release` under `Manage Production`:

Scroll down, click on the `Review` button. Check your app details and if you are satisfied, click on the `Start rollout to production` button to publish your app in the `Google Play Store`. Clicking on the confirm button, you will publish your app in the Store! ### Manual Republish If you make some changes in the app (colors, titles, change which extensions you use or their order), you will need to republish your app. Manual republishing of the app requires you to go through all the steps described here except creating new developer account and certificates. ### Maintaining the app data Once your app is live, you might want to change the data used in the app. The data shown in Shoutem CMS (used in Shoutem Cloud Storage) is live and changes to it will be immediately visible in the apps using Shoutem Cloud Storage. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-01-SettingLocalEnvironment.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/setting-local-environment title: Setting up your Local Environment section: Tutorials --- # Setting up your Local Environment In this tutorial we will explain how to set up a local environment that allows you to preview changes to your app in real time. In other words, once you're set up, you won't have to _push_ your extension to Shoutem every time you want to see the changes you made to it. You can do this using your own physical device or an emulator. To be able to follow this tutorial, you should go through our [Getting Started]({{ site.url }}/docs/extensions/tutorials/getting-started) tutorial so you have a custom extension as well as a cloned app to easily test your local environment with. ## Local Development There are limitations to what the Builder can preview. Namely, it can only preview apps that have no native code linked into them. This is because it has it's own binary, so it can only preview changes made to the JavaScript bundle of the app. To work on apps with native code changes, you can use `react-native run-ios` and `react-native run-android` inside your cloned app's directory, the same way you'd use them with any other React Native app! You can find out how to set up your local environment for React Native development using [Facebook's official documentation](https://facebook.github.io/react-native/docs/getting-started.html). Make sure you strictly pass through all the steps described there.

Once you have that set up, you can continue your work like it was a regular React Native app. ```ShellSession $ react-native run-ios Scanning 706 folders for symlinks in /path/to/Restaurants/node_modules (18ms) ... ``` ## Real Time Code Changes We will now explain how to preview code changes in your extensions in real-time, in other words, to be able to see changes in your extension as you make them **without** having to push your extension to Shoutem every time you make a change. In [Getting Started]({{ site.url }}/docs/extensions/tutorials/getting-started), we already cloned the app we made, so you can just navigate to it's directory and run it locally: ```ShellSession $ cd Restaurants $ react-native run-ios Scanning 706 folders for symlinks in /path/to/Restaurants/node_modules (5ms) ... ``` Changes made to JavaScript code can be seen instantly now, all you have to do is reload the app: #### iOS - ⌘R - ⌘D -> Click on `Reload` #### Android - press R twice - Ctrl+M -> Click on `Reload` Now you can develop your extension much faster because you can see the changes you make to your extension in real time, exactly like a regular React Native app. Let's see how this works. Change something inside your extension from [Getting Started]({{ site.url }}/docs/extensions/tutorials/getting-started), for example you could add another line of `` to the `List` screen: ```javascript{5} #file: Restaurants/extensions/{{ site.example.devName }}.restaurants/app/screens/List.js render() { return ( Let's eat! This is my first extension! ); } ``` Save the changes, reload the device as previously described and you should see your new line of text right there. ### Managing your app's Configuration This section will cover the uses of the configuration script and associated commands. #### shoutem configure ```ShellSession $ shoutem configure > @shoutem/mobile-app@1.1.2 configure /path/to/Restaurants > node scripts/configure ... ``` The command should be called when: - an extension is installed, updated or uninstalled - changing native code of any of the extensions from the `extensions` directory - changing cocoapods or gradle dependencies of any of the extensions from the `extensions` directory - switching between published (`shoutem configure --release`) and development (`shoutem configure`) configuration
#### shoutem configure --release ```ShellSession $ shoutem configure --release > @shoutem/mobile-app@1.1.2 configure /path/to/Restaurants > node scripts/configure "--release" ... ``` This command should be called when: - bundling assets into the app
#### shoutem configure --production ```ShellSession $ shoutem configure --production > @shoutem/mobile-app@1.1.2 configure /path/to/Restaurants > node scripts/configure "--production" ... ``` This command should be called when: - switching between development (`shoutem configure`) and last published (`shoutem configure --production`) configuration - bundling assets into the app - preparing app for [Apple App Store and Google Play Store Submission]({{ site.url }}/docs/extensions/tutorials/publish-your-app) - activating Shoutem features like CodePush, Push Notifications and Analytics ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-02-WritingATheme.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/writing-a-theme title: Writing a Theme section: Tutorials --- # Writing a Theme Shoutem comes with a dozen of available themes, but if you want a custom one, you can write your own. To start, it's important that you understand two concepts (extension parts): - Theme: file containing all the style for your app - Theme variables: schema for the Builder that describes what app owners can customize in your theme through the Builder itself > #### Note > This tutorial continues on [My First Extension]({{ site.url }}/docs/extensions/my-first-extension/introduction). If you don't have an app which is the result from the My First Extension section, you can find the `Restaurants` extension on [Github](https://github.com/shoutem/extension-examples/tree/master/tom.restaurants-getting-started), install it onto a new app and fill it with some restaurants. ## Creating a Theme To create a theme within the existing Restaurants extension, switch over to the `Restaurants` extension folder: ```ShellSession $ cd {{ site.example.devName }}.restaurants ``` Create a theme and fill it with basic data: ```ShellSession $ shoutem theme add restaurant Enter information about your theme. Press `return` to accept (default) values. Title: (Restaurants) Description: Awesome restaurant theme! File `app/themes/restaurant.js` is created. File `server/themes/restaurantVariables.json` is created. ``` The `extension.json` file was modified to include the newly created theme: ```JSON{9-19} #file: extension.json { "name": "restaurants", "version": "0.0.1", "title": "Restaurants", "description": "List of restaurants", "shortcuts": [{ ... }], "screens": [{ ... }], "dataSchemas": [{ ... }], "themes": [{ "name": "restaurant", "title": "Restaurant", "variables": "@.restaurant" "description": "Awesome restaurant theme!", "showcase": "" }], "themeVariables": [{ "name": "restaurantVariables", "path": "server/themes/restaurantVariables.json" }] } ``` The `showcase` property, which is empty, is an array of images and videos that will show off your theme. Download this prepared [showcase]({{ site.url }}/img/tutorials/settings-theme/assets.zip) and copy it to the `server/assets` folder. Change the `showcase` property to: ```JavaScript #file: extension.json "showcase": [ "server/assets/list.png", "server/assets/details.png" ], ``` As stated above, you can add videos as well to show off your theme. The CLI also made sure that `app/extension.js` handles the newly created theme: ```JavaScript{9-10,17-19} #file: app/extension.js // This file is managed by Shoutem CLI // You should not change it import pack from './package.json'; // screens imports import List from './screens/List'; import Details from './screens/Details'; // themes imports import restaurant from './themes/restaurant'; export const screens = { List, Details }; export const themes = { restaurant }; export function ext(resourceName) { return resourceName ? `${pack.name}.${resourceName}` : pack.name; } ``` And that our public API, `app/index.js`, exports the newly created theme: ```JavaScript{8} #file: app/index.js // Reference for app/index.js can be found here: // http://shoutem.github.io/docs/extensions/reference/extension-exports import reducer from './reducer'; import * as extension from './extension.js'; export const screens = extension.screens; export const themes = extension.themes; export { reducer }; } ``` Check `app/themes/restaurant.js` file. It's a copy of Shoutem's default theme - Rubicon. Push this theme to the Shoutem server. This might take a while, since you need to upload the `showcase` files too: ```ShellSession $ shoutem push Uploading `Restaurants` extension to Shoutem... Success! ``` Since the Restaurants extension is already installed, so is your theme. However, it's not applied yet. Navigate to the `Styles` tab. Currently, the selected theme is `Rubicon`, clearly no match for your gorgeous new theme, so let's fix that. Click on the `Change theme` button. Here you can find every installed theme. Yours is also there! Select it and you'll see the showcase you set up for it earlier.

Select `Apply theme`. Now check the `Customize theme` tab.

Here the app owner can customize your theme through theme variables. These variables can be found in `server/themes/restaurantVariables.json` and they're a copy of the [Rubicon theme variables](https://github.com/shoutem/extensions/blob/master/shoutem-rubicon-theme/server/primeThemeVariables.json). ## How Themes Work A theme is a set of styling rules that customize components in the app connected to the theme, called `customizable components`. All the components in [@shoutem/ui](https://github.com/shoutem/ui) package are connected to the theme so they all share the same style. The theme file, `app/theme/restaurant.js` exports the **theme function** that resolves theme variables and returns **theme objects**. Theme objects consist of [styling rules]({{ site.url }}/docs/ui-toolkit/theme/introduction#theme-style-rules) defined by the [@shoutem/theme](https://github.com/shoutem/theme) package. Each component is connected to the theme by the `name`, by which it can be targeted in theme. Open `app/theme/restaurant.js` and check the styling rules used in `Rubicon` theme. Let's create a customizable component now. ## Customizable Components Suppose we want to create a theme which will make the title in restaurant rows bigger and change the background color of subtitles to white, while changing the text color to black. Since restaurant rows are defined in `app/screens/List.js` we can make that component customizable or encapsulate a separate restaurant row component. Let's go with the first, simpler solution. From the docs on how to use `@shoutem/theme`, in order to support the theme, we need to: - Replace the occurrences of styles with this.props.style - Connect the component to the theme We didn't use `style`, but now we're going to use it from `this.props.style` in the `renderRow` method. Also, import `connectStyle` from `@shoutem/theme` to connect the component to the theme and assign a `name` to it. ```JavaScript{20,53,63-64,92-94} #file: app/screens/List.js import React, { PureComponent } from 'react'; import { TouchableOpacity } from 'react-native'; import { connect } from 'react-redux'; import { navigateTo, NavigationBar } from 'shoutem.navigation'; import { find, isBusy, shouldRefresh, getCollection } from '@shoutem/redux-io'; import { connectStyle } from '@shoutem/theme'; import { ImageBackground, ListView, Tile, Title, Subtitle, Overlay, Divider, Screen } from '@shoutem/ui'; import { ext } from '../const'; class List extends PureComponent { constructor(props) { super(props); // bind renderRow function to get the correct props this.renderRow = this.renderRow.bind(this); } componentDidMount() { const { find, restaurants } = this.props; if (shouldRefresh(restaurants)) { find(ext('Restaurants'), 'all', { include: 'image', }); } } renderRow(restaurant) { const { navigateTo, style } = this.props; return ( navigateTo({ screen: ext('Details'), props: { restaurant } })}> {restaurant.name} {restaurant.address} ); } render() { const { restaurants } = this.props; return ( this.renderRow(restaurant)} /> ); } } export default connect( (state) => ({ restaurants: getCollection(state[ext()].allRestaurants, state) }), { navigateTo, find } )( connectStyle(ext('List'))(List) ); ``` As you can see, `connectStyle` takes the same format as the `connect` method from Redux. Notice that we added the extension prefix to the component name. While not necessary, it's a good practice to prevent namespace collisions. Ok, we've added style from theme to the component, but we haven't implemented those styling rules in the theme, so lets modify our theme. ## Modifying Themes We created a theme file (`app/themes/restaurant.js`) with a Rubicon template. Since a Shoutem app can only have 1 theme applied at a time, it's a good practice to include styling rules for the components usually used in Shoutem extensions, such as the ones from `@shoutem/ui`. The theme file is **huge** and it won't be pasted into the code snippet here fully. Just search for the `export default` statement which exports theme functions. In `return`, add the agreed styling rules: - make titles bigger in restaurant rows - change the background color of subtitles to white - change the subtitle color to black Import the `ext` function and add the following styling rules to the beginning of the exported object: ```JavaScript{3,14-22} #file: app/themes/restaurant.js // other imports ... import { ext } from '../const'; // exports and constants ... export default (customVariables = {}) => { const variables = { ...defaultThemeVariables, ...customVariables, }; return _.merge({}, getTheme(variables), { [ext('List')]: { title: { fontSize: 25, }, subtitle: { color: 'black', backgroundColor: 'white' } }, // the rest of the styling rules... }) ``` Styling props for `Text` components are documented in [React Native documentation](https://facebook.github.io/react-native/docs/text.html). Great! Push the extension now. ```ShellSession $ shoutem push Uploading `Restaurants` extension to Shoutem... Success! ``` Now open the app in the Builder preview.

This is good, but what if we got a request from people using our theme that they want to be able to modify subtitle text color? We can do that with variables, so they don't have to even leave the Builder. ## Customizing Themes with Variables Exposes variables and defines UI editor types to edit these variables. To enable customization of themes, we use the theme variables schema. The schema was already created when we added the theme to the project. It's the `server/themes/restaurantVariables.json` file. We use it to define the UI editor for adjusting theme variables used in styling rules, which gives Shoutem the information about which variables and their formats are being used for the theme. The full schema reference can be found [here]({{ site.url }}/docs/extensions/reference/theme-variables). Open the `Style` tab and choose `Customize theme`. You can see that theme variables are grouped into sections. Under `properties`, add a new variable with a `color` format, where `black` is the default value. Afterwards, reference that variable in `layout.sections` so it's included in the interface. We'll create a new section for that. ```JSON{3-7,13-15} #file: server/themes/restaurantVariables.json { "properties": { "subtitleColor": { "type": "string", "format": "color", "title": "Subtitle color", "default": "black" }, // other variables }, "layout": { "sections": [{ "title": "Restaurants", "properties": ["subtitleColor"] }, { // other sections }] } } ``` The only thing left to do is to use this variable in the theme file. Again, search for the `export default` statement: ```JavaScript{13} #file: app/themes/restaurant.js export default (customVariables = {}) => { const variables = { ...defaultThemeVariables, ...customVariables, }; return _.merge({}, getTheme(variables), { [ext('List')]: { title: { fontSize: 25, }, subtitle: { color: 'black', backgroundColor: 'white' } }, // the rest of the styling rules... }) ``` You're done! Push the extension that contains your theme to Shoutem. ```ShellSession $ shoutem push Uploading `Restaurants` extension to Shoutem... Success! ``` Check `Customize theme` under the `Style` tab. You can see the `Restaurants` section with a color picker for subtitle text color. Well done!

## Adding a Custom Font In order to add a custom font to a theme, you'll have to add a `fonts` folder to your extension's `app` folder and add your custom fonts to it in `.TTF` format. You can see this structure in our [Rubicon theme](https://github.com/shoutem/extensions/tree/master/shoutem-rubicon-theme/app/fonts) extension. For the purpose of this tutorial you can use the [Roboto font](https://www.fontsquirrel.com/fonts/roboto). These fonts also have to be linked into the app's binary as [assets](https://github.com/rnpm/rnpm#advanced-usage) using [`rnmp`](https://github.com/rnpm/rnpm). You do this by adding the following to your extension's `app/package.json` file: ```JSON{5-9} #file: app/package.json { "name": "{{site.example.devName}}.restaurants", "version": "0.0.1", "description": "My first extension.", "rnpm": { "assets": [ "fonts" ] } } ``` > #### Note > In the above code snippet, `fonts` is the name of the folder: `app/fonts`. In order to be able to choose the font you've added through the Builder, it has to also be added to your theme's variables in the `server/themes/restaurantVariables.json` file. The custom font we've provided you with is called `Roboto` and we'll be using the `Roboto-Regular` version if it. ```JSON{7} #file: server/themes/restaurantVariables.json "fontFamily": { "enum": [ "Rubik-Regular", "NotoSans", "NotoSerif", "MuktaVaani-Regular", "Roboto-Regular" ] }, ``` After that, you can _push_ your extension to update the changes you've made on the Builder. After choosing your newly created theme you will be able to use the custom font you added:

However, you won't be able to preview these changes in the Builder. This is because fonts are linked into the binary of the app, while the Builder previews as a predefined binary and only previews JavaScript bundle changes. So to be able to preview this you'll have to set up your local development environment so the custom font is linked into the local binary. You can find out how to do that [here]({{ site.url }}/docs/extensions/tutorials/setting-local-environment). > #### Note > If your theme extension is already inside a _cloned_ app for local development, you will have to re-clone the app. Make sure you push all your local changes before cloning again! Here you can see the difference between `Noto Serif` (left), a font included in our default theme, and our example custom font `Roboto Regular` (right):

## Adding a Custom Icon to the Builder Icons in the Builder are used to customize your app's icons in the Main navigation's different layouts:

When creating an app, you may want to use your own custom icon for the Main navigation, for instance you want to add a restaurant related icon for your Restaurants extension's screen. There's two ways you can do this: - uploading your own icon to that app through the Builder - makes the icon available to only that app - adding that icon to your own custom theme - makes the icon available to any app with the theme extension installed and activated ### 1) Uploading an icon to the Builder To do this, simply click on the icon you want to change to open the modal window in which you can choose the icon you want to use. You'll notice there's two categories: `Theme icons` and `My icons`. After selecting `My icons` you can see the list of icons you uploaded for that specific app. The disadvantage of using this method is that the icon will only exist for that app, while your extension might be installed in multiple apps. The `Theme icons` category refers to the icons from different themes. You can find out which icons are offered in our Rubicon theme [here](https://github.com/shoutem/extensions/tree/master/shoutem-rubicon-theme/server/assets/icons). ### 2) Adding an icon to your custom theme In order to add a custom icon to a theme, you'll have to create a `server/assets/icons` directory inside your extension's directory: ```ShellSession $ cd server $ mkdir assets/icons ``` And then add your custom icon to it. The reason we use the server folder is because this is utilized by the server side, i.e. the Builder. You can see this structure in our [Rubicon theme](https://github.com/shoutem/extensions/tree/master/shoutem-rubicon-theme/server/assets/icons). The icon you add should be a `.png` and `48x48` resolution. In this example tutorial we'll use the previously mentioned restaurant related icon, a plate and utensils. After creating the directory and adding your custom icon to it you have to modify the `extension.json` file to include icons: ```JSON{6} #file: extension.json "themes": [{ "name": "restaurant", "title": "Restaurant", "variables": "@.restaurant" "description": "Awesome restaurant theme!", "icons": "server/assets/icons/", "showcase": [ "server/assets/list.png", "server/assets/details.png" ] }], ``` You can see this in our [Rubicon theme](https://github.com/shoutem/extensions/blob/master/shoutem-rubicon-theme/extension.json). Now you can simply _push_ to update your extension on Shoutem and once you open your Restaurants app in the Builder and check the Main navigation icons you can see your new icon under the `Theme icons` category.

The other icons you see are a copy of the icons provided by the [Rubicon theme](https://github.com/shoutem/extensions/tree/master/shoutem-rubicon-theme/server/assets/icons), because, like we said, when you create a custom theme you're making a copy of Rubicon. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-03-ScreenLayouts.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/screen-layouts title: Screen Layouts section: Tutorials --- # Screen Layouts
Each screen can have multiple layouts. App owners can choose which screen layout they want to use in their app (e.g. for news from politics they might want to use a layout with smaller images, and for fashion they might want large images). As screen layouts are just plain screens, they can contain different logic than the screen they are altering and can be easily used for A/B testing. We’ll create a different layout in the **Restaurants** extension from the [My First Extension]({{ site.url }}/docs/extensions/my-first-extension/introduction) tutorial, which you can [get here](https://github.com/shoutem/extension-examples/tree/master/tom.restaurants-getting-started). The Restaurants extension has a `List` screen (for listing all the restaurants) and a `Details` screen (for details of one particular restaurant). Let’s add one additional screen that will represent an alternative layout for the `List` screen where we'll use smaller images as shown here:

Switch over to the extension folder: ```ShellSession $ cd {{ site.example.devName }}.{{ site.example.extensionName }} ``` Create an additional screen: ```ShellSession $ shoutem screen add SmallList ? Screen name: SmallList ? Create a shortcut (so that screen can be added through the Builder)? No Success ``` Again, notice how we didn't create a Shortcut. That's because SmallList isn't the starting screen of the extension, `List` is. Now extend the `List` screen and override its `renderRow` method. We're going to use the `Row` component from [UI toolkit]({{ site.url }}/docs/ui-toolkit/components/rows). This is the complete code for `SmallList.js` file with the main parts being highlighted. ```javascript{15-17,27-50} #file: app/screens/SmallList.js import React from 'react'; import { TouchableOpacity } from 'react-native'; import { connect } from 'react-redux'; import { navigateTo } from 'shoutem.navigation'; import { find, getCollection } from '@shoutem/redux-io'; import { Image, Row, View, Subtitle, Caption, Divider, Icon } from '@shoutem/ui'; import { List } from './List'; import { ext } from '../extension'; export class SmallList extends List { // overriding the renderRow function renderRow(restaurant) { const { navigateTo } = this.props; return ( navigateTo({ screen: ext('Details'), props: { restaurant } })}> {restaurant.name} {restaurant.address} ); } } export default connect( (state) => ({ restaurants: getCollection(state[ext()].allRestaurants, state) }), { navigateTo, find } )(SmallList); ``` The Restaurants extension uses `CMS settings page`, so app owners can manage data in the app. We need to give them option to chose which layout they want to use. To do that, we're going to use the `layout settings page` from the [shoutem-layouts](https://github.com/shoutem/extensions/tree/master/shoutem-layouts) extension. The layout settings page resolves which screens have multiple layouts and shows the `layout selectors` for them. Example of two layout selectors for Shoutem's News RSS extension is shown below.

For the layout settings page to be able to resolve which screens should show layout selectors, we need to add the `extends` property to screens which act as additional layouts, referencing the "default" layout. Screen layouts inherit all the properties from extending screens and can override them. Reference the layout settings page in the `adminPages` shortcut and extend the screens in `extension.json`. ```JSON{9-10,15-17,30-33} #file: extension.json { "name": "restaurants", "version": "0.0.1", "platform": "1.0.*", "title": "Restaurants", "description": "List of restaurants", "screens": [{ "name": "List", "title": "List with large images", "image": "./server/assets/large-image-list.png" }, { "name": "Details" }, { "name": "SmallList", "title": "List with small images", "image": "./server/assets/small-image-list.png", "extends": "@.List" }], "shortcuts": [{ "name": "Restaurants", "title": "Restaurants", "description": "Allow users to browse through list of restaurants", "screen": "@.List", "adminPages": [{ "page": "shoutem.cms.CmsPage", "title": "Content", "parameters": { "schema": "@.Restaurants" } }, { "page": "shoutem.layouts.LayoutPage", "title": "Layout" }] }], "dataSchemas": [{ "name": "Restaurants", "path": "server/data-schemas/Restaurants.json" }] } ``` We also included additional properties like `title` and `image` to screens which will be shown in the layout selector, so they look nicer. [Download this file]({{ site.url }}/static/screen-layouts/assets.zip). It includes screen images that we can use as a preview of the layout in the layout selector. Extract the folder and place it in the `server` folder of the extension. Here you can find the [list]({{ site.url }}/docs/extensions/reference/extension) of all available properties in `extension.json`. If we wanted to show a layout selector for `Details` screen, we would need to add a `navigatesTo` property to the `List` screen. That way, the layout settings page could calculate the screen hierarchy starting from the screen referenced in the shortcut. An example of this can be found in the [extension.json](https://github.com/shoutem/extensions/blob/master/shoutem-rss-news/extension.json) file of Shoutem's News RSS extension. Ok, now you're done! Let's push the extension. ```ShellSession $ shoutem push Uploading `Restaurants` extension to Shoutem... Success! ``` > #### Note > If you don't have this extension installed on any app, you can install with `shoutem install`. Add the screen through the builder, add a few CMS items (Restaurants), as described in [Using cloud storage]({{ site.url }}/docs/extensions/my-first-extension/using-cloud-storage), and run the preview. If you don't have any restaurants added, you won't see anything in the preview. By default, the layout will be the `List` screen. Switch to `Layout` and select **List with small images**. This is the result you should get:

Great job! Now you know how to create additional layouts for your extension. Since we built this extension, we can add additional layouts to it directly. However, sometimes we want to add additional layouts for extensions from another developer - essentially we want to modify the extensions. You can find out how to do this in the [Modifying Extensions]({{ site.url }}/docs/extensions/tutorials/modifying-extensions) tutorial. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-04-WritingReactSettingsPage.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/writing-react-settings-page title: Writing React settings pages section: Tutorials --- # Writing a React settings page In this tutorial, we'll show you how to create React Settings pages for both shortcut settings and extension settings pages. ## Shortcut settings pages First, let's make an extension to work with. We'll make a simple `Hello World!` example so we can easily cover the basic concepts. ```ShellSession $ shoutem init react-hello-world Enter information about your extension. Press `return` to accept (default) values. ? Title React Hello World ? Version 0.0.1 ? Description Learning React settings pages. ... Extension initialized. ``` We need to add a screen with a shortcut, so we have a shortcut to add settings pages to. Locate to the extension folder: ```ShellSession cd {{ site.example.devName }}.html-hello-world ``` And add the screen: ```ShellSession $ shoutem screen add Hello ? Screen name: Hello ? Create a shortcut (so that screen can be added through the Builder)? Yes ? Shortcut name: Hello ? Shortcut title: Hello ? Shortcut description: A shortcut for Hello ... Success ``` Now let's create the actual settings page: ```ShellSession $ shoutem page add HelloWorldShortcutPage ? Page type: react ? Page name: HelloWorldShortcutPage ? Page title: Hello World Shortcut Page ? This settings page controls settings for: an existing screen ? Select existing screen: Hello ... React settings page added to pages/hello-world-shortcut-page ``` `HelloWorldShortcutPage` was also added to `extension.json` as an `adminPage` of the `Hello` shortcut and as one of the `pages` of the extension: ```JSON #file: extension.json "shortcuts": [ { "name": "Hello", "title": "Hello", "description": "A shortcut for Hello", "screen": "@.Hello", "adminPages": [ { "page": "@.HelloWorldShortcutPage", "title": "Hello World Shortcut Page" } ] } ], "pages": [ { "name": "HelloWorldShortcutPage", "type": "react-page" } ] ``` This is the `server` folder structure: ``` server/ | bin/ ├ node_modules/ ├ src | ├ pages/ | | └ hello-world-shortcut-page | | ├ index.js | | ├ HelloWorldShortcutPage.jsx | | └ style.css | ├ const.js | ├ extension.js | └ index.js └ package.json ``` It contains `bin` and `src` folders. The `bin` folder holds core react settings pages code and the webpack config, there shouldn't be any need to modify it. The `src` folder contains your extension's settings pages code divided into separate folders inside the `pages` folder. It also contains the `index.js` file where you export extension functionalities, and the `extension.js` file which is managed by the Shoutem CLI. As you can see, `HelloWorldShortcutPage` is added under `src/pages/hello-world-shortcut-page` folder. Inside is a React component `HelloWorldShortcutPage.jsx` that implements a shortcut settings page. It's a starting template that shows you how to manage shortcut settings with a simple input field. The template contains ordinary React lib with an addition of Shoutem libraries. ```JS #file: server/src/pages/hello-world-shortcut-page/HelloWorldShortcutPage.jsx import React, { Component, PropTypes } from 'react'; import { Button, ButtonToolbar, ControlLabel, FormControl, FormGroup, HelpBlock, } from 'react-bootstrap'; import { LoaderContainer } from '@shoutem/react-web-ui'; import { fetchShortcut, updateShortcutSettings, getShortcut, } from '@shoutem/redux-api-sdk'; import { shouldRefresh } from '@shoutem/redux-io'; import { connect } from 'react-redux'; import './style.scss'; ``` It uses: - @shoutem/react-web-ui - customized Bootstrap to Shoutem theme and contains useful React components - @shoutem/redux-api-sdk - official Shoutem API SDK that exports Redux actions, selectors and reducers which enable managing of Shoutem resources with React and Redux - `fetchShortcut(shortcutId)` - action for fetching shortcut resource - `updateShortcutSettings(shortcut, settingsPatch)` - action for updating shortcut settings resource - `getShortcut(state, shortcutId)` - selector for selecting shortcut from state - @shoutem/redux-io - library for data management of network data in redux and ease of data use in react - ./style.scss - style file used for custom styling of the settings page Before diving into an explanation of the `HelloWorldShortcutPage` React component, let's see what it receives in props. It receives `props` passed from the parent core component and from `connect` that binds React component to the Redux store. ```JavaScript #file: server/pages/hello-world-shortcut-page/HelloWorldShortcutPage.jsx function mapStateToProps(state, ownProps) { const { shortcutId } = ownProps; return { shortcut: getShortcut(state, shortcutId), }; } function mapDispatchToProps(dispatch, ownProps) { const { shortcutId } = ownProps; return { fetchShortcut: () => dispatch(fetchShortcut(shortcutId)), updateShortcutSettings: (shortcut, settings) => ( dispatch(updateShortcutSettings(shortcut, settings)) ), }; } export default connect(mapStateToProps, mapDispatchToProps)(HelloWorldShortcutPage); ``` The parent component passes a scope of information to the settings page depending on the context in which the settings page is rendered. Basically, it passes `extensionName, shortcutId, screenId` and `parameters` from extension definition. In `mapStateToProps` and `mapDispatchToProps` we prepare props for managing `shortcut` resources and you are open to add your props as you would in typical React development. Now we can focus on the `HelloWorldShortcutPage` React component that contains the same concepts as any other React component. Particularly, the component renders an input field and a Save button for managing a greeting in shortcut settings. ```JavaScript #file: server/pages/hello-world-shortcut-page/HelloWorldShortcutPage.jsx class HelloWorldShortcutPage extends Component { static propTypes = { shortcut: PropTypes.object, fetchShortcut: PropTypes.func, updateShortcutSettings: PropTypes.func, }; constructor(props) { super(props); this.handleTextChange = this.handleTextChange.bind(this); this.handleSave = this.handleSave.bind(this); this.handleSubmit = this.handleSubmit.bind(this); props.fetchShortcut(); this.state = { error: null, greeting: _.get(props.shortcut, 'settings.greeting'), // flag indicating if value in input field is changed hasChanges: false, }; } componentWillReceiveProps(nextProps) { const { shortcut } = this.props; const { shortcut: nextShortcut } = nextProps; const { greeting } = this.state; if (_.isEmpty(greeting)) { this.setState({ greeting: _.get(nextShortcut, 'settings.greeting'), }); } if (shortcut !== nextShortcut && shouldRefresh(nextShortcut)) { this.props.fetchShortcut(); } } handleTextChange(event) { this.setState({ greeting: event.target.value, hasChanges: true, }); } handleSubmit(event) { event.preventDefault(); this.handleSave(); } handleSave() { const { shortcut } = this.props; const { greeting } = this.state; this.setState({ error: '', inProgress: true }); this.props.updateShortcutSettings(shortcut, { greeting }) .then(() => ( this.setState({ hasChanges: false, inProgress: false }) )).catch((err) => { this.setState({ error: err, inProgress: false }); }); } render() { const { error, hasChanges, inProgress, greeting } = this.state; return (

Choose your greeting

Name:
{error && {error} }
); } } ``` You will notice how we trigger shortcut loading, reading the greeting value and updating it on Shoutem API once the user clicks the Save button. You are free to customize it in any way you need, implementing new React components and importing them into this file. This page is now created and referenced in the `Hello` shortcut in `extension.json`. Let's add our `greeting` setting to it and give it a default value: ```json{12-14} #file: extension.json "shortcuts": [ { "name": "Hello", "title": "Hello", "description": "A shortcut for Hello", "screen": "@.Hello", "adminPages": [ { "page": "@.HelloWorldShortcutPage", "title": "Hello World Shortcut Page" } ], "settings": { "greeting": "Tom" } } ] ``` ### Accessing the shortcut settings in the application The Shoutem CLI implemented the shortcut settings page into our pre-existing shortcut, all that is left to do is to access the settings in the `Hello` screen. Update the screen file: ```JS{3-4,8} #file: app/screens/Hello.js export default class Hello extends Component { render() { const { shortcut } = this.props; const { greeting } = shortcut.settings; return ( Hello {greeting}! ); } } ``` Now let's publish and install the extension. ```ShellSession $ shoutem push Uploading React Hello World extension to Shoutem... Success! ``` ```ShellSession $ shoutem install --new "React Hello World" Extension installed See it in browser: {{ site.shoutem.builderURL }}/{{ site.example.appId }} ``` Our default setting applies and the app owner has an input form to change the `greeting` value.

## Extension settings pages Extension settings pages provide you with settings that you can pass to every part of the extension, so in our simple use case, we'll suppose the extension settings page lets the app owner determine which company the person being greeted is working for, as you can see from the input form. The key difference between extension and shortcut settings pages is where they're defined in the `extension.json`. They're defined on the same level as `shortcuts` and `pages`. Let's create an extension settings page. ```ShellSession $ shoutem page add ? Page type: react ? Page name: HelloWorldExtensionPage ? Page title: Hello World Extension Page ? This settings page controls setting sfor: the 'react-hello-world' extension ... React settings page added to pages/hello-world-extension-page ``` The CLI added `HelloWorldExtensionPage` to the root level of `extension.json`, but let's add a default value. ```json #file: extension.json "settingsPages": [ { "page": "@.HelloWorldExtensionPage", "title": "Hello World Extension Page" } ], "settings": { "company": "Shoutem" } ``` The template page generated is pretty much identical to the one generated for `HelloWorldShortcutPage`, referencing `extension` instead of `shortcut` throughout it and using `company` instead of `greeting`. ```JavaScript #file: server/src/pages/hello-world-extension-page.jsx import { fetchExtension, updateExtensionSettings, getExtension, } from '@shoutem/redux-api-sdk'; ``` ```JavaScript{2-3,7} #file: server/src/pages/hello-world-extension-page.jsx

Enter company name

Company:
``` In order to access the extension settings from the actual app, we have to `connect` to the redux state, which means we have to add certain imports as well as use `mapStateToProps`. This is also included in the boilerplate. ```JavaScript function mapStateToProps(state, ownProps) { const { extensionName } = ownProps; return { extension: getExtension(state, extensionName), }; } function mapDispatchToProps(dispatch, ownProps) { const { extensionName } = ownProps; return { fetchExtension: () => dispatch(fetchExtension(extensionName)), updateExtensionSettings: (extension, settings) => ( dispatch(updateExtensionSettings(extension, settings)) ), }; } export default connect(mapStateToProps, mapDispatchToProps)(HelloWorldExtensionPage); ``` Let's retrieve that `company` value from the redux store and use it in our `Hello` screen. ```JavaScript{11-15,19,25,42-53} #file: app/screens/Hello.js import React, { Component } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { connect } from 'react-redux'; import { connectStyle } from '@shoutem/theme'; import _ from 'lodash'; import { getExtensionSettings } from 'shoutem.application'; import { ext } from '../const'; export class Greeting extends Component { render() { const { shortcut, company } = this.props; const { greeting } = shortcut.settings; return ( Hello {greeting}! You work for {company}. ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, text: { fontSize: 20, }, }); export const mapStateToProps = (state) => { const extensionSettings = getExtensionSettings(state, ext()); const company = _.get(extensionSettings, 'company'); return { company }; }; export default connect(mapStateToProps, undefined)( connectStyle(ext('Greeting'))(Greeting), ); ``` Finally, let's push the new version of our `react-hello-world` extension that we've made to Shoutem and see our extension settings page in action. ```ShellSession $ shoutem push Uploading React Hello World extension to Shoutem... Success! ```

So what's the purpose of extension settings pages as opposed to shortcut? Well, in our simple example, we made an extension where the app owner can define which company he's addressing and then make each Screen he adds on the Builder greet a unique employee. Each screen added will address the company defined in the Extension settings pages, while the app owner can choose which employee each screen greets. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-05-Installing3rdPartyPackages.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/installing-3rd-party-packages title: Installing 3rd Party Packages section: Tutorials --- # Installing 3rd Party Packages
Since Shoutem is an architecture built on top of React Native and we made sure not to restrict developers on how to use React Native, it's of course possible to utilize any and all 3rd party packages that you would be able to use with your regular React Native projects. This tutorial will show you how to install a 3rd party package into an extension and through that into the entire app. For an example we're going to use [react-native-swiper](https://github.com/leecade/react-native-swiper) as a non-native package and [react-native-camera](https://github.com/lwansbrough/react-native-camera) as a native package. ## 1) Installing a non-native Package A non-native package doesn't utilize native capabilities of the underlying device. Simply put, the package doesn't handle anything differently regardless of the fact it's being run on iOS or Android. When you finish this tutorial you'll have a functioning `swiper-extension`. ### Making an Extension To begin, we'll need an extension, so let's just make one. If this is something you don't know how to do yet, you should really go through [My First Extension]({{ site.url }}/docs/extensions/my-first-extension/introduction) to make sure you understand the fundamentals. We begin by initializing an extension and writing in the basic information about it. ```ShellSession $ shoutem init swiper-extension Enter information about your extension. Press `return` to accept (default) values. Title: Swiper Extension Version: 0.0.1 Description: Extension that uses react-native-swiper. Initializing extension: ... Extension initialized! ``` Let's switch over to the extension directory and add a screen with a shortcut that will show the Swiper. ```ShellSession $ shoutem screen add SwiperScreen ? Screen name: SwiperScreen ? Create a shortcut (so that screen can be added through the Builder)? Yes ? Shortcut name: Swiper ? Shortcut title: Swiper ? Shortcut description: A shortcut for SwiperScreen ... Success ``` ### Installing the Package All we have to do is install the package into the extension using `npm install react-native-swiper --save` in the `app` directory: ```ShellSession $ cd app $ npm install react-native-swiper --save {{ site.example.devName }}.swiper-extension@0.0.1 /absolute/path/swiper-extension/app └── react-native-swiper@1.5.4 ``` > #### Note > The reason we have to install it into the `app` directory is because the the `app` directory is bundled into the full app along with all the other extensions the app uses. Doing this will automatically add `react-native-swiper` as a dependency in our `app/package.json` file: ```json{5-7} #file: app/package.json { "name": "{{ site.example.devName }}.not-swiper", "version": "0.0.1", "description": "Extension that uses react-native-swiper.", "dependencies": { "react-native-swiper": "^1.5.4" } } ``` You can ignore the `node_modules` folder that you can now see in your extension's directory, because it'll be ignored since extensions are packed with `npm pack` before being installed into an app. ### Using the Package Our extension now has full access to everything `react-native-swiper` has to offer and we can use it just like we would in a normal React Native app, so let's make use of it's simplest example to show how it works. We'll have to edit our `app/screens/SwiperScreen.js` file to use the Swiper by importing the Swiper component, changing the `render();` method to use the Swiper component and making some changes to the `styles` constant so our Swiper component can function properly. ```javascript{11,16-26,32-54} #file: app/screens/SwiperScreen.js import React, { Component } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import Swiper from 'react-native-swiper'; export default class SwiperScreen extends Component { render() { return ( Hello Swiper Beautiful And simple ); } } const styles = StyleSheet.create({ text: { color: '#FFFFFF', fontSize: 30, fontWeight: 'bold', }, slide1: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#9DD6EB', }, slide2: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#97CAE5', }, slide3: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#92BBD9', } }); ``` And there we go, we implemented `react-native-swiper` into our extension. Now we need to push our extension to the Shoutem server and install it onto an app so we can actually test it out: ```ShellSession $ shoutem push Uploading `Swiper Extension` extension to Shoutem... Success! $ shoutem install --new SwiperApp Extension installed See it in browser: `{{ site.shoutem.builderURL }}/app/{{ site.example.appId }}` ``` Opening the SwiperApp in the Builder will show us an app with no Screens, but since we just installed our Swiper Extension onto the app, we can just add the Swiper screen to it by clicking on the + button next to Screens, going to the Custom category and selecting the Swiper Extension. As soon as it's loaded into the Main Navigation we can preview the app:

## 2) Installing a Native Package A native package utilizes native capabilities of the underlying device. When installing such a package we have to make sure we link the native dependencies it has. ## Shoutem Platform with React Native >=0.60 (2.2.0 and higher) With the introduction of autolinking, it is now surprisingly easy to add native packages to your app if they support it. Simply add the name of the package into your extension's `app/package.json` under a `nativePackages` property. ```json{8-10} #file: app/package.json { "name": "{{ site.example.devName }}.qr-reader-extension", "version": "0.0.1", "description": "An extension that scans QR codes and displays the encoded information to the user.", "dependencies": { "react-native-camera": "~3.11.0" }, "nativeDependencies": [ "react-native-camera" ] } ``` If your dependency doesn't support autolinking or you want to manually link it, you can use a `react-native.config.js` file inside of your extension's `app` segment to run a `prelink` or `postlink` hook, as well as disable autolinking for it. #### NOTE: We strongly recommend upgrading to packages that support autolinking as it will reduce maintenance and the number of bugs or other issues with building your app via Shoutem. Since extensions are effectively used as npm packages, the `react-native.config.js` file [should reflect this](https://github.com/react-native-community/cli/blob/master/docs/configuration.md#libraries). ```javascript #file: app/react-native.config.js module.exports = { dependency: { platforms: { android: null, ios: null, }, hooks: { postlink: 'node ./node_modules/tom.qr-reader-extension/scripts/run.js', }, }, }; ``` We use a postlink hook because our configuration script will run `react-native link` on any extension with `ios` or `android` directories, as well as if it has any `nativeDependencies` set in it's `package.json` file. If you're confused by what the `run.js` script is, please read the section after this one, where it's explained. ## Shoutem platform with React Native <0.60 (2.1.1 and lower) This is done using [postinstall scripts](https://docs.npmjs.com/misc/scripts). As an example, we'll be making a QR Code reader that's going to display what the scanned QR code says. To scan a QR code we'll need to use the devices camera, which we'll get access to using `react-native-camera`, a 3rd party package for utilizing device cameras. When you finish this tutorial you'll have a functioning `qr-reader-extension`, like the one from our [extension examples](https://github.com/shoutem/extension-examples). ### Making an Extension To begin, we'll need an extension, so let's just make one. If this is something you don't know how to do yet, you should really go through [My First Extension]({{ site.url }}/docs/extensions/my-first-extension/introduction) to make sure you understand the fundamentals. We begin by initializing an extension and writing in the basic information about it. ```ShellSession $ shoutem init qr-reader-extension Enter information about your extension. Press `return` to accept (default) values. Title: QR Reader Version: 0.0.1 Description: An extension that scans QR codes and displays the encoded information to the user. Initializing extension: Installing packages for server... ... Extension initialized. ``` Let's switch over to the extension directory and add a screen with a shortcut that will be the user interface for our QR Reader. ```ShellSession $ shoutem screen add QRReaderScreen ? Screen name: QRReaderScreen ? Create a shortcut (so that screen can be added through the Builder)? Yes ? Shortcut name: QRReader ? Shortcut title: QRReader ? Shortcut description: A shortcut for QRReaderScreen ... Success ``` ### Installing the Package To make sure the native dependencies are linked, we'll have to make sure our custom postlink script is run by putting it in our `app/package.json` file. `rnpm`'s `postlink` command runs our `app/scripts/run.js` script that we'll explain afterwards. ```json{8-12} #file: app/package.json { "name": "{{ site.example.devName }}.qr-reader-extension", "version": "0.0.1", "description": "An extension that scans QR codes and displays the encoded information to the user.", "dependencies": { "react-native-camera": "1.1.4" }, "rnpm": { "commands": { "postlink": "node node_modules/{{ site.example.devName }}.qr-reader-extension/scripts/run.js" } } } ``` > #### Note > `rnpm` refers to [React Native Package Manager](https://www.npmjs.com/package/rnpm), used for linking native dependancies in React Native apps. The reason we run it from `node_modules` is because extensions are bundled inside `node_modules` and since our postlink script is inside the extension, it should be run from within `node_modules`. Create a `scripts` directory and make the postlink script in it: `app/scripts/run.js` ```ShellSession $ touch app/scripts/run.js ``` Using the `react-native-link` helper method from the Shoutem platform's `@shoutem/build-tools`, we can easily link the package. ```javascript{1-21} #file: app/scripts/run.js const { reactNativeLink } = require('@shoutem/build-tools'); reactNativeLink('react-native-camera'); ``` ### Using the Package Our extension will now have access to everything `react-native-camera` has to offer. For this example we'll edit `app/screens/QRReaderScreen.js` so that it displays an [Alert](https://facebook.github.io/react-native/docs/alert.html) when the camera reads a QR code and the alert message will contain the QR code data. ```javascript{2-4,6-10,12-21,25-28} #file: app/screens/QRReaderScreen.js import React, { Component } from 'react'; import { Alert } from 'react-native'; import Camera from 'react-native-camera'; import _ from 'lodash'; export default class QRReaderScreen extends Component { constructor(props) { super(props); this.onBarCodeRead = this.onBarCodeRead.bind(this); } // when camera recognizes a QR code, it will store it's content in 'code' // and then display an alert with the 'code' contents, onBarCodeRead(code) { Alert.alert( 'QR Code Detected', code.data, [ { text: 'OK, read it.', onPress: () => console.log('User saw QR Code contents.') }, ], { cancelable: false }, ); } render() { return ( ); } } ``` After making these changes, we can push the extension to Shoutem and install it in a new app: ```ShellSession $ shoutem push Uploading `QR Reader` extension to Shoutem... Success! $ shoutem install --new QRReader Extension installed See it in browser: `{{ site.shoutem.builderURL }}/app/{{ site.example.appId }}` ``` Opening the QRReader app in the Builder will show us an app with no Screens, but since we just installed our QR Reader Extension onto the app, we can just add the QRReader screen to it by clicking on the + button next to Screens, going to the Custom category and selecting the QR Reader Extension. This specific native dependency that we're using (`react-native-camera`) is already linked in the Builder preview binary, so we will be able to preview the there. Once it finishes building and loading our QRReader app, scan a QR code, like [this one](https://upload.wikimedia.org/wikipedia/commons/d/d0/QR_code_for_mobile_English_Wikipedia.svg), which should display the URL to WikiPedia's English mobile main page. With any other native dependency, previewing the app through the Builder won't be possible, because of its predefined binary, so instead we have to [preview it locally](http://shoutem.github.io/docs/extensions/tutorials/setting-local-environment) using `react-native run-ios` or `react-native run-android`. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-06-UsingNativeModules.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/using-native-api title: Using native API section: Tutorials --- # Using native API This tutorial shows how to easily gain access to native (iOS and Android) APIs in your Shoutem extension. Most of the time when creating extensions, you'll only use React Native writing in JavaScript. However, you can access any API of the underlying platform or a 3rd party native module. As an example, we'll show you how we've built a Shopify extension using their [Mobile Buy SDK](https://help.shopify.com/api/sdks/mobile-buy-sdk) for iOS and Android. Although this is an advanced use case, as you follow the tutorial, you'll notice just how easy it was. > #### Note > Before we start, you might want to read the excellent official guides on how to write native modules for [iOS](https://facebook.github.io/react-native/docs/native-modules-ios.html) and [Android](https://facebook.github.io/react-native/docs/native-modules-android.html), since this tutorial relies on these concepts. Make sure you've read [Getting started]({{ site.url }}/docs/extensions/tutorials/getting-started) with Shoutem extensions too. There are 3 cases for accessing native APIs from your extension: 1. **Exposing native methods to JavaScript**: This is the simplest case for which you just need to follow the React Native's official guides mentioned above. 2. **Using a 3rd party SDK (e.g. for Shopify or Firebase)**: This is the case we'll describe in more detail. To use the SDK, you'll need to find an existing React Native bridge that already exposes its methods to JavaScript, or create one on your own. The same principles apply when it comes to integrating the bridge with Shoutem. 3. **Requiring mobile permissions (e.g. push notifications, location, etc.)**: You'll need to write a script that modifies the Shoutem application's project files. We're working on a guide on how to do this meanwhile improving the process. ## Creating a Shopify extension [Shopify](https://www.shopify.com/) lets you create and manage an online store. Merchants all over the world use it to grow their business, and many of them want to have their stores accessible from mobile apps. [Shopify's Mobile Buy SDK](https://help.shopify.com/api/sdks/mobile-buy-sdk) for iOS and Android helps developers integrate a store into an app. Shoutem's Shopify extension lets customers browse and buy products through a mobile app built with Shoutem. All that a store owner needs to do is enter his API key in [extension settings]({{ site.url }}/docs/extensions/reference/settings-types). The result of this tutorial is a Shopify store within an app built with Shoutem: ![Shopify extension]({{ site.url }}/img/tutorials/native-modules/shop-in-app.png "Shopify extension"){:.docs-component-image} We won't go into the detail of managing the store's logic. ## Creating a React Native bridge for Shopify's SDK To use Shopify's **_Mobile Buy SDK_** in our JavaScript code, we need to create a React Native bridge. There wasn't one available so we built and open sourced [our own](https://github.com/shoutem/react-native-shopify). When needing to create React Native bridge, you have two options: 1. Embedding the bridge in your extension 2. Creating the bridge as a separate module and referring to it as a dependency from the extension Latter is recommended as it keeps your extension focused on consuming the SDK's API, separates responsibilities and lets other developers use your bridge in their projects. > #### Note > Creating a bridge is time-consuming and error-prone process, but there is a great [tool](https://github.com/frostney/react-native-create-library) that helps you use JavaScript with native code. [Use CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html) the dependency management system for iOS, to install ***Mobile Buy SDK*** for iOS. iOS application will install the native dependency **_Pod_** in their `ios/Pods` folder. To have your bridge see this native code, set its **_Header Search Paths_** in **_Build Settings_** of your bridge's `.xcodeproj`. To reference the application's `Pods` folder from the bridge's `ios` folder, reference the current directory with the `$SRCROOT` variable in XCode, and then refer to the application's `Pods` folder. The result should look like this: ``` $(SRCROOT)/../../../ios/Pods ``` Set this path for both the **_Debug_** and **_Release_** builds, and mark it as recursive. The following picture shows how this setting looks like for the Shopify bridge when viewed in XCode: ![Xcode and Header Search Paths]({{ site.url }}/img/tutorials/native-modules/header-search-paths.png "Xcode and Header search paths"){:.docs-component-image} ## Including the bridge in our Shopify extension There are 3 steps you need to take before using the bridge in your extension. First, add it as a dependency to your extension's `package.json`, in the `app` folder. Then, [link all the bridge's native code](https://facebook.github.io/react-native/docs/linking-libraries-ios.html) to the end `package.json`. With Shoutem, you do this by using an `rnpm postlink` command in `package.json`. Invoke the `react-native link` command for every native dependency. Shoutem's build process will execute it after it installs all dependencies for all extensions. This is how our Shopify extension's `package.json` looks like after these two steps: ```JSON{5-12} #file: app/package.json { "name": "shoutem.shopify", "version": "0.0.1", "description": "Shopify extension", "dependencies": { "react-native-shopify": "0.0.1-alpha.0" }, "rnpm": { "commands": { "postlink": "react-native link react-native-shopify" } } } ``` You can only execute a single command with `rnpm postlink`. For linking more than one native library, use an execution script, as shown below: ```JSON #file: app/package.json "postlink": "node node_modules/shoutem.shopify/scripts/run.js" ``` > #### Note > Shoutem installs extensions as node modules. This is why you need to prepend `node_modules/{extension name}` to the script path. The Shopify extension uses a React Native library `react-native-search-bar` that wraps the iOS native search bar. Since there are two libraries we need to link, we wrote the following `run` script, which you can change and reuse in your extension: ```JavaScript const exec = require('child_process').execSync; const dependenciesToLink = ['react-native-shopify', 'react-native-search-bar']; const command = 'react-native link'; dependenciesToLink.forEach((dependency) => { exec(`${command} ${dependency}`); }); ``` > #### Note > We used Node's synchronous API because the asynchronous version causes the build to hang forever in some situations. The last step is to include the SDKs from Shopify, which do the heavy lifting on the native side. ## Referencing a 3rd party SDK The best practice when creating wrappers around native libraries is to let the application itself manage the dependencies. Shoutem extension is a module within a larger Shoutem application which is responsible for dependency management. On Android, things are straightforward because you specify native dependencies in `build.gradle` and install them as part of the build process. Native modules for iOS need an extra step but Shoutem makes this easy. Shoutem treats an extension's iOS aspect as a **_Pod_** and lets it specify a `.podspec` file where it can declare its dependencies. At a build time, Shoutem application dynamically includes all native iOS dependencies declared by its extensions in one `Podfile`. The example below shows how the Shopify extension declares its **_Mobile Buy SDK_** dependency: ```JSON #file: ShoutemShopify.podspec Pod::Spec.new do |s| s.name = "ShoutemShopify" s.version = "1.0" s.summary = "A Shopify extension for Shoutem." s.description = "The Shopify extension enables store owners to make money by selling products in Shoutem applications." s.homepage = "http://www.shoutem.com" s.platform = :ios s.license = { :type => "MIT" } s.author = "Shoutem" s.source = { :path => "" } s.exclude_files = "Classes/Exclude" # s.public_header_files = "Classes/**/*.h" s.dependency 'Mobile-Buy-SDK', '2.2.0' end ``` This is how will the Shoutem application `Podfile` look like after the build process: ```JSON #file: Podfile target 'ShoutemApp' do # Uncomment this line if you're using Swift or would like to use dynamic frameworks use_frameworks! pod 'ShoutemShopify', :path => '../node_modules/shoutem.shopify/ShoutemShopify.podspec' target 'ShoutemAppTests' do inherit! :search_paths # Pods for testing end end ``` After you build the application, a native dependency will be available to any bridge that wraps it and thus to JavaScript code. ## Calling the SDK's methods from JavaScript Once you have your bridge and its native dependencies installed, you can use it as any other JavaScript module. Let's use Shopify's SDK to fetch shop information and use it in our extension: ```JavaScript import { getExtensionSettings } from 'shoutem.application'; import Shopify from 'react-native-shopify'; import { shopLoaded, shopErrorLoading, } from './redux/actionCreators'; import { ext } from './const'; export function appDidMount(app) { const store = app.getStore(); const { dispatch } = store; const state = store.getState(); const { store: shopifyStore, apiKey } = getExtensionSettings(state, ext()); Shopify.initialize(shopifyStore, apiKey); return Shopify.getShop() .then(shop) => { dispatch(shopLoaded(collections, shop, tags)); }) .catch((error) => { console.log(error); dispatch(shopErrorLoading()); }); } ``` ## Requiring user permissions and other advanced use cases As mentioned in the introduction, sometimes you'll need to support advanced use cases and change core project files. This can mean changing the application's `xcode.project` or `build.gradle` files, `AndroidManifest.xml`, or even `AppDelegate` and `MainActivity`. With the help of Node's filesystem modules and 3rd party libraries such as [xcode](https://www.npmjs.com/package/xcode), you can do just about anything. We're working on a more developer friendly system for common use cases, which we'll describe in detail. For now, with some Node scripting, you can meet any goal in your custom extension, although with some extra effort. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-07-ModifyingNativeProject.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/modifying-native-project title: Modifying Root App section: Tutorials --- # Modifying Native Project When making an extension with native dependencies, it's often necessary to change properties outside of the extension. One way we address this need using file anchors, which can be used to inject code into files which often need to be modified in order to set up a native dependency. You can also affect the native project by creating an `android` and `ios` directory in the extension's app segment, you can read more about that in the [**App segment**]({{ site.url }}/docs/extensions/tutorials/modifying-native-project#app-segment) section. ## Injecting This section will elaborate on how to inject code into the native parts of the root app without leaving the extension. #### Available anchors The list of available anchors can be seen in the [platform's helper scripts](https://github.com/shoutem/platform/blob/master/scripts/helpers/const.js). The `ANCHORS` object is structured as follows: ```JavaScript #file: AppName/scripts/helpers/const.js const ANCHORS = { IOS: { PODFILE: { EXTENSION_DEPENDENCIES: '## ', EXTENSION_POSTINSTALL_TARGETS: '## ', }, ... }, ANDROID: { MAIN_ACTIVITY: { IMPORT: '//NativeModuleInjectionMark-mainActivity-import', ON_CREATE: '//NativeModuleInjectionMark-mainActivity-onCreate', ON_ACTIVITY_RESULT: '//NativeModuleInjectionMark-mainActivity-onActivityResult', ON_ACTIVITY_RESULT_END: '//NativeModuleInjectionMark-mainActivity-onActivityResult-end', }, ... }, }; module.exports = { ANCHORS, }; ``` #### 'build' directory structuring ``` build/ ├ const.js ├ inject{ExtensionName}{MobileOSName}.js └ index.js ``` We can look at the `shoutem.code-push` [extension](https://github.com/shoutem/extensions/tree/master/shoutem.code-push/app/build) as an example. `const.js` should contain all the plaintext modifications you need to inject, in separate variables, exported as a single object named after your extension, with a structure resembling that of the `ANCHORS` object from `@shoutem/build-tools` seen above in the **Available anchors** section and the platform's `scripts/helpers/const.js` [file](https://github.com/shoutem/platform/blob/master/scripts/helpers/const.js). ```JavaScript #file: shoutem.code-push/app/build/const.js const codepush = { ios: { appDelegate: { import: '#import ', oldBundle: appDelegateOldBundle, newBundle: appDelegateNewBundle, }, ... }, android: { app: { import: 'import com.microsoft.codepush.react.CodePush;', ... }, }, }; ``` `inject{ExtensionName}` should contain the functions which inject the code, one for Android and one for iOS, named `injectCodePushAndroid` and `injectCodePushIos`, respectively. ```JavaScript #file: shoutem.code-push/app/build/injectCodePush.js const { ANCHORS, ... } = require('@shoutem/build-tools'); const { codepush } = require('./const'); function injectCodePushAndroid() { // app/build.gradle mods const gradleAppPath = getAppGradlePath({ cwd: projectPath }); inject( gradleAppPath, ANCHORS.ANDROID.GRADLE.APP.REACT_GRADLE, codepush.android.app.gradle.codepushGradle, ); ... } function injectCodePushIos() { const appDelegate = getAppDelegatePath({ cwd: projectPath }); inject(appDelegate, ANCHORS.IOS.APP_DELEGATE.IMPORT, codepush.ios.appDelegate.import); replace(appDelegate, codepush.ios.appDelegate.oldBundle, codepush.ios.appDelegate.newBundle); ... } module.exports = { injectCodePushAndroid, injectCodePushIos, }; ``` `index.js` should contain a single export, a `preBuild` function which will be called in the preBuild step of the `shoutem configure` lifecycle. The preBuild function itself should simply call the functions imported from the `inject{ExtensionName}.js` file. ```JavaScript #file: shoutem.code-push/app/build/index.js const { injectCodePushAndroid, injectCodePushIos } = require('./injectCodePush'); exports.preBuild = function preBuild() { injectCodePushAndroid(); injectCodePushIos(); } ``` #### inject{ExtensionName}{MobileOSName} convention - name your functions `inject{ExtensionName}{MobileOSName}` - start a new block by adding a comment declaring which file is going to be modified - assign that file's path to a new const - use inject() and/or replace() to apply modifications - create another block if more changes are needed The following example follows all of the above convention rules. ```JavScript // app/settings.properties mods const gradlePropertiesPath = getGradlePropertiesPath({ cwd: projectPath }); inject( gradlePropertiesPath, ANCHORS.ANDROID.GRADLE.PROPERTIES, codepush.android.app.gradle.codepushKey, ); // MainApplication.java mods const mainApplicationPath = getMainApplicationPath({ cwd: projectPath }); inject( mainApplicationPath, ANCHORS.ANDROID.MAIN_APPLICATION.IMPORT, codepush.android.app.import, ); inject( mainApplicationPath, ANCHORS.ANDROID.MAIN_APPLICATION.RN_HOST_BODY, codepush.android.app.rnHost, ); inject( mainApplicationPath, ANCHORS.ANDROID.MAIN_APPLICATION.GET_PACKAGES, codepush.android.app.getPackages, ); ``` #### inject() and replace() The `inject()` and `replace()` functions can be used to either inject code at an anchor, or replace content in a specific file. Both functions will check if the code is already injected/replaced before doing so. **inject()** accepts the following arguments: - `filePath:` the path to the file that you need to modify - `anchor:` position in the file specified in the `ANCHORS` object - `contents`: the source code that needs to be injected at anchor position **replace()** accepts the following arguments: - `filePath:` the path to the file that you need to modify - `oldContent:` the source code to search for in the file - `newContent`: the source code that should replace `oldContent` in the file ## App segment As mentioned in the introduction, another tool provided for modifying the root app with an extension is to define new Android modules using an `android` directory in the app segment of your extension. For example, instead of directly modifying the root Android `build.gradle`, you can simply create a `build.gradle` in the `{{ site.example.devName }}.extension-name/app/android` directory which defines a new Android module for the app. You can see an example in `shoutem.places`, [here](https://github.com/shoutem/extensions/tree/master/shoutem.places/app/android). Furthermore, unique parts of the Android module are merged from the extension into the app, such as the `AndroidManifest.xml`, also visible in the above `shoutem.places` example. You can read more about manifest merging [here](https://developer.android.com/studio/build/manifest-merge). ```XML #file: shoutem.places/app/android/src/main/AndroidManifest.xml ``` There is no similar method for iOS, however, we've included `Info.plist` merging, so you can create an `{{ site.example.devName }}.extension-name/app/ios` directory and an `Info.plist` file inside of it, which will get merged into the root `Info.plist` by the platform's `merge-info-plists.js` [script](https://github.com/shoutem/platform/blob/master/scripts/merge-info-plists.js) during the app's configuration. An example of this can be seen in the `shoutem.camera` [extension](https://github.com/shoutem/extensions/blob/master/shoutem.camera/app/ios/Info.plist), which adds permissions to the root `Info.plist`. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-08-ModifiyingExtensions.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/modifying-extensions title: Modifying Extensions section: Tutorials --- # Modifying Extensions Shoutem's mission is simple - **do not solve problems that have already been solved**. Quite often apps that we build need common features you've seen numerous times: push notifications, analytics, ads, authentication, RSS readers, eCommerce integrations and so on. This is what extensions are for - just reuse pre-built features. However, for the app to succeed, it needs to be **unique**. Recreating an RSS reader just to make it unique is redundant work. Sometimes, it's enough to customize your app in the dashboard, customize the theme, or [write a new theme]({{ site.url }}/docs/extensions/tutorials/writing-a-theme), but sometimes we need to modify the currently available extensions. There are two ways to achieve this: - 1) Directly modify the extension code - the resulting new extension will **not** get future updates that the original extension gets - 2) Extend the extension - new extension will get future updates from the original extension Both methods result in a brand new extension. (Yes, even when you _extend_ an extension) ## 1) Directly Modify the Extension Code Since all Shoutem extensions are [open sourced](https://github.com/shoutem/extensions), you can directly take the code of an extension, modify it and _push_ it as your own extension to Shoutem. For the purpose of this demo, create a new blank app and add a **News RSS** screen inside of the navigation. In the **Content** section, enter a link to an RSS feed (e.g. https://www.nasa.gov/rss/dyn/breaking_news.rss). In the **Layout** section, select `List` as the default layout. This is what it should look like (images and text vary with RSS feed):

You do want list layout, but with large images in `Tiles`, such as the ones provided in the [UI toolkit]({{ site.url }}/docs/ui-toolkit/components/tiles). So let's modify News RSS extension directly to get those large images. Download the [News RSS extension](https://github.com/shoutem/extensions/tree/master/shoutem-rss-news) from the repository and navigate to the extension's folder: ```ShellSession $ cd shoutem.rss-news ``` Check the `renderRow` method in `app/screens/ArticlesListScreen.js`: ```javascript #file: app/screens/ArticlesListScreen.js renderRow(article) { return ( ); } ``` It uses the `ListArticleView` component. Customize the `render` method in that component to use `Tiles` and don't forget to import additional components from the `@shoutem/ui`. This is the complete code you should end up with: ```JSX{7-8,24,27-30} #file: app/components/ListArticleView.js import React from 'react'; import moment from 'moment'; import { TouchableOpacity, Caption, ImageBackground, Tile, Title } from '@shoutem/ui'; import { ListArticleView } from 'shoutem.news/components/ListArticleView'; import { getLeadImageUrl } from 'shoutem.rss'; export class ListArticleView extends ArticleView { render() { const { article } = this.props; const dateFormat = moment(article.timeUpdated).isBefore(0) ? null : ({moment(article.timeUpdated).fromNow()}); return ( {article.title} {dateFormat} ); } } ``` And that's it! Save it and push it to Shoutem: ```ShellSession $ shoutem push Uploading `News RSS` extension to Shoutem... Success! ``` Install it on your app either through the [Builder]({{ site.shoutem.builderURL }}) or with: ```ShellSession $ shoutem install Select app to install extension: News App Success! ``` Delete the old News RSS screen from the navigation (click on the `...` in the top right corner and select **Delete**), insert the new `News RSS` screen from the `Custom` category, set up your RSS feed in the **Content** tab and select the `List` layout from the **Layout** tab. This is what you should get (images and text vary with RSS feed):

This is what we wanted! It was quite easy and straightforward. However, doing this means creating a completely separate extension which won't get any updates done by Shoutem on Shoutem's **News RSS** extension. To get those updates from Shoutem, we want to extend the extension instead of changing its code directly. ### Debugging directly edited extensions When directly editing Shoutem extensions you will have to either re-clone the app after uploading and installing the extension into your app or re-naming the extension's directory name locally, otherwise you might experience issues on the Builder. > #### Note > When forking (directly editing) Shoutem's extensions, you should re-clone the app after uploading and installing the extension to keep everything in sync. For example, you decide to modify the News extension and install it to your app, then uninstall `shoutem.news` so you only have your News extension installed, `{{ site.example.devName }}.news`. This will cause issues with `shoutem.rss-news` because it imports components from `shoutem.news` in multiple places: ```javascript #file: shoutem.rss-news/app/ArticlesGridScreen.js import { FeaturedArticleView, GridArticleView } from 'shoutem.news'; ``` In the described situation, the Builder preview will fail, but locally your app will function as intended. This is because your local extension directory for `{{ site.example.devName }}.news` is still called `shoutem.news`, while the Builder only has `{{ site.example.devName }}.news`. In order to catch these sorts of issues locally, you will have to either rename the extension directory or re-clone the app. ```ShellSession $ shoutem clone --dir "Fresh Clone" ``` ## 2) Extend the extension Extending an extension is the preferred way of modifying extensions. ##### When to do it? For bigger modifications, such as changing the data schema being used, it's not possible to modify functionality without directly modifying the extension. Extending extensions is usually done for writing screen layouts, extending components (overwriting contents) and such. ##### How to do it? [Here](https://github.com/shoutem/extension-examples/tree/master/tom.extending-extension) is the open sourced extension as the final result of this chapter. Let's create a new extension: ```ShellSession $ shoutem init news-rss ... Extension initialized! ``` Switch over to the newly made directory: ```ShellSession $ cd {{ site.example.devName }}.news-rss ``` Create a new screen which will extend the List screen from the original Shoutem **News RSS** extension: ```ShellSession $ shoutem screen add ListWithBigPictures ? Screen name: ListWithBigPictures ? Create a shortcut (so that screen can be added through the Builder)? No Success ``` Now we need to say which screen our `ListWithBigPictures` screen extends. Do this in `extension.json` in the `extends` field: ```JSON{9-11} #file: extension.json { "name": "news-rss", "title": "My news RSS", "version": "0.0.1", "description": "", "platform": "1.*.*", "screens": [{ "name": "ListWithBigPictures", "title": "List with big pictures", "image": "./server/assets/large-news-list.png", "extends": "shoutem.rss-news.ArticlesGridScreen" }] } ``` We extended `ArticlesGridScreen` from Shoutem's **News RSS** extension, since it's the [original screen](https://github.com/shoutem/extensions/blob/master/shoutem-rss-news/extension.json#L29). We've also added a `title` and `image` which will both be shown in the layout selector. Download the image [here]({{ site.url }}/static/modifying-extension/assets.zip), extract it and paste the `assets` folder into the extension's `server` folder. > #### Note > If you can't remember the fields in `extension.json`, all of them are documented in the [reference]({{ site.url }}/docs/extensions/reference/extension). Let's implement our `ListWithBigPictures` screen. In `extension.json`, we are extending `ArticlesGridScreen`, but that's only for the layout selector. In the implementation, we actually want to extend the `ArticlesListScreen`. Why? Because `ArticlesListScreen` renders the `ListArticleView` component from Shoutem **News**. When overriding that method, we could immediately implement the `ListArticleView` functionality inside. However, since we want to get Shoutem's updates for the `ListArticleView` component too, we'll create a new component extending that one and use it in the overridden `renderRow` function. Create the `components` folder and a component file inside: ```ShellSession $ mkdir app/components $ touch app/components/BigPictureView.js ``` Implement the `BigPictureView` component: ```javascript #file: app/components/BigPictureView.js import React from 'react'; import moment from 'moment'; import { TouchableOpacity, Caption, ImageBackground, Tile, Title, } from '@shoutem/ui'; import { ListArticleView } from 'shoutem.news/components/ListArticleView'; import { getLeadImageUrl } from 'shoutem.rss'; const resolveDateCaption = (article) => ( moment(article.timeUpdated).isBefore(0) ? null : {moment(article.timeUpdated).fromNow()} ); export class BigPictureView extends ListArticleView { render() { const { article } = this.props; const dateFormat = resolveDateCaption(article); return ( {article.title} {dateFormat} ); } } ``` We're importing `ListArticleView` from the `shoutem.news` extension, since the `app` folder is bundled into the overall app. We've implemented the new `render` function and deleted everything we didn't need to override from the `ListArticleView` component. Now, let's override the `renderRow` method in the `ListWithBigPictures` screen: ```javascript #file: app/screens/ListWithBigPictures.js import React from 'react'; import { connect } from 'react-redux'; import { connectStyle } from '@shoutem/theme'; import { ArticlesListScreen, mapStateToProps, mapDispatchToProps, } from 'shoutem.rss-news/screens/ArticlesListScreen'; import { BigPictureView } from '../components/BigPictureView'; import { ext } from '../const'; export class ListWithBigPictures extends ArticlesListScreen { renderRow(article) { return ( ); } } // since the original screen is connected to redux state, // we have to reconnect it in our screen too export default connect(mapStateToProps, mapDispatchToProps)( connectStyle(ext('ListWithBigPictures'))(ListWithBigPictures), ); ``` And we're done! Push the extension to Shoutem. ```ShellSession $ shoutem push Uploading `My news RSS` extension to Shoutem... Success! ``` Install it to a new blank app: ```ShellSession shoutem install --new "News App" ... ``` Open your new app in the Builder. Now, add a screen from the **Shoutem** News RSS extension. Why that one? Because in our new extension, we only extended a screen and didn't create a new shortcut, so we couldn't add it's screen even if we wanted to! However, since both **News RSS** and **My news RSS** are installed in the app, the layout selector will recognize the new `List with big pictures` layout from **My news RSS** and show it in the layout list of Shoutem's **News RSS**. Add an RSS feed in the **Content** tab, select the new layout in the **Layout** tab and run the app. This is what you should get (images and text vary with RSS feed):

This way, we only extended Shoutem's **News RSS** extension and our extension will automatically get the all updates from Shoutem. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-09-FAQ.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/faq title: FAQ section: Frequently Asked Questions --- # Frequently Asked Questions This document lists frequently asked questions when developing Shoutem extensions. If you don't find an answer here, try to find it on [Stackoverflow](http://stackoverflow.com/questions/tagged/shoutem?sort=newest) or feel free to ask it there using `shoutem` tag. **Questions:** - [What's the difference between React Native and Shoutem?](#whats-the-difference-between-react-native-and-shoutem) - [Is Shoutem free?](#is-shoutem-free) - [How can I get the complete app code?](#how-can-i-get-the-complete-app-code) - [How can I make my existing React Native app work with Shoutem?](#how-can-i-make-my-existing-react-native-app-work-with-shoutem) - [Can I publish app without using Shoutem?](#can-i-publish-app-without-using-shoutem) - [Which editor should I use?](#which-editor-should-i-use) - [Can I use native modules in apps?](#can-i-use-native-modules-in-apps) - [What do I need to run the app locally?](#what-do-i-need-to-run-the-app-locally) ## What's the difference between React Native and Shoutem? Shoutem is a simple modular architecture for building React Native apps. It allows you to use everything from React Native, including native packages, without any special setup. The modular architecture allows developers to create reusable packages, called `extensions`, which can be easily installed into multiple apps. There are a number of benefits of using Shoutem on top of React Native: - Shoutem [open sourced a lot of extensions](https://github.com/shoutem/extensions) which you can use, modify and install into your apps - Update apps instantly without resubmitting them to the stores - Shoutem provides a customizable web interface which your clients can use to manage the app - A lot of non-technical users are using Shoutem Builder, to whom you can sell your extensions - Shoutem takes care of all the breaking changes in React Native ## Is Shoutem free? Yes! Shoutem is completely free and [open sourced](https://github.com/shoutem) for developers. Use unlimited number of extensions in your app. There are some premium features like automated publishing to both stores. See the pricing details [here]({{ site.shoutem.pricing }}). ## How can I get the complete app code? There's no lock-in. Use `shoutem clone` command to download all the code locally, or feel free to fork extension source code from the [GitHub repository](https://github.com/shoutem/extensions). The `shoutem clone` command will download the code for all the extensions installed into your app. ## How can I make my existing React Native app work with Shoutem? Shoutem app is a plain React Native app. We firmly believe that architecture that Shoutem introduces is the right way to do React Native apps. You should think of your app as a collection of multiple functionalities. Divide those functionalities into smaller modules, which will eventually become reusable extensions. You can start by wrapping your entire app into one large extension. Using Shoutem, there's no restriction in how you can use React Native. As the time goes by, self-contained functionalities will become apparent. At that point, you should start dividing them into multiple extensions. ## Can I publish app without using Shoutem? Of course you can! With `shoutem clone` you are getting the whole React Native project, the whole app which you can build and publish manually. ## Which editor should I use? You can use editor of your choice. We recommend [Visual Studio Code](https://code.visualstudio.com/). ## Can I use native modules in apps? Yes, you can use them the [same](https://facebook.github.io/react-native/docs/native-modules-ios.html) [way](https://facebook.github.io/react-native/docs/native-modules-android.html) you've used them in React Native. ## What do I need to run the app locally? Since Shoutem apps are React Native apps, you need to [set up your local environment]({{ site.url }}/docs/extensions/tutorials/setting-local-environment) the same way you'd do it for a React Native app. After that, use the Shoutem CLI tool to get a local copy of your app using the `shoutem clone` command. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-10-DebugSettingsPages.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/debug-settings-pages title: Debugging and local development of settings pages section: Tutorials --- # Debugging and Local Development
This short tutorial covers how to improve your settings page development flow by utilizing the `Debug` feature in the Builder. It'll explain how to see code changes without having to `push` every code change and how to use dev tools to debug the settings pages through the Builder. > #### Note > HTML settings pages currently cannot use the `Debug` feature in the Builder. ## Local development When you start developing settings pages, you'll quickly realize that it's inpractical to shoutem publish every change to Shoutem just to see the updated settings page. To avoid this and enjoy a much more efficient development flow, you can simply do the following: 1. Push, and install your extension in Shoutem Builder application (Only once) 2. Locate to the `extName/server` directory and execute `npm run dev` to run the webpack dev server (Until you kill server there is no need to run it again) 3. In your browser go to Shoutem Builder and open settings page you are developing 4. Click on the `Debug` button Once you click Debug, the settings page will reload from `https://localhost:4790` and there is possibility that your browser will define `localhost` as insecure. We've prepared quick and simple tutorials on how to allow your browser to load the settings page from `localhost`. **Chrome**
Chrome will show an error in the actual settings page iframe, stating that the website is moved to a new address or removed. Open that link in a new tab. Chrome will warn you that it is not a secure site, click on `Advanced` and choose to navigate to the site. After that, you can close the tab and your settings page will now work in the Builder. **Safari**
Safari will prompt you with an alert that Safari can't verify the identity of the website "localhost". Simply click `Continue` and Safari will then take you to the localhost site. After that, navigate back to the Builder using the back button and again click on the `Debug` button your settings page will be started and you can reload it after making changes to your local files. **Firefox**
Firefox will show an error in the settings page iframe, stating that the connection isn't secure. To resolve this, you'll have to pen this URL: chrome://pippki/content/exceptionDialog.xul. In the `Location` field enter `localhost:4790`, check the `Permanently store this exception` checkbox and then click the `Confirm Security Exception` button. You can now successfully debug the settings page. Since we use React and Webpack dev server we have the hot module reload feature enabled, meaning that every time you save changes to your code in your editor, the server will re-bundle it and reload it in the browser, so basically you have a live preview while you develop your settings page. There are some exceptions when it cannot work, like adding new npm module or file, but in that case just re-run dev server with and reload the Builder in your browser. ## Debugging settings pages Debugging of settings pages should be done using the Debug feature explained in the section above. Once you enter debug mode, open the developer console (in Chrome keyboard shortcut is F12) and use Console, Sources, Elements and Network tab to debug your code. You can put breakpoint on code lines of your `/src` folder files by opening the `Source` tab and use the `Ctrl + P` keyboard shortcut to open the search bar. | | | | Normal | | | Debug | |----------------|:-:|:-:|:------:|:-:|:-:|:-----:| | Transpiled ES5 | | | Yes | | | No | | Bundled | | | Yes | | | No | | Breakpoints | | | No | | | Yes | | Stack Trace | | | No | | | Yes |
Settings page debugging outside of debug mode is problematic, because the code is bundled in `extension.{hash}.js` and it's dependencies are in `vendor.{hash}.js` which the iframe loads. Webpack2 bundles and transpiles your code and we don't have to pass source maps. You can't easily put breakpoints or investigate the stack trace in such code. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-11-WritingHTMLSettingsPages.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/writing-html-settings-page title: Writing HTML settings pages section: Tutorials --- # Writing an HTML settings page In this tutorial, we'll show you how to create HTML Settings pages for both shortcut settings and extension settings pages. HTML settings pages are useful to developers who have an existing dashboard they want to implement into the Builder without having to re-write it into React. For example if they have an existing AngularJS based dashboard. ## Shortcut settings pages First, let's make an extension to work with. We'll make a simple `Hello World!` example so we can easily cover the basic concepts. ```ShellSession $ shoutem init html-hello-world Enter information about your extension. Press `return` to accept (default) values. ? Title HTML Hello World ? Version 0.0.1 ? Description Learning HTML settings pages. ... Extension initialized. ``` We need to add a screen with a shortcut, so we have a shortcut to add settings pages to. Locate to the extension folder: ```ShellSession cd {{ site.example.devName }}.html-hello-world ``` And add the screen: ```ShellSession $ shoutem screen add Hello ? Screen name: Hello ? Create a shortcut (so that screen can be added through the Builder)? Yes ? Shortcut name: Hello ? Shortcut title: Hello ? Shortcut description: A shortcut for Hello ... Success ``` Now let's create the actual settings page: ```ShellSession $ shoutem page add HelloWorldShortcutPage ? Page type: react ? Page name: HelloWorldShortcutPage ? Page title: Hello World Shortcut Page ? This settings page controls settings for: an existing screen ? Select existing screen: Hello ... React settings page added to pages/hello-world-shortcut-page ``` `HelloWorldShortcutPage` was also added to `extension.json` as an `adminPage` of the `Hello` shortcut and as one of the `pages` of the extension: ```JSON #file: extension.json "shortcuts": [ { "name": "Hello", "title": "Hello", "description": "A shortcut for Hello", "screen": "@.Hello", "adminPages": [ { "page": "@.HelloWorldShortcutPage", "title": "Hello World Shortcut Page" } ] } ], "pages": [ { "name": "HelloWorldShortcutPage", "path": "server/pages/hello-world-shortcut-page/index.html", "type": "html" } ] ``` This is the new structure of `server` folder: ``` server/ ├ pages/ | └ hello-world-shortcut-page | ├ index.html | ├ index.js | └ style.css ├ src/ └ package.json ``` It contains `src` and `pages` folders. The `src` is added by default when you initialize an extension, it doesn't affect HTML settings pages. The `pages` folder hosts all your HTML settings pages. Since we made a `shortcut` settings page, it includes a simple example settings pages that allows the app owner to enter the name of an person to be greeted in the app in the `index.html` file. ```HTML #file: server/pages/hello-world-shortcut-page/index.html Title

Enter company name

``` It uses: - CSS - Web UI - Shoutem style defined on top of [Bootstrap v3](http://getbootstrap.com/) - **style.css** - a place where you write your own CSS - JavaScript - [jQuery](https://jquery.com/) - [Bootstrap v3](http://getbootstrap.com/) - iframeResizer - for managing the size of the iFrame in which settings page is set - api-sdk - exposing `shoutem` variable globally for easier access of Shoutem API - extension-sandbox - enabling the communication between your page and the builder - **index.js** - a place where you write your own JS code with lifecycle methods prepared `index.js` comes with pre-made lifecycle methods for your settings page: ```JS #file: server/pages/hello-world-shortcut-page/index.js // listen for Shoutem initialization complete document.addEventListener('shoutemready', onShoutemReady, false); // handler for Shoutem initialization finished function onShoutemReady(event) { // config object containing builder extension configuration, can be accessed via event // or by shoutem.sandbox.config const config = event.detail.config; // Waiting for DOM to be ready to initialize shoutem.api and call app start function $(document).ready(function() { shoutem.api.init(config.context); onPageReady(config); }); }; // Put your settings page logic here, executes when sandbox and DOM are initalized function onPageReady(config) { function errorHandler(err) { console.log('Something went wrong:', err); } function handleSubmit(e) { // prevent default action and bubbling e.preventDefault(); e.stopPropagation(); const greeting = $('#greetingName').val(); // updates current shortcut settings by patching with current settings shoutem.api.shortcuts.updateSettings({ greeting }) .catch(errorHandler); return false; } function initForm(settings) { if(!settings) { return; } $('#greetingName').val(settings.greeting); } $('button[type="submit"]').click(handleSubmit); // shoutem.api knows current shortcut and returns promise with fetched settings shoutem.api.shortcuts.getSettings() .then(initForm, errorHandler); } ``` Sandbox is a container where your settings page is loaded. Once it's ready, `onShoutemReady` is triggered. By default, logic for extracting the configuration for your extension and initializing jQuery is inside that function. Write your own code after `onShoutemReady`. Finally, we have a simple CSS file `style.css` where you can store your custom CSS: ```CSS #file: server/pages/hello-world-shortcut-page/style.css .footer { margin-top: 15px; } ``` This page is now created and referenced in the `Hello` shortcut in `extension.json`. Let's add our `greeting` setting to it and give it a default value: ```json{12-14} #file: extension.json "shortcuts": [ { "name": "Hello", "title": "Hello", "description": "A shortcut for Hello", "screen": "@.Hello", "adminPages": [ { "page": "@.HelloWorldShortcutPage", "title": "Hello World Shortcut Page" } ], "settings": { "greeting": "Tom" } } ] ``` When the app owner clicks `Save`, we want to save the settings entered into the `` field. This is doneusing the two functions in `server/index.js`: `handleSubmit` and `initForm`. For simplified communication with the Shoutem API, such as updating and getting shortcut settings, use `api-sdk`. It puts the `shoutem` object into the global environment. Both these functions (and an `errorHandler`) are added into the `onPageReady` function when you generate a shortcut settings page. ```JS #file: server/pages/hello-world-shortcut-page/index.js function onPageReady(config) { function errorHandler(err) { console.log('Something went wrong:', err); } function handleSubmit(e) { // prevent default action and bubbling e.preventDefault(); e.stopPropagation(); const greeting = $('#greetingName').val(); // updates current shortcut settings by patching with current settings shoutem.api.shortcuts.updateSettings({ greeting }) .catch(errorHandler); return false; } function initForm(settings) { if(!settings) { return; } $('#greetingName').val(settings.greeting); } $('button[type="submit"]').click(handleSubmit); // shoutem.api knows current shortcut and returns promise with fetched settings shoutem.api.shortcuts.getSettings() .then(initForm, errorHandler); } ``` ### Accessing the shortcut settings in the application The Shoutem CLI implemented the shortcut settings page into our pre-existing shortcut, all that is left to do is to access the settings in the `Hello` screen. Update the screen file: ```JS #file: app/screens/Hello.js export default class Hello extends Component { render() { const { shortcut } = this.props; const { greeting } = shortcut.settings; return ( Hello {greeting}! ); } } ``` Now let's publish and install the extension. ```ShellSession $ shoutem push Uploading HTML Hello World extension to Shoutem... Success! ``` ```ShellSession $ shoutem install --new "HTML Hello World" Extension installed See it in browser: {{ site.shoutem.builderURL }}/{{ site.example.appId }} ``` Our default setting applies and the app owner has an input form to change the `greeting` value.

## Extension settings pages Extension settings pages provide you with settings that you can pass to every part of the extension, so in our simple use case, we'll suppose the extension settings page lets the app owner determine which company the person being greeted is working for, as you can see from the input form. The key difference between extension and shortcut settings pages is where they're defined in the `extension.json`. They're defined on the same level as `shortcuts` and `pages`. Let's create an extension settings page. ```ShellSession $ shoutem page add ? Page type: html ? Page name: HelloWorldExtensionPage ? Page title: Hello World Extension Page ? This settings page controls setting sfor: the 'html-hello-world' extension ... React settings page added to pages/hello-world-extension-page ``` The CLI added `HelloWorldExtensionPage` to the root level of `extension.json`, but let's add a default value. ```json #file: extension.json "settingsPages": [ { "page": "@.HelloWorldExtensionPage", "title": "Hello World Extension Page" } ], "settings": { "company": "Shoutem" } ``` The template page generated is pretty much identical to the one generated for `HelloWorldShortcutPage`, except the `shoutem.api` references `extensions` instead of `shortcuts`, and `company` instead of `greeting`. ```JavaScript{6,9,18} #file: server/pages/hello-world-extension-page/index.js function handleSubmit(e) { // prevent default action and bubbling e.preventDefault(); e.stopPropagation(); const company = $('#companyName').val(); // updates extension settings by patching with current settings shoutem.api.extensions.updateSettings({ company }) .catch(errorHandler); return false; } ... // shoutem.api returns promise with fetched settings shoutem.api.extensions.getSettings() .then(initForm, errorHandler); ``` ```HTML{2,4-5} #file: server/pages/hello-world-extension-page/index.html

Enter company name

``` Let's retrieve that `company` value from the redux store and use it in our `Hello` screen. ```JavaScript{11-15,19,25,42-53} #file: app/screens/Hello.js import React, { Component } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { connect } from 'react-redux'; import { connectStyle } from '@shoutem/theme'; import _ from 'lodash'; import { getExtensionSettings } from 'shoutem.application'; import { ext } from '../const'; export class Greeting extends Component { render() { const { shortcut, company } = this.props; const { greeting } = shortcut.settings; return ( Hello {greeting}! You work for {company}. ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, text: { fontSize: 20, }, }); export const mapStateToProps = (state) => { const extensionSettings = getExtensionSettings(state, ext()); const company = _.get(extensionSettings, 'company'); return { company }; }; export default connect(mapStateToProps, undefined)( connectStyle(ext('Greeting'))(Greeting), ); ``` Finally, let's push the new version of our `html-hello-world` extension that we've made to Shoutem and see our extension settings page in action. ```ShellSession $ shoutem push Uploading HTML Hello World extension to Shoutem... Success! ```

So what's the purpose of extension settings pages as opposed to shortcut? Well, in our simple example, we made an extension where the app owner can define which company he's addressing and then make each Screen he adds on the Builder greet a unique employee. Each screen added will address the company defined in the Extension settings pages, while the app owner can choose which employee each screen greets. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-12-SettingsPageIntro.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/settings-pages-introduction title: Settings Pages section: Tutorials --- # Settings pages Settings pages are used to allow the app owner to control the behavior of an extension. You define all of your settings pages in the `pages` root field in `extension.json`. However, it's important to differentiate different types of settings pages. You can define three different types of settings pages.
### Extension settings pages They're defined as `settingsPages` in the root of the `extension.json` file and are defined as an array of pages used for adjusting settings on the level of the entire extension, meaning they're accessible throughout the entire extension. An example of this type of settings page was mentioned above, the User Authentication extension: ```JSON #file: shoutem.auth/extension.json "settingsPages": [{ "page": "@.SettingsPage", "title": "Settings" }, { "page": "@.ProvidersPage", "title": "Authentication providers" }], "pages": [{ "name": "SettingsPage", "type": "native-component", "path": "dummy.js" }, { "name": "ProvidersPage", "type": "native-component", "path": "dummy.js" }], ```

### Shortcut settings pages They're defined as `adminPages` in the `shortcuts` field of the `extension.json` file as an array of pages used for adjusting settings on the level of a specific shortcut, meaning they're accessible only to those shortcuts and not on the level of an entire extension. An example of this type of settings page can be found in the News RSS extension: ```JSON{6-11} #file: shoutem.rss-news/extension.json "shortcuts": [{ "name": "news-shortcut", "title": "News RSS", "icon": "theme://news.png", "screen": "@.ArticlesGridScreen", "adminPages": [{ "page": "shoutem.rss.RssPage", "title": "Content", "parameters": { "schema": "shoutem.proxy.news" } }, { "page":"shoutem.layouts.LayoutPage", "title": "Layout" }] }], ```

### Screen settings page It's defined as `settingsPage` in the `screens`field as a single page for adjusting a specific layout's settings. This is what you see in the `shoutem.navigation` (Main Navigation) extension. ```JSON{} #file: shoutem.navigation/ { "name": "Drawer", "title": "Drawer", "image": "./server/assets/screens/drawer.png", "extends": "@.TabBar", "settingsPage": { "page": "@.Drawer" }, "settings": { "startingScreen": null, "showIcon": true, "showText": true } } ```

Now that we understand the differences between settings pages, let's see [how to make them]({{ site.url }}/docs/extensions/tutorials/writing-react-settings-page). ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-13-UsingLocalization.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/using-localization title: Localization section: Tutorials --- # Localization i18n stands for internationalization (i, 18 letters, n). Shoutem's localization is based on [i18n-js by fnando](https://github.com/fnando/i18n-js). Each extension has it's own set of translations located in the `app` segment in the `translations` directory. These extension-level translations are fallbacks for when a new extension is made and an existing language file doesn't have it's strings. Language files are uploaded by the app owner using the `shoutem.i18n` settings page.

## Translating your App To use your translation, you can download and unzip [this file]({{ site.url }}/static/localization/en.json.zip) and replace all the English strings with anything you like. Either a different language or alternative English strings. Once you've extracted the `en.json` file, rename it to the language you want to translate your app to, e.g. `de.json`, for German. Then translate all strings from English to German. For example: ```JSON #file: en.json "navBarMapViewButton": "Map" ``` Is translated to German: ```JSON #file: de.json "navBarMapViewButton": "Karte" ``` We have short examples of how to translate more complex strings with [pluralization]({{ site.url }}/docs/extensions/tutorials/using-localization#pluralized-strings) and [variables]({{ site.url }}//docs/extensions/tutorials/using-localization#variables-in-strings). After translating the strings to your chosen language, you can validate the JSON for any syntax errors using a free and easy to use tool like [JSONLint](https://jsonlint.com/). ## Implementing i18n in your Custom Extension Think back to [Getting Started]({{ site.}}/docs/extensions/tutorials/getting-started). We made a simple extension with a line of text in one screen: ```JavaScript{5} #file: tom.restaurants/app/screens/List.js export default class List extends Component { render() { return ( Let's eat! ); } } ``` To implement translations to that we'd have to import `I18n`: ```JavaScript{11} #file: tom.restaurants/app/screens/List.js import React, { Component } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { I18n } from 'shoutem.i18n'; ``` Then use the imported `I18n`: ```JavaScript{2} #file: tom.restaurants/app/screens/List.js I18n.t(ext('letsEatMessage')) ``` Now we need to implement the fallback by creating a `translations` directory in the `app` segment: ```ShellSession $ mkdir app/translations && touch app/translations/en.json ``` And adding our extension's string to it: ```JavaScript #file: tom.restaurants/app/translations/en.json { "shoutem": { "restaurants": { "letsEatMessage": "Let's eat!" } } } ``` This string has to be exported in `app/index.js`: ```JavaScript{1,10-16} #file: tom.restaurants/app/index.js import enTranslations from './translations/en.json'; // Constants `screens` (from extension.js) and `reducer` (from index.js) // are exported via named export // It is important to use those exact names // export everything from extension.js export * from './extension'; export const shoutem = { i18n: { translations: { en: enTranslations, }, }, }; ``` And that's it! #### Pluralized Strings When editing strings that contain pluralization (e.g. `1 point` vs. `2 points`) we utilize the following format for the language file: ```JSON{4-7} #file: en.json { "shoutem": { "loyalty": { "pointsInStore": { "one": "{% raw %}{{{% endraw %}count}} point collected.", "other": "{% raw %}{{{% endraw %}count}} points collected.", "zero": "No points collected." } } } } ``` And the following method inside the actual React Native component: ```JavaScript{5} #file: shoutem.loyalty/app/components/PlaceIconView.js const { place, points, onPress } = this.props; return ( {I18n.t(ext('pointsInStore'), { count: points })} ); ``` It's important to use the `count` variable name specifically as described in `i18n-js` [docs](https://github.com/fnando/i18n-js#readme). #### Variables in Strings When editing strings that contain variables we utilize the following format for the language file: ```JSON{3-4} #file: en.json { "shoutem": { "auth": { "loggedInUserInfo": "Username: {% raw %}{{{% endraw %}username}}" } } } ``` And the following method inside the actual React Native component: ```JavaScript{6} #file: shoutem.auth/app/screens/EditProfileScreen.js const { user } = this.props; const { name, profile_image_url: image } = user; return ( {I18n.t(ext('loggedInUserInfo'), { username: name })} ); ``` ## Implementing Fallback Strings in Custom Extensions Fallback strings are made to make sure that each string has a translation if the language file (e.g. `en.json`) is missing a translation. They are implemented in each extension which has strings specific to itself. One such example is the `BUY THIS BOOK` string, which is specific to the `shoutem.books` extension. The fallback for this translation looks like this: ```JSON #file: shoutem.books/app/translations/en.json { "shoutem": { "books": { "buyButtonText": "BUY THIS BOOK" } } } ``` As you can see, it's identical to the full language file, however, it only contains the strings for that specific extension. You'll also have to export these translations from `app/index.js`. ```JavaScript #file: shoutem.books/app/index.js import enTranslations from './translations/en.json'; export const shoutem = { i18n: { translations: { en: enTranslations, }, }, }; ``` ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-14-SettingUpInstagram.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/setting-up-instagram title: Setting up Instagram section: Tutorials --- # Setting up Instagram Shoutem's Photos RSS can utilize an Instagram feed, however, since Instagram has now closed their API, you will have to be the owner of the feed in order to generate a URL. In order to show an Instagram feed in a Shoutem app using the Photos RSS, you will have to set up a redirect URL. The first step is to go to the Instagram developer [site](https://www.instagram.com/developer/) and create a [new client](https://www.instagram.com/developer/clients/register/) if you don't already have one. Take note of the Client ID and Client Secret, you will be needing these to get your access token. Here are the steps: #### 1) Manage your client, go to the Security tab and set two redirection URIs: `https://new.shoutem.com` and `http://new.shoutem.com` #### 2) Un-check the `Disable implicit 0Auth` setting.

#### 3) Update the client. #### 4) Navigate to: ``` https://api.instagram.com/oauth/authorize/?client_id=&redirect_uri=https://new.shoutem.com&response_type=token&scope=public_content ``` > #### Note > Replace any placeholder values with the ones from your client. For example, `client_id=` should be `client_id=1oct365163444080a0cd6c3451486736`. At this point your browser may warn you that it requires you to authorize access to this if the client is in sandbox mode. Authorize it in order to proceed and get the access token. #### 5) You can find the token after the # symbol in the address bar of your browser.

## How to generate a content URL for Shoutem In order to fetch content via Photos RSS you will need to fetch it from an Instagram endpoint using your access token. You can find out which endpoints are available in the Instagram [documentation](https://www.instagram.com/developer/endpoints/), of which you'll most likely be interested in the [media](https://www.instagram.com/developer/endpoints/media/) ones. Here are some example links you can use with Shoutem's Photos RSS in order to fetch Instagram images. #### Recent images of all users This can be used to retrieve all recent images, it's the most commonly used link. ``` https://api.instagram.com/v1/users/self/media/recent?access_token= ``` > #### Note > Replace any placeholder values with the ones from your client. For example, `client_id=` should be `client_id=1oct365163444080a0cd6c3451486736`. #### Search within a location This can be used to retrieve images from a specific location defined by latitude and longitude. ``` https://api.instagram.com/v1/media/search?lat=48.858844&lng=2.294351&access_token= ``` > #### Note > Replace any placeholder values with the ones from your client. For example, `client_id=` should be `client_id=1oct365163444080a0cd6c3451486736`. ## API alternative You can also generate an access token using the following API call with `curl`: ```ShellSession curl -X POST \ https://api.instagram.com/oauth/access_token \ -H 'cache-control: no-cache' \ -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \ -H 'postman-token: 76328f00-0a28-153d-1e8e-14bab1433aef' \ -F client_id= \ -F client_secret= \ -F grant_type=authorization_code \ -F redirect_uri=https://new.shoutem.com \ -F code=2f012e51d8cd4e649d6971b9b11841a1 ``` ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-15-UsingPatchPackage.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/using-patch-package title: Using patch-package with extensions section: Tutorials --- # Using patch-package with extensions As of platform 2.2.2, Shoutem apps support [patch-package](https://www.npmjs.com/package/patch-package) changes being made to dependencies by extensions during the Shoutem app configuration process. Read on to find out how you can utilize this feature. ## When to use patch-package When working with third party packages, you'll run into the issue of maintenance, bugs on their end and missing features. Sometimes the answer is to fork, sometimes it's better to just patch it. ## How to make a patch The following steps are how we created our own `patch-package` changes, for example, in [shoutem.audio](https://github.com/shoutem/extensions/tree/master/shoutem.audio/app/patch). It uses the instructions provided by the `patch-package` package. #### 1. Add the dependency To make a patch, you will have to have the dependency you want to edit already installed as you would when running `shoutem configure`. You can [add your dependency]({{ site.url }}/docs/extensions/tutorials/installing-3rd-party-packages) via your extension's `app/package.json` and it will be installed when `shoutem configure` is run. #### 2. Make the changes in node_modules After running `shoutem configure`, you will have to make the edits you need in `App-Name/node_modules/your-dependency`, this will allow `patch-package` to generate a _diff_ and you'll be set. #### 3. Creating the patch file Following the changes made in step 2, create a `patch` directory in your extension's app segment. From the `App-Name/` directory, you run the following: ```ShellSession $ npx patch-package your-dependency@version --patch-dir extensions/your-dev-name.your-ext-name/app/patch ``` This will generate a patch-file and place it into your extension's `app/patch` directory that you just created. #### 4. Let Shoutem do it's magic Kick back, relax, Shoutem takes care of it from here. If you wanna know how, read the next section. ## How it works "Under the hood" Shoutem's platform has a `postinstall` script which will apply all patches provided in every installed extension's `app/patch` directory right after the `shoutem configure` script does it's dependency installation step (since it's the postinstall script being run). In short, patches will be applied every time the app's dependencies are installed, either manually, or with the `shoutem configure` command. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-16-NavigationIntroduction.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/navigation-introduction title: Navigation section: Tutorials --- # Navigation With the platform update 3.0.0, we’ve made some significant changes to the navigation extension. Main motivation behind this extension overhaul was mostly performance related, but it is also supposed to allow for easier extension development for both Shoutem in-house team, and the 3rd party developers, going forward. Due to the scope of the changes we needed to implement, there are some breaking changes that 3rd party developers will need to take into account when upgrading their extensions to support the new platform. You can continue reading about changes and improvements in the [following pages]({{ site.url }}/docs/extensions/tutorials/navigation-breaking-changes), where we will dive deeper into main differences between the old platform and suggest best practices with updating your current custom extension versions. ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-17-NavigationBreakingChanges.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/navigation-breaking-changes title: Breaking changes section: Tutorials --- # Breaking changes In this section, we'll cover most fundamental changes implemented with the rework of our navigation flows. Consequently, most of these changes are in fact breaking changes as they modify how things work under the hood ## React navigation as the underlying architecture We wanted to design our navigation mechanisms to closely follow the [most commonly used navigation library](https://reactnavigation.org/docs/5.x/getting-started/) across react native apps. So if you’re familiar with creating apps for react native, this interface should be familiar, and allow for easier transition. Every screen in Shoutem apps will pass down the standard react navigation props, such as [route](https://reactnavigation.org/docs/5.x/route-prop/), or [navigation](https://reactnavigation.org/docs/5.x/navigation-prop/). So from this point of view, nothing really changes in comparison to standard react navigation apps. In addition to that, you will receive the shortcut route props, that were previously passed directly to the shoutem screen. So where you were reading the shortcut prop inside the screen directly, you will now need to destructure it first, from the shortcut route prop. This by itself, makes it possible to use the react-navigation library just like you would outside Shoutem apps. We’ve also created a few helper methods for extracting the Shoutem relevant props, from the navigation route object. For full reference, you can check out the shoutem.navigation extension in our [public extensions repository](https://github.com/shoutem/extensions). ## Removing redux based navigation Prior to the new platform update, our navigation implementation relied heavily on redux actions / redux middleware. While this allowed us more control over navigation in the past, it also introduced a significant drawback in terms of performance and overall resource usage. This was the main motivation behind walking away from this approach in place of more performant architecture. As mentioned earlier, we now make use of react-navigation “native” actions which are wrapped under shoutem.navigation methods. This interface went through minimal changes, looking at it from the top level. This means that most of the actions are still named the same, and are imported from the same extension / path. However, under the hood, they now use more performant mechanisms. This is another `breaking change` you will need to consider. The change however, is minimal. You will simply need to remove the dispatch layer around the navigation actions (seeing as they are no longer run through redux mechanisms). For example, if you used the `navigateTo` action from shoutem.navigation, you will simply remove it from `mapDispatchToProps`, or just stop dispatching the action if you’re not mapping it through redux `connect`. Similar goes for `openInModal`, or any other navigation action you used before. In the snippet below, you have a standard way of navigating in previous platforms. ```JavaScript import { navigateTo } from 'shoutem.navigation'; ... navigateToScreen() { const { navigateTo } = this.props; navigateTo('ScreenName'); } ... const mapDispatchToProps = { ...otherMappedActions, navigateTo }; ``` To make this code compatible with the new platform, you would simply use the navigateTo action directly, without having to map it to dispatcher. ```JavaScript import { navigateTo } from 'shoutem.navigation'; ... navigateToScreen() { navigateTo('ScreenName'); } ... const mapDispatchToProps = { ...otherMappedActions }; ``` You can apply this for every other navigational action, using the same formula. In case you have more complex behaviours and need some advice, feel free to check how other [shoutem extensions](https://github.com/shoutem/extensions) are handling their navigation flows, or contact us directly. ## Removing NavigationBar component from screens Previously, this component was something you would need to implement in every screen you create. The main purpose of this was to provide you with ways of styling the navbar, specifically to this one screen. You would do this through the `styleName` prop, very similar to any other component styled with the Shoutem theme. While this worked well, the implementation was very abstract and customizing certain behaviours was complex and difficult to understand. React-navigation allows us to do this in an easier way, without introducing an abstracted component in your JSX code. Additionally, it will provide us with ways to change the navbar styling dynamically, and overall, provide more control over how the navbar is drawn. Styling your navbar now is done the same way you would do it outside Shoutem apps, if you’re using react-navigation library. Navigation prop that is passed to the screen will allow you to call it’s [setOptions](https://reactnavigation.org/docs/5.x/navigation-prop/#setoptions) method, which amongst other things, allows you to set specific navbar / header styling. So now, you can style your navbar code outside the render method. To allow for easier transition, we’ve introduced a helper method that will apply the same navbar styling you would achieve by setting the `styleName` prop on the old `NavigationBar` component. Please find the example below. Previously, you would add the NavigationBar component to your render method like this : ```JavaScript render() { return ( {this.renderData()} ); } ``` This code/logic is now moved outside the render method, resulting in cleaner JSX syntax, as well as options of changing the styling dynamically in any lifecycle method, or other cases you may require. ```JavaScript import { composeNavigationStyles } from 'shoutem.navigation'; componentDidMount() { const { navigation } = this.props; navigation.setOptions({ ...composeNavigationStyles(['clear', 'fade']), ...otherOptions, }); } render() { return ( {this.renderData()} ); } ``` We make use of the `composeNavigationStyles` helper here, to apply the same styling you would receive through the `styleName` props on `NavigationBar`. Simply pass on the `styleName`'s used before, into this method, and you will receive the same styling you’ve had before. Alongside other benefits, you can now apply your own custom navbar styling in a more transparent and easier way if necessary. This is also true for general navbar behaviour, such as hiding the navbar in specific cases or similar. For the list of options on specific navigators, please check the [react-navigation documentation.](https://reactnavigation.org/docs/5.x/screen-options/) ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-18-NavigationStacks.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/navigation-stacks title: Navigation stacks section: Tutorials --- # Navigation stacks Another convenient improvement made possible by integrating react-navigation was the ability to define your own custom navigation flows, or navigation stacks as they are referred to in react-navigation. Previously, the navigational structure of Shoutem apps heavily depended on the shortcut tree you have defined inside the app builder. With the introduction of custom navigation stacks, you can now define your own navigation flow that can go outside the bounds of screen structure defined in the app builder. That means you can design multiple screens and define their navigational behaviour, without having to compose any kind of screen structure inside the app builder. This comes in very handy when you need to implement a set of 3rd party authentication screens, or if you want to create some kind of onboarding set of screens, or any similar scenario that would require the user to step out of the standard screen structure, to complete some kind of app segment. The interface needed to create something like this is conveniently simple, and it relies mostly on registering the screens that are a part of the new navigation flow. ```JavaScript import { NavigationStacks } from 'shoutem.navigation'; NavigationStacks.registerNavigationStack(config); ``` Where the config object has the following properties -> - **name** -> name of the custom stack. You can use the extension canonical name here, or anything else that fits your case. You will use this name to initialize the custom stack flow, and open it’s first screen - **navigationStack** -> If needed, you can pass on the complete navigation stack that you created previously. By default, we will create a new [Stack type navigator](https://reactnavigation.org/docs/5.x/stack-navigator/), and add the registered screen as it’s children - **screens** -> array of objects containing `{ name, component }` properties. These are the screens that will be added to your custom navigation stack. - **screenOptions** -> Set of global screen options that will be applied to your custom navigator. Check the full reference [here](https://reactnavigation.org/docs/5.x/screen-options/#screenoptions-prop-on-the-navigator). - **navigatorOptions** -> Any other props you wish to pass on to your custom navigator. Options here depend mostly on the type of navigation stack from react-navigation that you’re using here. Once you have registered your custom stack, you can navigate to it by simply calling the `openStack` method. ```JavaScript import { NavigationStacks } from 'shoutem.navigation'; NavigationStacks.openStack(stackName, params = {}, initialRoute = null); ``` Where the `stackName` is the name of the stack you registered beforehand, `params` are the optional set of route params you want to pass to the first route of the stack, and the `initialRoute` as the name of the screen in the stack you want to open first. By default, `initialRoute` will be the first registered screen for the custom stack. Once you’re done, you can simply call the `closeStack` method and return to the point in the app where you were prior to opening the stack. ```JavaScript NavigationStacks.closeStack(stackName); ``` A good example of how you can use this new interface is our [shoutem.auth](https://github.com/shoutem/extensions/blob/master/shoutem.auth/app/navigation.js#L11) extension that implements a custom stack for it’s authentication flows. ## Modal stack navigation Another example of navigation that uses the new navigation stack interface is the modal stack. By default, every shortcut screen from any of the extensions installed in your app, will be added to the modal stack, so it can be opened from any point in your application. If you need to add other screens, you can do so by calling ModalStack `registerModalScreens` method. If you’re registering a screen that is exported from one of the extensions, you can use the short syntax, for example: ```JavaScript ModalScreens.registerModalScreens(['shoutem.news.NewsListScreen']); ``` Or, you can use the longer syntax if necessary: ```JavaScript ModalScreens.registerModalScreens([{ name: 'shoutem.news.NewsListScreen', component: ScreenComponent, options: reactNavigationScreenOptions }]); ``` Where the component represents the Screen component, and the options are the optional screen options object conforming to the react-navigation syntax. Once you have this setup in place, navigating to a screen inside the modal stack is as easy as calling the `openInModal` from shoutem.navigation. Remember, these actions do not need to be dispatched through redux anymore. ```JavaScript import { openInModal } from 'shoutem.navigation'; openInModal('shoutem.auth.EditProfileScreen'); ``` When you want to close the modal stack, just call closeModal from shoutem.navigation, and you will be returned to the point in app prior to opening the modal stack. ```JavaScript import { closeModal } from 'shoutem.navigation'; closeModal(); ``` ================================================ FILE: docs/extensions/tutorials/_posts/1970-01-19-NavigationScreenDecorators.md ================================================ --- layout: doc permalink: /docs/extensions/tutorials/navigation-screen-decorators title: Screen decorators section: Tutorials --- # Screen decorators Another powerful tool at your disposal with the new platform is the ability to decorate all registered screens with some custom behaviour. Internally, this is most commonly used to replace any custom logic that depended on navigation actions middleware, but you can use it to deliver any kind of code that needs to affect screens globally. A screen decorator in its nature is just a simple [HoC](https://reactjs.org/docs/higher-order-components.html) that wraps your screen with additional code. Good example of this can be seen in [shoutem.advertising](https://github.com/shoutem/extensions/blob/master/shoutem.advertising/app/services/withAdBanner.js) extension where we implement a screen decorator that renders ad banners on top of the screen, based on various parameters. Once you write your decorator, you will need to register it, in order to be applied globally through the app. This is done in following way: ```JavaScript import { Decorators } from 'shoutem.navigation'; import { withAdBanner } from './services'; Decorators.registerDecorator(withAdBanner); ``` This concludes the most notable or breaking changes that were introduced with the new platform. We encourage all developers to take a look at our [extensions repository](https://github.com/shoutem/extensions) that contains all of our extensions, rewritten to be compliant with the new navigation. It should be a great example on how to make use of the new functionalities, or just how to solve some familiar problems when it comes to creating good and meaningful navigation flows in your apps. Lastly, we are thankful for any feedback or suggestions you may have in regards to the new additions. Feel free to contact us via our usual [channels](https://shoutem.com/about/contact-us/support/). ================================================ FILE: docs/ui-toolkit/animation/_posts/1970-01-02-Driver.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/animation/driver title: Driver section: Animation --- # Driver An animation is driven by the `driver`. It encapsulates the creation of animation [input events](https://facebook.github.io/react-native/docs/animations.html#input-events), making React Native animations even more declarative. Drivers are attached to animation components which are set to listen to specific driver inputs. Currently, two drivers are supported: - `ScrollDriver`, that binds scrolling of React Native's [ScrollView](https://facebook.github.io/react-native/docs/scrollview.html) to the animation components and - `TimingDriver`, that creates animated values that change with time ## ScrollDriver Binding is done by passing properties to `ScrollView`: ```javascript import React from 'react'; import { ScrollView } from 'react-native'; import { ScrollDriver } from '@shoutem/animation'; class Screen extends React.Component { render() { const driver = new ScrollDriver(); return ( {/* Pass driver to animation components */} ); } } ``` ## TimingDriver Animated value is changing with time: ```javascript import React from 'react'; import { View, Easing } from 'react-native'; import { TimingDriver, FadeIn } from '@shoutem/animation'; class Screen extends React.Component { render() { driver = new TimingDriver({ duration: 400 // 250 by default, easing: Easing.inOut // Easing.cubic is passed by default delay: 200 // 0 by default }); return ( {/* Some components to fade in with time passing */} ) } } ``` To trigger the start of the timer, call `runTimer` method on `TimingDriver` instance. The signature of `runTimer` method is as follows: #### `runTimer(endValue, onFinish)`: - `endValue`: Number, when the timer reaches this value the animations will end - `onFinish` Function, callback that will be called upon reaching `endValue` ================================================ FILE: docs/ui-toolkit/animation/_posts/1970-01-03-Animations.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/animation/introduction title: Introduction section: Animation --- # Animation
When building an app, there's always a need to create animations to enrich the user experience. Although React Native [provides a way](https://facebook.github.io/react-native/docs/animations.html) to implement arbitrary animations, it is not an easy task to do, even for simple animations. That's where `@shoutem/animation` package comes in. The package contains **animation [components](#components)** that should be wrapped around components that you want to animate, and [**drivers**](#driver) that _drive_ the animation components. ## Installation Simply install it with: ```ShellSession $ npm install @shoutem/animation ``` ## New animations - coming soon We're working hard to open source more `animation components` and `drivers`, which are already helping us in many [Shoutem extensions]({{ site.url }}/docs/extensions/tutorials/getting-started). Some of them are: **Drivers:** - `ComputionalDriver` - `TouchDriver`
**Animation components:** - `Slide` - several `Transitions`
Stay tuned! ================================================ FILE: docs/ui-toolkit/animation/_posts/1970-01-03-FadeIn.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/animation/fade-in title: FadeIn section: Animation --- # FadeIn Fades in components wrapped by it.
## API #### Props - `driver`: Driver that is running the animation - `children`: Components that will be affected by the animation - `inputRange`: Array `[from, to]` including a `'from' animated value` and `'to' animated value` ## Example
#### JSX declaration ```javascript const driver = new ScrollDriver(); return ( ); ``` The above code will create a scroll dependent fade in animation over `Image` component from scroll position 100 to scroll position 150 where `Image` is fully transparent at scroll position 100 and opaque at scroll position 150. ================================================ FILE: docs/ui-toolkit/animation/_posts/1970-01-04-FadeOut.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/animation/fade-out title: FadeOut section: Animation --- # FadeOut Fades out components wrapped by it.
## API #### Props - `driver`: Driver that is running the animation - `children`: Components that will be affected by the animation - `inputRange`: Array `[from, to]` including a `'from' animated value` and `'to' animated value` ## Example
#### JSX declaration ```javascript const driver = new ScrollDriver(); return ( ); ``` The above code will create a scroll dependent fade out animation over `Image` component from scroll 100 to scroll 150 where `Image` is opaque at scroll 100, and fully transparent at scroll 150. ================================================ FILE: docs/ui-toolkit/animation/_posts/1970-01-05-ZoomIn.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/animation/zoom-in title: ZoomIn section: Animation --- # ZoomIn Zooms in components wrapped by it.
## API #### Props - `driver`: Driver that is running the animation - `children`: Components that will be affected by the animation - `inputRange`: Array `[from, to]` including a `'from' animated value` and `'to' animated value` - `maxFactor`: Number, factor to which `children` components will be zoomed in ## Example
#### JSX declaration ```javascript const driver = new ScrollDriver(); return ( ); ``` The above code will create a scroll dependent zoom in animation over `Image` component from scroll 100 to scroll 150 where `Image` has its original size at scroll 100, and is scaled by maxFactor at scroll 150. ================================================ FILE: docs/ui-toolkit/animation/_posts/1970-01-06-ZoomOut.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/animation/zoom-out title: ZoomOut section: Animation --- # ZoomOut Zooms out components wrapped by it.
## API #### Props - `driver`: Driver that is running the animation - `children`: Components that will be affected by the animation - `inputRange`: Array `[from, to]` including a `'from' animated value` and `'to' animated value` - `maxFactor`: Number, factor to which `children` components will be zoomed out ## Example
#### JSX declaration ```javascript const driver = new ScrollDriver(); return ( ); ``` The above code will create a scroll dependent zoom out animation over `Image` component from scroll 100 to scroll 150 where `Image` is scaled by maxFactor at scroll 100 and has its original size at scroll 150. ================================================ FILE: docs/ui-toolkit/animation/_posts/1970-01-07-Parallax.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/animation/parallax title: Parallax section: Animation --- # Parallax Adds parallax effect to its children components. The children are translated depending on the scroll speed by default, but you can pass extrapolation options to limit the translation.
## API #### Props - `driver`: Driver that is running the animation - `children`: Components that will be affected by the animation - `extrapolation`: Object, [extrapolation options](https://facebook.github.io/react-native/docs/animations.html#composing-animations) for parallax translation. By default, children will be translated by `scrollVector * (scrollSpeed - 1) * driver.value` where `scrollVector` is defined by scrolling direction - `scrollSpeed`: Number, how fast will the children be translated - `insideScroll` Bool, defines if the parallax placed is inside or outside of the `ScrollView` ## Example
#### JSX declaration ```javascript const driver = new ScrollDriver(); return ( Title ); ``` The above code will create a scroll dependent parallax animation over `Image` component where `Image` will be scrolled 2 times faster than `Title`. ================================================ FILE: docs/ui-toolkit/animation/_posts/1970-01-08-CombiningAnimations.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/animation/combining-animations title: Combining animations section: Animation --- # Combining animations Animations can be combined simply by wrapping each other. ```javascript import React, { Component } from 'react'; import { ScrollView } from 'react-native'; import { Parallax, HeroHeader, FadeOut, FadeIn, ScrollDriver, } from '@shoutem/animation'; import { Image, Tile, Title, Text, Subtitle, View, } from '@shoutem/ui'; export default class MyAnimatedScreen extends Component { getRestaurant() { return { name: "Gaspar Brasserie", address: "185 Sutter St, San Francisco, CA 94109", url: "gasparbrasserie.com", image: { "url": "https://shoutem.github.io/restaurants/restaurant-1.jpg"}, mail: "info@gasparbrasserie.com" }; } render() { const restaurant = this.getRestaurant(); const driver = new ScrollDriver(); return ( {restaurant.name} {restaurant.address} Gaspar is a delightful French restaurant in San Francisco\’s Financial District that is inspired by the romantic, bustling Paris of old. Located near famed Union Square, our richly-designed interiors make you feel as if you are truly in Paris and provide the perfect setting for enjoying our exquisite classic and modern French fare such as Duck Leg Confit and always popular Steak Frites. Gaspar offers two stories of dining in addition to full bars both upstairs and downstairs and an exclusive room reserved to hold the largest selection of Cognac in San Francisco. In addition to our all day menu, we offer live jazz music on Saturdays. ); } } ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-01-Introduction.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/introduction title: Introduction section: UI toolkit --- # Introduction ![UI toolkit]({{ site.url }}/img/ui-toolkit/introduction@2x.jpg "UI toolkit"){:.docs-component-image} Shoutem UI toolkit enables you to build professionally looking React Native apps with ease. It consists of three libraries: - [@shoutem/ui](https://github.com/shoutem/ui): beautiful and customizable UI [components]({{ site.url }}/docs/ui-toolkit/components/typography) - [@shoutem/theme](https://github.com/shoutem/theme): “CSS-way” of styling the entire app with [themes]({{ site.url }}/docs/ui-toolkit/theme/introduction) - [@shoutem/animation](https://github.com/shoutem/animation): declarative way of applying ready-made [animations]({{ site.url }}/docs/ui-toolkit/animation/introduction) ## Prerequisites Before starting make sure you have: - Installed [npm](https://www.npmjs.com/) (installed with [Node.js](https://nodejs.org/en/)) - Installed [React Native](https://facebook.github.io/react-native/docs/getting-started.html) ## Installation Create new React Native project: ```bash $ react-native init HelloWorld $ cd HelloWorld ``` Install and link `@shoutem/ui` in your project: ```bash $ npm install @shoutem/ui --save $ react-native link ``` To check components in UI toolkit, simply copy the following to your `index.ios.js` file of React Native project: ```JavaScript #file: index.ios.js import React, { Component } from 'react'; import { Examples } from '@shoutem/ui'; export default class App extends Component<{}> { render() { return ( ); } } ``` Finally, run the app! ```bash $ react-native run-ios ``` To see other components, import them from `@shoutem/ui` and render them. You can also use standard React Native components in your layouts anywhere you want, but they will not inherit either the theme or the parent styles, so you will need to style them manually. ## Styling components All components have default style defined by theme in [theme.js](https://github.com/shoutem/ui/blob/develop/theme.js) file in the root of `@shoutem/ui` package. Theme uses [styling rules]({{ site.url }}/docs/ui-toolkit/theme/introduction) interpreted by [@shoutem/theme](https://github.com/shoutem/theme) package. Each component can be customized with React Native inline styles or by using `styleName` properties. Style name is similar to CSS class - it contains a set of styles that is applied to a component, defined in theme Listed below are common style names that can be used in several UI toolkit components and their variations. ### Gutters A gutter is an empty space between a component's boundaries and the component's content. Set the same gutter for each side: * **sm-gutter**: small size, defaults to 5px * **md-gutter**: medium size, defaults to 15px * **lg-gutter**: large size, defaults to 30px * **xl-gutter**: extra large size, defaults to 45px > On `View`, `Tile` and `Overlay` components, gutter is applied as padding, while on `Text` (Typography) and `Button` components gutter is applied as margin. Set the gutter for specific side: * **_size_-gutter-left**: applied to the left side of component * **_size_-gutter-right**: applied to the right side of component * **_size_-gutter-top**: applied to the top side of component * **_size_-gutter-bottom**: applied to the bottom side of component * **_size_-gutter-horizontal**: applied to horizontal sides (left and right) of component * **_size_-gutter-vertical**: applied only to vertical sides (top and bottom) of component ### Other common style names * **rounded-corners**: applies border radius (defaults to 2 px) to component * **flexible**: applies `flexbox` to component so it fills parent container component * **inflexible**: component is sized according to its width/height properties but completely inflexible * **collapsible**: causes the component to shrink if it overflows parent container * **stretch**: causes the component to fully fill parent container ### Example Below is one example where and how common style names can be used:
#### JSX Declaration ```JSX -20% COOL BLACK AND WHITE STYLISH WATCHES $280.00 $250.00 ``` Check [Shoutem Theme]({{ site.url }}/docs/ui-toolkit/theme/introduction "Shoutem Theme") to learn more about styling rules, style names and how to define your own style name. ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-02-Typography.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/typography title: Typography section: UI toolkit --- # Typography Typography components consist of several flavors of `Text` components with predefined styles. Available components are: ```JSX ... ... ... ... ... ``` ![Title example]({{ site.url }}/img/ui-toolkit/typography/title@2x.png "Title"){:.docs-component-image} ## API #### Props * Every component in this section supports every `prop` that the standard React Native `Text` component supports (eg. `numberOfLines`). You can see the full list of available props on React Native [Text component documentation](https://facebook.github.io/react-native/docs/text.html "React Native Text component documentation"). #### Style names * **bold**: Sets text to be bold * **h-center**: Centers the text horizontally * **line-through**: Defines a line through the text * **multiline**: Increases line-height to allow text to wrap * **v-center**: Works only in combination with `multiline` styleName. Applies additional top and bottom margins to compensate the unsupported `textAlignVertical` prop on iOS * **secondary**: Applies secondary styles as defined in [theme](https://github.com/shoutem/ui/blob/develop/theme.js#L1011) ## Examples ### Heading ![Heading example]({{ site.url }}/img/ui-toolkit/typography/heading@2x.png "Heading"){:.docs-component-image} #### JSX Declaration ```JSX Mobile App Creator ``` ### Title ![Title example]({{ site.url }}/img/ui-toolkit/typography/title@2x.png "Title"){:.docs-component-image} #### JSX Declaration ```JSX MOBILE APP CREATOR ``` ### Subtitle ![Subtitle example]({{ site.url }}/img/ui-toolkit/typography/subtitle@2x.png "Subtitle"){:.docs-component-image} #### JSX Declaration ```JSX Mobile App Creator ``` ### Text ![Text example]({{ site.url }}/img/ui-toolkit/typography/text@2x.png "Text"){:.docs-component-image} #### JSX Declaration ```JSX Mobile App Creator ``` ### Caption ![Caption example]({{ site.url }}/img/ui-toolkit/typography/caption@2x.png "Caption"){:.docs-component-image} #### JSX Declaration ```JSX Mobile App Creator ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-03-NavigationBar.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/navigation-bar title: NavigationBar section: UI toolkit --- # NavigationBar Shoutem UI toolkit contains two different `NavigationBar` components: 1) Simple 3-column `NavigationBar` that can be used on any screen or `Modal` window ![Navbar / Solid example]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-title-only@2x.png "Navbar / Solid"){:.docs-component-image} 2) `Redux` and stack-based `NavigationBar` enables any view to act as a navigation view using reducers to manipulate state at a top-level object. Can be used only on components that are within the stack (i.e. it cannot be used on `Modal` window). Internally, it relies on [`NavigationExperimental`](https://github.com/shoutem/react-native-navigation-experimental-compat) from `react-native-navigation-experimental-compat`. # Simple NavigationBar ## Import ```JSX import { NavigationBar } from '@shoutem/ui' ``` `NavigationBar` is `node` for [Navigator](https://facebook.github.io/react-native/docs/navigator.html#navigationbar) React Native component. It provides a simpler way to use 3-column navigation bar. ## API #### Props * **title**: string - Sets the `centerComponent` prop to a `Title` component with the provided string as the title text * **centerComponent**: object - Represents the center component in `NavigationBar` (e.g. screen title) * **leftComponent**: object - Represents the left component in `NavigationBar` (e.g. back button) * **rightComponent**: object - Represents the right component in `NavigationBar` (e.g. drop-down menu button) * **hasHistory**: bool - If set to `true`, the `leftComponent` will become a back arrow which triggers `navigateBack` on tap * **navigateBack**: function - Callback triggered after tapping the `Back` button if `hasHistory` is set to `true` #### Style names * **clear**: sets the `Text` color to white and background colors to transparent * **inline**: forces relative positioning of `NavigationBar` component, allowing component to be used inline with other components, i.e. `ListView`, without its content overlapping `NavigationBar` * **no-border**: removes the bottom border #### Style * **centerComponent** - Style applied to center navigation component * **container** - Style for `View` that holds all components within `NavigationBar` * **componentsContainer** - Style for `View` container that holds `leftComponent`, `centerComponent` and `rightComponent` objects * **leftComponent** - Style applied to left navigation component * **rightComponent** - Style applied to right navigation component ## Examples ### Solid ![Solid example]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-title-only@2x.png "Solid"){:.docs-component-image} #### JSX Declaration ```JSX TITLE} /> ``` ### Showing Background Image ![Clear (Image) example]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-imageoverlay-image@2x.png "Clear (Image)"){:.docs-component-image} #### JSX Declaration ```JSX TITLE} /> ``` ### Navbar + Drawer ![Navbar + Drawer example]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-drawernav@2x.png "Navbar + Drawer"){:.docs-component-image} #### JSX Declaration ```JSX } centerComponent={TITLE} /> ``` ### Navbar + Picker ![Navbar + Picker example]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-picker@2x.png "Navbar + Picker"){:.docs-component-image} #### JSX Declaration ```JSX constructor(props){ super(props); this.state = { filters: [ { name: 'Filter', value: 'Filter' }, { name: 'Sport', value: 'Sport' }, { name: 'Food', value: 'Food' }, ], } } render() { return ( } centerComponent={ {this.state.selectedFilter ? this.state.selectedFilter.value : this.state.filters[0].value} } rightComponent={ this.setState({ selectedFilter: filter })} titleProperty="name" valueProperty="value" /> } /> ); } ``` ### Navbar + Action ![Navbar + Action example]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-action@2x.png "Navbar + Action"){:.docs-component-image} #### JSX Declaration ```JSX } centerComponent={TITLE} rightComponent={( )} /> ``` ### Navbar + Icon Button ![Navbar + Icon example]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-icon@2x.png "Navbar + Icon"){:.docs-component-image} #### JSX Declaration ```JSX } centerComponent={TITLE} rightComponent={( )} /> ``` ### Navbar (Sublevel) + Icon ![Navbar (Sublevel) + Icon example]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-sublevel-icon@2x.png "Navbar + (Sublevel) + icon"){:.docs-component-image} #### JSX Declaration ```JSX TITLE} share={% raw %}{{{% endraw %} link: 'http://shoutem.github.io', text: 'This is the best', title: 'Super cool UI Toolkit', }} /> ``` ### Navbar (Sublevel) + Action (no border) ![Navbar (Sublevel) + Action (no border) example]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-sublevel-action-no-border@2x.png "Navbar (Sublevel) + Action (no border)"){:.docs-component-image} #### JSX Declaration ```JSX TITLE} share={% raw %}{{{% endraw %} link: 'http://shoutem.github.io', text: 'This is the best', title: 'Super cool UI Toolkit', }} /> ``` ### Navbar (Sublevel) + Action ![Navbar (Sublevel) + Action example]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-sublevel-action@2x.png "NavigationBar (Sublevel) + Action"){:.docs-component-image} #### JSX Declaration ```JSX TITLE} rightComponent={( )} /> ``` ### Navbar (Modal) + Share Button ![Navbar (Modal) + Share Button]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-modal-icon@2x.png "Navbar (Modal) + Icon"){:.docs-component-image} #### JSX Declaration ```JSX )} centerComponent={TITLE} share={% raw %}{{{% endraw %} link: 'http://shoutem.github.io', text: 'This is the best', title: 'Super cool UI Toolkit', }} /> ``` ### Navbar (Modal) + Action ![Navbar (Modal) + Action example]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-modal-action@2x.png "Navbar (Modal) + Action"){:.docs-component-image} #### JSX Declaration ```JSX )} centerComponent={TITLE} rightComponent={( )} /> ``` ## Navbar (Modal) + Action 2 ![Navbar (Modal) + Action 2 example]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-modal-action-2@2x.png "Navbar (Modal) + Action"){:.docs-component-image} #### JSX Declaration ```JSX Cancel )} centerComponent={TITLE} rightComponent={( )} /> ``` ### Navbar (Modal) + Action 2 (disabled) ![Navbar (Modal) + Action 2 (disabled) example]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-modal-action-2-disabled@2x.png "Navbar (Modal) + Action (disabled)"){:.docs-component-image} #### JSX Declaration ```JSX Cancel )} centerComponent={TITLE} rightComponent={( )} /> ``` ### Navbar (Sublevel) + Share + Showing Background Color ![Navbar (Sublevel) + Share + Showing Background Color]({{ site.url }}/img/ui-toolkit/navigationbar/navbar-sublevel-action-no-border-copy@2x.png "Navbar / On primary color / back + share"){:.docs-component-image} #### JSX Declaration ```JSX TITLE} share={% raw %}{{{% endraw %} link: 'http://shoutem.github.io', text: 'This is the best', title: 'Super cool UI Toolkit', }} /> ``` # Redux and stack-based NavigationBar with CardStack ## Import ```JSX import { NavigationBar } from '@shoutem/ui/navigation' ``` This `NavigationBar` and `CardStack` components provide simpler API for navigation between `Screens` (scenes) with respect to its underlying [Redux](https://github.com/reactjs/redux)-based `NavigationExperimental` React Native component. ## NavigationBar ## API #### Props * **renderLeftComponent**: function - Function that should return components representing left component in `NavigationBar` (example: back button) - Note that outermost component returned by this function should be `View` that has `container` styleName and `virtual` prop * **renderRightComponent**: function - Function that should return components representing right component in `NavigationBar` (example: drop-down menu button) - Note that outermost component returned by this function should be `View` that has `container` styleName and `virtual` prop * **onNavigateBack**: function - This callback is triggered after tapping the Back button if `hasHistory` Prop is set to `true` * **renderTitleComponent**: function - Function that should return components representing center/title component, allowing you to override the default `title` component/prop in `NavigationBar` - Note that outermost component returned by this function should be `View` that has `container` styleName and `virtual` prop * **title**: string - Prop that defines screen title #### Style names * **clear**: sets the `Text` color to white and background colors to transparent * **fade**: sets the `Text` color to white and applies linear gradient to background * **no-border**: removes the bottom border #### Style * **centerComponent** - Style applied to center Navigation component * **container** - Style prop for `View` that holds all components within `NavigationBar` * **componentsContainer** - Style prop for `View` container that holds `leftComponent`, `centerComponent` and `rightComponent` objects * **leftComponent** - Style applied to left Navigation component * **rightComponent** - Style applied to right Navigation component ## CardStack ## API #### Props * **navigationState**: object - Object containing current navigation state (stack) * **onNavigateBack**: function - This callback is triggered after tapping the Back button (usually in `leftComponent`) * **renderNavBar**: function - Function that should return components representing actual `NavigationBar` `View` (container) * **renderScene**: function - Function that should return/render Screen (scene) depending on stack content (current topmost route) #### Style names * None #### Style * None ## Example In order to use this `NavigationBar` component, you will need to initialize `CardStack` as root component of your application and define base `NavigationBar.View` which will hold actual `NavigationBar` according to provided props.
#### JSX Declaration ```JSX class App extends Component { static propTypes = { onNavigateBack: React.PropTypes.func.isRequired, navigationState: React.PropTypes.object, scene: React.PropTypes.object, }; constructor(props) { super(props); this.renderNavBar = this.renderNavBar.bind(this); this.renderScene = this.renderScene.bind(this); } renderScene(props) { const { route } = props.scene; let Screen = route.key === 'RestaurantDetails' ? RestaurantDetails : RestaurantsList; return (); } renderNavBar(props) { const { onNavigateBack } = this.props; return ( ); } render() { const { navigationState, onNavigateBack } = this.props; return ( ); } } ``` Also, on each screen where you want to have NavigationBar, you'll need to define it as every other component. ```JSX ``` Note that example above is just a part of required code. For a full example, refer to RestaurantApp [example](https://github.com/shoutem/ui/blob/develop/examples/RestaurantsApp/app/), where `App` is root application component holding code above. ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-04-DropDownMenu.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/dropdown-menu title: DropDownMenu section: UI toolkit --- # DropDownMenu DropDownMenu is a full-screen contextual menu for displaying lists of items. ![DropDownMenu example]({{ site.url }}/img/ui-toolkit/dropdownmenu/drop_down_menu@2x.png "DropDownMenu"){:.docs-component-image} ## API #### Props * **onOptionSelected(option: object)**: function - Called after tapping an option from menu, with all Props from that option passed to the function * **options**: array - Array of options that are rendered as `Button`s within `ListView` * **selectedOption**: any - Sets which `option` from the `options` array is selected by default * **titleProperty**: string - Attribute that defines what `key` from `options` Prop will be used to render option Titles in Dropdown menu * **valueProperty**: string - Attribute that defines what `key` from `options` Prop will be used to link an option from `options` with `id` in Dropdown menu * **visibleOptions**: number - Sets a number of options shown without scroll in Dropdown menu. Can also be set through style, note that defining through Props overrides definition in Style #### Style names * **horizontal**: renders a full-width selected option button with background color (defaults to gray), as defined in Theme #### Style * **modal** - Style prop for the outermost `View` within `Modal` component * **modalItem** - Style prop that holds single item (row) in `ListView` * **selectedOption** - Style prop for a dropdown `Button` that opens a full-screen contextual menu and represents currently selected option * **visibleOptions** - Sets a number of options shown without scroll in Dropdown menu. Can also be set through Props, note that defining through Props overrides definition in Style ## Examples ```JSX constructor(props){ super(props); this.state = { cars: [ { brand: "Audi", models: { model: "Audi R8", image: { url: "https://shoutem.github.io/img/ui-toolkit/dropdownmenu/Audi-R8.jpg" }, description: "Exclusively designed by Audi AG's " + "private subsidiary company, Audi Sport GmbH." } }, { brand: "Bugatti", models: { model: "Chiron", image: { url: "https://shoutem.github.io/img/ui-toolkit/dropdownmenu/Chiron.jpg" }, description: "Bugatti premiered the Bugatti " + "Chiron as a successor to the Veyron." } }, { brand: "Chrysler", models: { model: "Dodge Viper", image: { url: "https://shoutem.github.io/img/ui-toolkit/dropdownmenu/Dodge-Viper.jpg" }, description: "The Dodge Viper is a super car " + "manufactured by Dodge (SRT for 2013 and 2014)." } }, ], } } render() { const selectedCar = this.state.selectedCar || this.state.cars[0]; return ( this.setState({ selectedCar: car })} titleProperty="brand" valueProperty="cars.model" /> {selectedCar ? selectedCar.models.model : this.state.cars[0].models.model} {selectedCar ? selectedCar.models.description : this.state.cars[0].models.description} ); } ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-05-ListView.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/list-view title: ListView section: UI toolkit --- # ListView ListView component is a base component used to render Lists of items, as well as a featured first item with a unique layout. This component is also used by GridView to create Grid-like menu structures. Under the hood, it uses React Native's `FlatList` and `SectionList`. ![ListView example]({{ site.url }}/img/ui-toolkit/listview/list_view@2x.png "ListView"){:.docs-component-image} ## API #### Props * **autoHideHeader**: bool - Prop defining if ListView header should automatically hide * **data**: array - Prop containing items that will be rendered by the ListView component * **loading**: bool - Prop that defines whether the ListView should render loading spinner (to indicate it's still loading data) or actual items (when the data is successfully loaded) * **onLoadMore()**: function - Called when the ListView is scrolled all the way to the bottom of the first page. - In this function you should update `data` array (state) with additional items * **onRefresh()**: function - Called when the ListView is pulled down, triggering the refresh action. - In this function you should update `data` array (state) with new items - If this function is declared, the Component will be considered refreshable * **getSectionId()**: function - Function that returns the section ID, this ID is used to group the items into a section * **sections**: array - Prop containing an array of individual sections to be rendered, as defined [here](https://facebook.github.io/react-native/docs/sectionlist#sections) * **renderRow(item: Object)**: function - Function that renders each item (row) from `data` * **renderHeader()**: function - Function that renders the Header content * **renderFooter()**: function - Function that renders the Footer content * **renderSectionHeader()**: function - Function that renders the Section Header content * **hasFeaturedItem**: bool - Prop that defines whether or not the ListView being rendered has a featured item * **renderFeaturedItem()**: function - Function that renders - _Note:_ If passing a `sections` prop, `renderFeaturedItem` will be ignored, since you can simply create a `section` containing the featured item along with it's render function. #### Style * **list** - These Props are passed to Style Prop of underlying React-Native `ListView` component * **listContent** - These Props are passed to `contentContainerStyle` Prop of underlying React-Native `ListView` component * **loadMoreSpinner** - These Props are passed to Style Prop of the `Spinner` component that appears during initial content loading * **refreshControl** - These Props are passed to Style Prop of the `RefreshControl` component. - You can also define `refreshControl.tintColor` prop in this Style, which is passed to the `tintColor` prop of the `RefreshControl` component. ## Examples ![ListView example]({{ site.url }}/img/ui-toolkit/listview/listview-example.png "ListView"){:.docs-component-image} ### Simple list example
#### JSX declaration ```JSX constructor(props) { super(props this.renderRow = this.renderRow.bind(this); this.state = { restaurants: [ { "name": "Gaspar Brasserie", "address": "185 Sutter St, San Francisco, CA 94109", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-1.jpg" }, }, { "name": "Chalk Point Kitchen", "address": "527 Broome St, New York, NY 10013", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-2.jpg" }, }, { "name": "Kyoto Amber Upper East", "address": "225 Mulberry St, New York, NY 10012", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-3.jpg" }, }, { "name": "Sushi Academy", "address": "1900 Warner Ave. Unit A Santa Ana, CA", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-4.jpg" }, }, { "name": "Sushibo", "address": "35 Sipes Key, New York, NY 10012", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-5.jpg" }, }, { "name": "Mastergrill", "address": "550 Upton Rue, San Francisco, CA 94109", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-6.jpg" }, } ], } } renderRow(restaurant) { if (!restaurant) { return null; } return ( {restaurant.name} {restaurant.address} ); } render() { const { restaurants } = this.state; return ( ); } ``` ### Rendering a featured item
#### JSX declaration ```JSX constructor(props) { super(props this.renderRow = this.renderRow.bind(this); this.renderFeaturedItem = this.renderFeaturedItem.bind(this); this.state = { restaurants: [ { "name": "Gaspar Brasserie", "address": "185 Sutter St, San Francisco, CA 94109", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-1.jpg" }, }, { "name": "Chalk Point Kitchen", "address": "527 Broome St, New York, NY 10013", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-2.jpg" }, }, { "name": "Kyoto Amber Upper East", "address": "225 Mulberry St, New York, NY 10012", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-3.jpg" }, }, { "name": "Sushi Academy", "address": "1900 Warner Ave. Unit A Santa Ana, CA", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-4.jpg" }, }, { "name": "Sushibo", "address": "35 Sipes Key, New York, NY 10012", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-5.jpg" }, }, { "name": "Mastergrill", "address": "550 Upton Rue, San Francisco, CA 94109", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-6.jpg" }, } ], } } renderFeaturedItem(restaurant) { if (!restaurant) { return null; } return ( {(restaurant.name || '').toUpperCase()} {restaurant.address} ); } renderRow(restaurant) { if (!restaurant) { return null; } return ( {restaurant.name} {restaurant.address} ); } render() { const { restaurants } = this.state; return ( ); } ``` ### Rendering sections example
#### JSX declaration ```JSX constructor(props) { super(props this.renderRow = this.renderRow.bind(this); this.state = { restaurants: [ { "name": "Gaspar Brasserie", "address": "185 Sutter St, San Francisco, CA 94109", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-1.jpg" }, }, { "name": "Chalk Point Kitchen", "address": "527 Broome St, New York, NY 10013", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-2.jpg" }, }, { "name": "Kyoto Amber Upper East", "address": "225 Mulberry St, New York, NY 10012", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-3.jpg" }, }, { "name": "Sushi Academy", "address": "1900 Warner Ave. Unit A Santa Ana, CA", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-4.jpg" }, }, { "name": "Sushibo", "address": "35 Sipes Key, New York, NY 10012", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-5.jpg" }, }, { "name": "Mastergrill", "address": "550 Upton Rue, San Francisco, CA 94109", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-6.jpg" }, } ], } } renderRow(restaurant) { if (!restaurant) { return null; } return ( {restaurant.name} {restaurant.address} ); } render() { const { restaurants } = this.state; const sections = {[ { title: 'New York', data: { restaurants[1], restaurants[2], restaurants[4] }}, { title: 'San Francisco', data: { restaurants[0], restaurants[5] }}, { title: 'Santa Ana', data: { restaurants[3] }}, ]}; return ( ); } ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-06-GridView.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/grid-view title: GridView section: UI toolkit --- # GridView Similar to [ListView]({{ site.url }}/docs/ui-toolkit/components/list-view), `GridView` is used to render Grid of items. ![GridView (GridRow) example]({{ site.url }}/img/ui-toolkit/gridview/grid_view@2x.png "Grid View"){:.docs-component-image} Instead of having a separate `GridView` component, you should use `GridRow` component to encapsulate a single row of items (cells), and then pass the `GridRow` as a normal row to a `ListView` component which does the actual content rendering. The main idea behind this approach is to allow developers to have a variable number of columns in each row, for example, the first row can have only 1 column, followed by a number of rows with 2 columns. ## API #### Props * **columns**: number - Number of columns in the GridRow #### Style names `GridView` has no specific style names. #### Methods * **groupByRows(data: *array*, columns: *number*, getColumnSpan: *function*)** - **data**: *array* containing all items. - **columns**: *number* defining number of columns in grid. - **getColumnSpan**: *function* (optional) returns the column span of a single element. Each element has a span of 1 by default. - returns an array of rows, where each row is an array of data elements. ## Example ![GridView (GridRow) example]({{ site.url }}/img/ui-toolkit/gridview/gridview-example.png "Grid View"){:.docs-component-image} #### JSX declaration ```JSX constructor(props) { super(props); this.renderRow = this.renderRow.bind(this); this.state = { restaurants: [ { "name": "Gaspar Brasserie", "address": "185 Sutter St, San Francisco, CA 94109", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-1.jpg" }, }, { "name": "Chalk Point Kitchen", "address": "527 Broome St, New York, NY 10013", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-2.jpg" }, }, { "name": "Kyoto Amber Upper East", "address": "225 Mulberry St, New York, NY 10012", "image": { "url": "https://shoutem.github.io/static/getting-started/restaurant-3.jpg" }, } ], } } renderRow(rowData, sectionId, index) { // rowData contains grouped data for one row, // so we need to remap it into cells and pass to GridRow if (index === '0') { return ( {rowData[0].name} {rowData[0].address} ); } const cellViews = rowData.map((restaurant, id) => { return ( {restaurant.name} {restaurant.address} ); }); return ( {cellViews} ); } render() { const restaurants = this.state.restaurants; // Group the restaurants into rows with 2 columns, except for the // first restaurant. The first restaurant is treated as a featured restaurant let isFirstArticle = true; const groupedData = GridRow.groupByRows(restaurants, 2, () => { if (isFirstArticle) { isFirstArticle = false; return 2; } return 1; }); return ( ); } ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-07-Cards.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/cards title: Cards section: UI toolkit --- # Cards Cards have become very popular in recent years. They are useful when displaying interactive content composed of different elements. ![Card grid item example]({{ site.url }}/img/ui-toolkit/cards/card@2x.png "Card grid item"){:.docs-component-image} ## API #### Props * `Card` has no specific (custom) Props, however, it supports every prop that the standard React Native `View` component supports. For full list of available props, visit [React Native View component documentation](https://facebook.github.io/react-native/docs/view.html "React Native View component documentation"). #### Style names `Card` has no specific style names, however the `View` component nested under `Card` can use the following Style name: * **content**: adds standard card margins to any child component ## Examples #### Card grid item ![Card grid item example]({{ site.url }}/img/ui-toolkit/cards/card@2x.png "Card grid item"){:.docs-component-image} #### JSX Declaration ```JSX Choosing The Right Boutique Hotel For You 21 hours ago ``` ## Card + Icon ![Card grid item + Icon example]({{ site.url }}/img/ui-toolkit/cards/card-icon@2x.png "Card grid item + Icon"){:.docs-component-image} #### JSX Declaration ```JSX Choosing The Right Boutique Hotel For You 21 hours ago ``` ## Card + Icon (Shop) ![Card grid item + Icon (Shop) example]({{ site.url }}/img/ui-toolkit/cards/card-icon-shop@2x.png "Card grid item + Icon (Shop)"){:.docs-component-image} #### JSX Declaration ```JSX Choosing The Right Boutique Hotel For You $99.99 $120.00 ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-08-Dividers.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/dividers title: Dividers section: UI toolkit --- # Dividers Dividers are components used to add space or any other separator between other components. ![Divider example]({{ site.url }}/img/ui-toolkit/dividers/divider@2x.png "Divider"){:.docs-component-image} ## API #### Props `Divider` has no specific (custom) Props, however, it supports every prop that the standard React Native `View` component supports. For full list of available props, visit [React Native View component documentation](https://facebook.github.io/react-native/docs/view.html "React Native View component documentation"). #### Style names * **line**: defines a line `Divider` (thin line), instead of standard (rectangle) `Divider` * **small**: can be used only with `line` style name, and sets the width to 55 px * **center**: can be used only with `line` style name, and centers the line within parent container * **section-header**: applies different styling with additional vertical border (as defined in Theme) ## Examples ### Divider ![Divider example]({{ site.url }}/img/ui-toolkit/dividers/divider@2x.png "Divider"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Line divider ![Line divider example]({{ site.url }}/img/ui-toolkit/dividers/line-divider@2x.png "Line divider"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Section divider Section dividers are usually used in lists to separate groups of similar list items, for example to group contacts by the first letter of their name. ListView will automatically style all dividers added to it. ![Section divider example]({{ site.url }}/img/ui-toolkit/dividers/section-divider@2x.png "Section divider"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Section divider + Label ![Section divider + Label example]({{ site.url }}/img/ui-toolkit/dividers/section-divider-label@2x.png "Section divider + Label"){:.docs-component-image} #### JSX Declaration ```JSX INFORMATION ``` ### Section divider + Label + Caption ![Section divider + Label + Caption example]({{ site.url }}/img/ui-toolkit/dividers/section-divider-lavel-caption@2x.png "Section divider + Label + Caption"){:.docs-component-image} #### JSX Declaration ```JSX PRODUCT NAME PRICE ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-09-Rows.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/rows title: Rows section: UI toolkit --- # Rows `Row` is a container that renders its children horizontally. ![Small list item + Icon + Description Row example]({{ site.url }}/img/ui-toolkit/rows/small-list-item-icon-description@2x.png "Small list item + Icon + Description"){:.docs-component-image} ## API #### Props `Row` has no specific (custom) Props, however, it supports every prop that the standard React Native `View` component supports. For full list of available props, visit [React Native View component documentation](https://facebook.github.io/react-native/docs/view.html "React Native View component documentation"). #### Style names * **small**: sets the fixed height of Row to 65px * Nested components can also use these Style names: * **disclosure**: applicable only for `Icon` components within `Row`. Pulls the icon to the right, and sets opacity to 50% * **notification-dot**: applicable only for `View` components within `Row`. Pulls the notification dot to the left of the content * **right-icon**: applicable only for `Button` components within `Row` * **top**: applicable to any component nested within `Row`. Positions the component with `top` style to the beginning of `Row` component * **vertical**: applicable only for `View` components within `Row`. Adds a bottom margin below each `View` in `Row` ## Examples ### Small list item ![Small list item Row example]({{ site.url }}/img/ui-toolkit/rows/small-list-item@2x.png "Small list item"){:.docs-component-image} #### JSX Declaration ```JSX Portland ugh fashion axe Helvetica, YOLO Echo Party ``` ### Small list item + Avatar thumbnail ![Small list item + Avatar thumbnail Row example]({{ site.url }}/img/ui-toolkit/rows/small-list-item-avatar@2x.png "Small list item + Avatar thumbnail"){:.docs-component-image} #### JSX Declaration ```JSX Add comment ``` ### Small list item + Icon ![Small list item + Icon Row example]({{ site.url }}/img/ui-toolkit/rows/small-list-item-icon@2x.png "Small list item + Icon"){:.docs-component-image} #### JSX Declaration ```JSX Add to favorites ``` ### Small list item + Icon + Right Arrow ![Small list item + Icon + Right Arrow Row example]({{ site.url }}/img/ui-toolkit/rows/small-list-item-icon-right-arrow@2x.png "Small list item + Icon + Right Arrow"){:.docs-component-image} #### JSX Declaration ```JSX About ``` ### Small list item + Icon + Description ![Small list item + Icon + Description Row example]({{ site.url }}/img/ui-toolkit/rows/small-list-item-icon-description@2x.png "Small list item + Icon + Description"){:.docs-component-image} #### JSX Declaration ```JSX Bridges Rock Gym www.example.com/deal/link/that-is-really-long ``` ### Small list item + Avatar + Description + Caption ![Small list item + Avatar + Description + Caption Row example]({{ site.url }}/img/ui-toolkit/rows/small-list-item-icon-description-caption@2x.png "Small list item + Avatar + Description + Caption"){:.docs-component-image} #### JSX Declaration ```JSX Dustin Malone 20 minutes ago Banjo tote bag bicycle rights, High Life sartorial cray craft beer whatever street art fap. Hashtag typewriter banh mi, squid keffiyeh High. ``` ### Medium list item ![Medium list item Row example]({{ site.url }}/img/ui-toolkit/rows/medium-list-item@2x.png "Medium list item"){:.docs-component-image} #### JSX Declaration ```JSX Portland ugh fashion axe Helvetica, YOLO Echo Park Austin gastropub roof party. ``` ### Medium list item + Description ![Medium list item + Description Row example]({{ site.url }}/img/ui-toolkit/rows/medium-list-item-description@2x.png "Medium list item + Description"){:.docs-component-image} #### JSX Declaration ```JSX Fact Check: Wisconsin Music, Film & Photography Debate 20 hours ago ``` ### Medium list item + Description + Icon ![Medium list item + Description + Icon Row example]({{ site.url }}/img/ui-toolkit/rows/medium-list-item-description-icon@2x.png "Medium list item + Description + Icon"){:.docs-component-image} #### JSX Declaration ```JSX Wilco Cover David Bowie's "Space Oddity" June 21 · 20:00 ``` ### Medium list item + Description + Icon + Label ![Medium list item + Description + Icon + Label Row example]({{ site.url }}/img/ui-toolkit/rows/medium-list-item-description-icon-label@2x.png "Medium list item + Description + Icon + Label"){:.docs-component-image} #### JSX Declaration ```JSX Family Safari Vacation To The Home Of The Gods $120.00 $150.00 ``` ### Medium list item + Notification dot ![Medium list item + Notification dot Row example]({{ site.url }}/img/ui-toolkit/rows/medium-list-item-notification-icon@2x.png "Medium list item + Notification dot"){:.docs-component-image} #### JSX Declaration ```JSX Fact Check: Wisconsin Music, Film & Photography Debate 20 hours ago ``` ### Medium list item + Description + Label ![Medium list item + Description + Label Row example]({{ site.url }}/img/ui-toolkit/rows/medium-list-item-description-label@2x.png "Medium list item + Description + Label"){:.docs-component-image} #### JSX Declaration ```JSX Take A Romantic Break In A Boutique Hotel 3 days ago 12:16 ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-10-Tiles.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/tiles title: Tiles section: UI toolkit --- # Tiles Tiles are a convenient way to display homogeneous content. They are often used in grid views. Children are rendered vertically, one below the other. ![Tile example]({{ site.url }}/img/ui-toolkit/tiles/tile@2x.png "Tile"){:.docs-component-image} ## API #### Props `Tile` has no specific (custom) Props, however, it supports every prop that the standard React Native `View` component supports. For full list of available props, visit [React Native View component documentation](https://facebook.github.io/react-native/docs/view.html "React Native View component documentation"). #### Style names * **clear**: sets the `clear` background color, as defined in Theme (usually transparent) * **small**: sets the fixed width to 145px and reduces margins and paddings * **text-centric**: centers content within container and applies standard margins * Nested `View` component can use the following Style name: * **content**: applies the standard margins around the content in `View` ## Examples ### Tile ![Tile example]({{ site.url }}/img/ui-toolkit/tiles/tile@2x.png "Tile"){:.docs-component-image} #### JSX Declaration ```JSX When The Morning Dawns - DJ Silver Sample Album 20 hours ago ``` ### Tile + Play Icon ![Tile + Icon example]({{ site.url }}/img/ui-toolkit/tiles/tile-icon@2x.png "Tile + Icon"){:.docs-component-image} #### JSX Declaration ```JSX When The Morning Dawns - DJ Silver Sample Album 20 hours ago ``` ### Large (featured) Tile ![Large (featured) Tile example]({{ site.url }}/img/ui-toolkit/tiles/large-tile@2x.png "Large Tile"){:.docs-component-image} #### JSX Declaration ```JSX MIKE PATTON TEAMING WITH JOHN KAADA FOR COLLAB ALBUM BACTERIA CULT Sophia Jackson 21 hours ago ``` ### Large (featured) tile + Button + Sale tag ![Large (featured) tile + Button + Sale tag Tile example]({{ site.url }}/img/ui-toolkit/tiles/large-tile-button-sale-tag@2x.png "Large Tile + Button + Sale tag"){:.docs-component-image} #### JSX Declaration ```JSX -20% COOL BLACK AND WHITE STYLISH WATCHES $280.00 $250.00 ``` ### Large (featured) tile + Button ![Large (featured) tile + Button Tile example]({{ site.url }}/img/ui-toolkit/tiles/large-tile-button@2x.png "Large Tile + Button"){:.docs-component-image} #### JSX Declaration ```JSX MIKE PATTON TEAMING WITH JOHN KAADA 150.00 99.99 ``` ### Large list item ![Large list item Tile example]({{ site.url }}/img/ui-toolkit/tiles/large-list-item@2x.png "Large list item"){:.docs-component-image} #### JSX Declaration ```JSX MAUI BY AIR THE BEST WAY AROUND THE ISLAND 1 hour ago 15:34 ``` ### Large list item + Icon + Timestamp ![Large list item + Icon + Timestamp Tile example]({{ site.url }}/img/ui-toolkit/tiles/large-list-item-icon-timestamp@2x.png "Large list item + icon + timestamp"){:.docs-component-image} #### JSX Declaration ```JSX MAUI BY AIR THE BEST WAY AROUND THE ISLAND 1 hour ago 15:34 ``` ### Large list item + Price tag ![Large list item + Price tag Tile example]({{ site.url }}/img/ui-toolkit/tiles/large-list-item-price-tag@2x.png "Large list item + Price tag"){:.docs-component-image} #### JSX Declaration ```JSX SMOKED SALMON, CLASSIC CONDIMENTS, BRIOCHE $18.30 ``` ### Large list item + Action icon ![Large list item + Action icon Tile example]({{ site.url }}/img/ui-toolkit/tiles/large-list-item-action-icon@2x.png "Large list item + Action icon"){:.docs-component-image} #### JSX Declaration ```JSX HOW TO MAINTAIN YOUR MENTAL HEALTH IN 2016 6557 Americo Hills Apt. 118 ``` ### Detail square + Price tag ![Detail square + Price tag Tile example]({{ site.url }}/img/ui-toolkit/tiles/detail-square-price-tag@2x.png "Detail square + Price tag"){:.docs-component-image} #### JSX Declaration ```JSX SMOKED SALMON, CLASSIC CONDIMENTS, BRIOCHE $18.30 ``` ### Detail square + Button ![Detail square + Button Tile example]({{ site.url }}/img/ui-toolkit/tiles/detail-square-button@2x.png "Detail square + Button"){:.docs-component-image} #### JSX Declaration ```JSX MIKE PATTON TEAMING WITH JOHN KAADA 150.00 99.99 ``` ### Detail square + Button + Sale tag ![Detail square + Button + Sale tag Tile example]({{ site.url }}/img/ui-toolkit/tiles/detail-square-button-sale-tag@2x.png "Detail square + Button + Sale tag"){:.docs-component-image} #### JSX Declaration ```JSX -20% COOL BLACK AND WHITE STYLISH WATCHES $280.00 $250.00 ``` ### Detail large + Price tag ![Detail large + Price tag Tile example]({{ site.url }}/img/ui-toolkit/tiles/detail-large-price-tag@2x.png "Detail large + Price tag"){:.docs-component-image} #### JSX Declaration ```JSX SMOKED SALMON, CLASSIC CONDIMENTS, BRIOCHE $18.30 ``` ### Detail large + Button ![Detail large + Button Tile example]({{ site.url }}/img/ui-toolkit/tiles/detail-large-button@2x.png "Detail large + Button"){:.docs-component-image} #### JSX Declaration ```JSX MIKE PATTON TEAMING WITH JOHN KAADA 150.00 99.99 ``` ### Detail large + Button + Sale tag ![Detail large + Button + Sale tag Tile example]({{ site.url }}/img/ui-toolkit/tiles/detail-large-button-sale-tag@2x.png "Detail large + Button + Sale tag"){:.docs-component-image} #### JSX Declaration ```JSX -20% COOL BLACK AND WHITE STYLISH WATCHES $280.00 $250.00 ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-11-Spinner.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/spinner title: Spinner section: UI toolkit --- # Spinner `Spinner` is a styled wrapper for `ActivityIndicator` React Native component. ![Spinner example]({{ site.url }}/img/ui-toolkit/spinner/spinner@2x.png "Spinner"){:.docs-component-image} ## API #### Props `Spinner` has no specific (custom) Props. #### Style names `Spinner` has no specific style names. #### Style * **color**: color: - defines foreground color of the spinner, defaults to `#cccccc` * **size**: string, `small` or `large` - defines the size of the indicator, defaults to `small` * Also, supports every `Style` prop that the standard React Native `View` component supports ## Example ![Spinner example]({{ site.url }}/img/ui-toolkit/spinner/spinner@2x.png "Spinner"){:.docs-component-image} #### JSX Declaration ```JSX ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-12-Buttons.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/buttons title: Buttons section: UI toolkit --- # Buttons Buttons are additionally styled [TouchableOpacity]({{ site.url }}/docs/ui-toolkit/components/touchable-opacity) components. ## API #### Props `Button` has no specific (custom) Props, however, it supports every prop that the standard React Native `TouchableOpacity` component supports. For full list of available props, visit [React Native TouchableOpacity component documentation](https://facebook.github.io/react-native/docs/touchableopacity.html "React Native TouchableOpacity component documentation"). #### Style names * **action**: increases `fontSize` for `Text` component within `Button` * **border**: applies `Border` color as defined in Theme * **clear**: removes the border around `Button` and sets `backgroundColor` to `Clear` color as defined in Theme * **confirmation**: sets the border around `Button` and applies a medium margin around * **secondary**: sets the text color to `Light` as defined in Theme, and background color to `Darker` as defined in Theme * **full-width**: `Button` stretches to full width of the container * **muted**: sets the opacity of the `Icon` and `Text` components within `Button`to 50% * **stacked**: vertically stacks `Icon` and `Text` within `Button` * **tight**: removes the right margin from `Icon` and `Text` within `Button` #### Style * **underlayColor**: the color that will show through when the `Button` is pressed * Also, supports every `Style` prop that the standard React Native `View` component supports ## Examples ### Button / Text only / Light ![Button / Text only / Light example]({{ site.url }}/img/ui-toolkit/buttons/button-text-only-light@2x.png "Button / Text only / Light"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Button / Text only / Dark ![Button / Text only / Dark example]({{ site.url }}/img/ui-toolkit/buttons/button-text-only-dark@2x.png "Button / Text only / Dark"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Button / Icon + Text / Light ![Button / Icon + Text / Light example]({{ site.url }}/img/ui-toolkit/buttons/button-icon-text-light@2x.png "Button / Icon + Text / Light"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Button / Icon + Text / Dark ![Button / Icon + Text / Dark example]({{ site.url }}/img/ui-toolkit/buttons/button-icon-text-dark@2x.png "Button / Icon + Text / Dark"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Button / Fixed size ![Button / Fixed size example]({{ site.url }}/img/ui-toolkit/buttons/button-fixed-size@2x.png "Button / Fixed size"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Button / Full width ![Button / Full width example]({{ site.url }}/img/ui-toolkit/buttons/button-full-width@2x.png "Button / Full width"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Icon Button ![Button / Navbar example]({{ site.url }}/img/ui-toolkit/buttons/button-navbar@2x.png "Button / Navbar"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Button / Vertical / Icon + Text ![Button / Vertical / Icon + Text example]({{ site.url }}/img/ui-toolkit/buttons/button-vertical-icon-text@2x.png "Button / Vertical / Icon + Text"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Button / Full width - Muted ![Button / Full width - Normal example]({{ site.url }}/img/ui-toolkit/buttons/button-full-width-normal@2x.png "Button / Full width - Normal"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Button / Full width - Active ![Button / Full width - Active example]({{ site.url }}/img/ui-toolkit/buttons/button-full-width-active@2x.png "Button / Full width - Active (Feed)"){:.docs-component-image} #### JSX Declaration ```JSX ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-13-Image.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/image title: Image section: UI toolkit --- # Image This document covers `Image` component and Image size style names available in Shoutem UI toolkit. ![List large thumbnail (375x200) example]({{ site.url }}/img/ui-toolkit/image-sizes/list-large-thumbnail-375-x-200@2x.png "List large thumbnail (375x200)"){:.docs-component-image} ## API #### Props `Image` has no specific (custom) Props, however, it supports every prop that the standard React Native `Image` component supports. For full list of available props, visit [React Native Image component documentation](https://facebook.github.io/react-native/docs/image.html "React Native Image component documentation"). #### Style names For most of available `Image` style names, Image dimensions are scaled depending on screen dimensions. As example, `Image` with `featured` style name applied will have dimensions of `365x345px (width, height respectively)` on device with screen width of `375px`. If device's screen width is larger, then the image dimensions will be larger than 365x345px and similar, if device screen width is smaller than 375px, actual image will be smaller than 365x345px. * **featured**: width: `(365 / 375) * window.width` height: `(345 / 375) * window.width` * **large**: width: `window.width` height: `(280 / 375) * window.width` * **large-portrait**: width: `window.width` height: `(280 / 375) * window.width` * **large-banner**: width: `window.width` height: `(200 / 375) * window.width` * **large-square**: width: `window.width` height: `window.width` * **large-wide**: width: `window.width` height: `(238 / 375) * window.width` * **large-ultra-wide**: width: `window.width` height: `(130 / 375) * window.width` * **medium-avatar**: rounded image, 72.5px radius * **medium**: width: `145px` x height: `92px` * **medium-wide**: width: `(180/375) * window.width` height: `85px` * **medium-square**: width: `145px` height: `145px` * **medium-portrait**: width: `(209/375)` height: `139px` * **rounded-corners**: defines rounded corners, as defined in Theme * **small-avatar**: rounded image, 25px radius * **small**: width: `65px` height: `65px` #### Style * Supports every `Style` prop that the standard React Native `Image` component supports ## Examples ### List image thumbnail (65x65) ![List image thumbnail (65x65) example]({{ site.url }}/img/ui-toolkit/image-sizes/list-image-thumbnail-65-x-65@2x.png "List image thumbnail (65x65)"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### List video thumbnail (145x92) ![List video thumbnail (145x92) example]({{ site.url }}/img/ui-toolkit/image-sizes/list-video-thumbnail-145-x-92@2x.png "List video thumbnail (145x92)"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Card image thumbnail (180x85) ![Card image thumbnail (180x85) example]({{ site.url }}/img/ui-toolkit/image-sizes/card-image-thumbnail-180-85@2x.png "Card image thumbnail (180x85)"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Avatar thumbnail (25x25) ![Avatar thumbnail (25x25) example]({{ site.url }}/img/ui-toolkit/image-sizes/avatar-thumbnail-25-x-25@2x.png "Avatar thumbnail (25x25)"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Avatar image (145x145) ![Avatar image (145x145) example]({{ site.url }}/img/ui-toolkit/image-sizes/avatar-image-145-x-145@2x.png "Avatar image (145x145)"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### List medium image (145x145) ![List medium image (145x145) example]({{ site.url }}/img/ui-toolkit/image-sizes/list-medium-image-145-x-145@2x.png "List medium image (145x145)"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### List large thumbnail (375x200) ![List large thumbnail (375x200) example]({{ site.url }}/img/ui-toolkit/image-sizes/list-large-thumbnail-375-x-200@2x.png "List large thumbnail (375x200)"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Featured image (365x345) ![Featured image (365x345) example]({{ site.url }}/img/ui-toolkit/image-sizes/featured-image-365-345@2x.png "Featured image (365x345)"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Detail large photo (375x518) ![Detail large photo (375x518) example]({{ site.url }}/img/ui-toolkit/image-sizes/detail-large-photo-375-581@2x.png "Detail large photo (375x518)"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Detail medium photo (375x280) ![Detail medium photo (375x280) example]({{ site.url }}/img/ui-toolkit/image-sizes/detail-medium-photo-375-280@2x.png "Detail medium photo (375x280)"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Detail wide photo (375x238) ![Detail wide photo (375x238) example]({{ site.url }}/img/ui-toolkit/image-sizes/detail-wide-photo-375-238@2x.png "Detail wide photo (375x238)"){:.docs-component-image} #### JSX Declaration ```JSX ``` ### Detail square photo (375x375) ![Detail square photo (375x375) example]({{ site.url }}/img/ui-toolkit/image-sizes/detail-square-photo-375-375@2x.png "Detail square photo (375x375)"){:.docs-component-image} #### JSX Declaration ```JSX ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-14-Icons.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/icons title: Icons section: UI toolkit --- # Icons This document covers available icons in the UI toolkit. You can see their implementation in the UI toolkit [here](https://github.com/shoutem/ui/blob/develop/components/Icon/config.json). ### API #### Props `Icon` has no specific (custom) Props. ### Style names The `styleName`s for icons are the `name`s used in the examples below. ### Examples #### Sidebar ![Sidebar icon example]({{ site.url }}/img/ui-toolkit/icons/sidebar.png "Sidebar"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Back ![Back icon example]({{ site.url }}/img/ui-toolkit/icons/back.png "Back"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Close ![Close icon example]({{ site.url }}/img/ui-toolkit/icons/close.png "Close"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Left-arrow ![Left-arrow icon example]({{ site.url }}/img/ui-toolkit/icons/left-arrow.png "Left-arrow"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Right-arrow ![Right-arrow icon example]({{ site.url }}/img/ui-toolkit/icons/right-arrow.png "Right-arrow"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Up-arrow ![Up-arrow icon example]({{ site.url }}/img/ui-toolkit/icons/up-arrow.png "Up-arrow"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Down-arrow ![Down-arrow icon example]({{ site.url }}/img/ui-toolkit/icons/down-arrow.png "Down-arrow"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Drop-down ![Drop-down icon example]({{ site.url }}/img/ui-toolkit/icons/drop-down.png "Drop-down"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Share ![Share icon example]({{ site.url }}/img/ui-toolkit/icons/share.png "Share"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Share-android ![Share-android icon example]({{ site.url }}/img/ui-toolkit/icons/share-android.png "Share-android"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Add-to-favorites-off ![Add-to-favorites-off icon example]({{ site.url }}/img/ui-toolkit/icons/add-to-favorites-off.png "Add-to-favorites-off"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Add-to-favorites-on ![Add-to-favorites-on icon example]({{ site.url }}/img/ui-toolkit/icons/add-to-favorites-on.png "Add-to-favorites-on"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Play ![Play icon example]({{ site.url }}/img/ui-toolkit/icons/play.png "Play"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Pause ![Pause icon example]({{ site.url }}/img/ui-toolkit/icons/pause.png "Pause"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Edit ![Edit icon example]({{ site.url }}/img/ui-toolkit/icons/edit.png "Edit"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Refresh ![Refresh icon example]({{ site.url }}/img/ui-toolkit/icons/refresh.png "Refresh"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Web ![Web icon example]({{ site.url }}/img/ui-toolkit/icons/web.png "Web"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Email ![Email icon example]({{ site.url }}/img/ui-toolkit/icons/email.png "Email"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Pin ![Pin icon example]({{ site.url }}/img/ui-toolkit/icons/pin.png "Pin"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Address ![Address icon example]({{ site.url }}/img/ui-toolkit/icons/address.png "Address"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Facebook ![Facebook icon example]({{ site.url }}/img/ui-toolkit/icons/facebook.png "Facebook"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Linkedin ![Linkedin icon example]({{ site.url }}/img/ui-toolkit/icons/linkedin.png "Linkedin"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Tweet ![Tweet icon example]({{ site.url }}/img/ui-toolkit/icons/tweet.png "Tweet"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Cart ![Cart icon example]({{ site.url }}/img/ui-toolkit/icons/cart.png "Cart"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Add-to-cart ![Add-to-cart icon example]({{ site.url }}/img/ui-toolkit/icons/add-to-cart.png "Add-to-cart"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Add-event ![Add-event icon example]({{ site.url }}/img/ui-toolkit/icons/add-event.png "Add-event"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Comment ![Comment icon example]({{ site.url }}/img/ui-toolkit/icons/comment.png "Comment"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Call ![Call icon example]({{ site.url }}/img/ui-toolkit/icons/call.png "Call"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Activity ![Activity icon example]({{ site.url }}/img/ui-toolkit/icons/activity.png "Activity"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Friends ![Friends icon example]({{ site.url }}/img/ui-toolkit/icons/friends.png "Friends"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Add-friend ![Add-friend icon example]({{ site.url }}/img/ui-toolkit/icons/add-friend.png "Add-friend"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Unfriend ![Unfriend icon example]({{ site.url }}/img/ui-toolkit/icons/unfriend.png "Unfriend"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Settings ![Settings icon example]({{ site.url }}/img/ui-toolkit/icons/settings.png "Settings"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Take-a-photo ![Take-a-photo icon example]({{ site.url }}/img/ui-toolkit/icons/take-a-photo.png "Take-a-photo"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Error ![Error icon example]({{ site.url }}/img/ui-toolkit/icons/error.png "Error"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### News ![News icon example]({{ site.url }}/img/ui-toolkit/icons/news.png "News"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Like ![Like icon example]({{ site.url }}/img/ui-toolkit/icons/like.png "Like"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Search ![Search icon example]({{ site.url }}/img/ui-toolkit/icons/search.png "Search"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Users ![Users icon example]({{ site.url }}/img/ui-toolkit/icons/users.png "Users"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### User-profile ![User-profile icon example]({{ site.url }}/img/ui-toolkit/icons/user-profile.png "User-profile"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Social-wall ![Social-wall icon example]({{ site.url }}/img/ui-toolkit/icons/social-wall.png "Social-wall"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Books ![Books icon example]({{ site.url }}/img/ui-toolkit/icons/books.png "Books"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Folder ![Folder icon example]({{ site.url }}/img/ui-toolkit/icons/folder.png "Folder"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Events ![Events icon example]({{ site.url }}/img/ui-toolkit/icons/events.png "Events"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Photo ![Photo icon example]({{ site.url }}/img/ui-toolkit/icons/photo.png "Photo"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Music-video ![Music-video icon example]({{ site.url }}/img/ui-toolkit/icons/music-video.png "Music-video"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Radio ![Radio icon example]({{ site.url }}/img/ui-toolkit/icons/radio.png "Radio"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Podcasts ![Podcasts icon example]({{ site.url }}/img/ui-toolkit/icons/podcasts.png "Podcasts"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### About ![About icon example]({{ site.url }}/img/ui-toolkit/icons/about.png "About"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Notifications ![Notifications icon example]({{ site.url }}/img/ui-toolkit/icons/notifications.png "Notifications"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Exit-to-app ![Exit-to-app icon example]({{ site.url }}/img/ui-toolkit/icons/exit-to-app.png "Exit-to-app"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Restaurant-menu ![Restaurant-menu icon example]({{ site.url }}/img/ui-toolkit/icons/restaurant-menu.png "Restaurant-menu"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Products ![Products icon example]({{ site.url }}/img/ui-toolkit/icons/products.png "Products"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Deals ![Deals icon example]({{ site.url }}/img/ui-toolkit/icons/deals.png "Deals"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Restaurant ![Restaurant icon example]({{ site.url }}/img/ui-toolkit/icons/restaurant.png "Restaurant"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### More-horizontal ![More-horizontal icon example]({{ site.url }}/img/ui-toolkit/icons/more-horizontal.png "More-horizontal"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Rss-feed ![Rss-feed icon example]({{ site.url }}/img/ui-toolkit/icons/rss-feed.png "Rss-feed"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Missing ![Missing icon example]({{ site.url }}/img/ui-toolkit/icons/missing.png "Missing"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Home ![Home icon example]({{ site.url }}/img/ui-toolkit/icons/home.png "Home"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Checkbox-on ![Checkbox-on icon example]({{ site.url }}/img/ui-toolkit/icons/checkbox-on.png "Checkbox-on"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Checkbox-off ![Checkbox-off icon example]({{ site.url }}/img/ui-toolkit/icons/checkbox-off.png "Checkbox-off"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Radiobutton-on ![Radiobutton-on icon example]({{ site.url }}/img/ui-toolkit/icons/radiobutton-on.png "Radiobutton-on"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Radiobutton-off ![Radiobutton-off icon example]({{ site.url }}/img/ui-toolkit/icons/radiobutton-off.png "Radiobutton-off"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Minus-button ![Minus-button icon example]({{ site.url }}/img/ui-toolkit/icons/minus-button.png "Minus-button"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Plus-button ![Plus-button icon example]({{ site.url }}/img/ui-toolkit/icons/plus-button.png "Plus-button"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Clear-text ![Clear-text icon example]({{ site.url }}/img/ui-toolkit/icons/clear-text.png "Clear-text"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Receipt ![Receipt icon example]({{ site.url }}/img/ui-toolkit/icons/receipt.png "Receipt"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### History ![History icon example]({{ site.url }}/img/ui-toolkit/icons/history.png "History"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Gift ![Gift icon example]({{ site.url }}/img/ui-toolkit/icons/gift.png "Gift"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Loyalty-card ![Loyalty-card icon example]({{ site.url }}/img/ui-toolkit/icons/loyalty-card.png "Loyalty-card"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Trophy ![Trophy icon example]({{ site.url }}/img/ui-toolkit/icons/trophy.png "Trophy"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Lock ![Lock icon example]({{ site.url }}/img/ui-toolkit/icons/lock.png "Lock"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Stamp ![Stamp icon example]({{ site.url }}/img/ui-toolkit/icons/stamp.png "Stamp"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Turn-off ![Turn-off icon example]({{ site.url }}/img/ui-toolkit/icons/turn-off.png "Turn-off"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Stop ![Stop icon example]({{ site.url }}/img/ui-toolkit/icons/stop.png "Stop"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Equalizer ![Equalizer icon example]({{ site.url }}/img/ui-toolkit/icons/equalizer.png "Equalizer"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Page ![Page icon example]({{ site.url }}/img/ui-toolkit/icons/page.png "Page"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Rsvp ![Rsvp icon example]({{ site.url }}/img/ui-toolkit/icons/rsvp.png "Rsvp"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Github ![Github icon example]({{ site.url }}/img/ui-toolkit/icons/github.png "Github"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Link ![Link icon example]({{ site.url }}/img/ui-toolkit/icons/link.png "Link"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### My-location ![My-location icon example]({{ site.url }}/img/ui-toolkit/icons/my-location.png "My-location"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Laptop ![Laptop icon example]({{ site.url }}/img/ui-toolkit/icons/laptop.png "Laptop"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Directions ![Directions icon example]({{ site.url }}/img/ui-toolkit/icons/directions.png "Directions"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Maps ![Maps icon example]({{ site.url }}/img/ui-toolkit/icons/maps.png "Maps"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Uber ![Uber icon example]({{ site.url }}/img/ui-toolkit/icons/uber.png "Uber"){:.docs-component-image} #### JSX Declaration ```JSX ``` #### Instagram ![Instagram icon example]({{ site.url }}/img/ui-toolkit/icons/instagram.png "Instagram"){:.docs-component-image} #### JSX Declaration ```JSX ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-15-View.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/view title: View section: UI toolkit --- # View View is a React Native's `View` with additional (flexbox) styling styleNames available. ## API #### Props `View` has no specific (custom) Props, however, it supports every prop that the standard React Native `View` component supports. For full list of available props, visit [React Native View component documentation](https://facebook.github.io/react-native/docs/view.html "React Native View component documentation"). #### Style names * **fill-parent**: `View` becomes absolutely positioned and takes all available space of its parent container * **horizontal**: Places all items in a row * **h-center**: Centers item in a row horizontally * **h-start**: Places item to the horizontal start of row * **h-end**: Places item to the horizontal end of row * **v-center**: Centers all items in a row vertically * **v-start**: Places item to the vertical start of row * **v-end**: Places item to the vertical end of row * **overlay**: Applies `Overlay` background color (as defined in Theme) to `View` * **space-between**: Places all flex items equally along the main axis * **vertical**: Places all items in a column * **h-center**: Centers all items in a column horizontally * **h-start**: Places item to the horizontal start of column * **h-end**: Places item to the horizontal end of column * **v-center**: Centers all items in a column vertically * **v-start**: Places item to the vertical start of column * **v-end**: Places item to the vertical end of column * **wrap**: Defines whether the flexible items should wrap #### Style * Supports every `Style` prop that the standard React Native `View` component supports ## Examples ### View
#### JSX Declaration ```JSX {...} ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-16-Screen.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/screen title: Screen section: UI toolkit --- # Screen Screen is a React Native's `View` with additional background color defined in Theme (defaults to gray). ## API #### Props `Screen` has no specific (custom) Props, however, it supports every prop that the standard React Native `View` component supports. For full list of available props, visit [React Native View component documentation](https://facebook.github.io/react-native/docs/view.html "React Native View component documentation"). #### Style names * **full-screen**: Applies negative top margin, so that the content within `Screen` covers the navigation bar (usually 70 pixels) * **paper**: Applies a `Light` color, as defined in Theme #### Style * Supports every `Style` prop that the standard React Native `View` component supports ## Examples ## Screen
#### JSX Declaration ```JSX {...} ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-17-TouchableOpacity.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/touchable-opacity title: Touchable Opacity section: UI toolkit --- # TouchableOpacity TouchableOpacity is a React Native's `TouchableOpacity` with additional styling applied through Theme. Basically, `TouchableOpacity` is React-Native's [component](https://facebook.github.io/react-native/docs/touchableopacity.html "React Native TouchableOpacity component documentation") that responds to touches. Once the component is pressed, the opacity of the component within `TouchableOpacity` is decreased, dimming it. ## API #### Props * Supports every `Style` prop that the standard React Native `TouchableOpacity` component supports ### Style names `TouchableOpacity` has no specific style names. #### Style * `TouchableOpacity` has the same Style props like React Native's `TouchableOpacity` component has (`activeOpacity`) * _Note that `activeOpacity` can also be set through Theme, and it defaults to 0.8._ ## Examples ### TouchableOpacity
#### JSX Declaration ```JSX {...} ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-18-Headers.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/headers title: Headers section: UI toolkit --- # Headers Headers are Tile variations - Headers do not have an `Image` as the parent component of the `Tile` component. ## API See [Tiles]({{ site.url }}/docs/ui-toolkit/components/tiles) for API reference. ## Examples ### Header / Article ![Header / Article example]({{ site.url }}/img/ui-toolkit/headers/header-article@2x.png "Header / article"){:.docs-component-image} #### JSX Declaration ```JSX MIKE PATTON TEAMING WITH JOHN KAADA FOR COLLAB ALBUM BACTERIA CULT Sophia Jackson 2 hours ago ``` ### Header / Shop item ![Header / Shop item example]({{ site.url }}/img/ui-toolkit/headers/header-shop-item@2x.png "Header / shop item"){:.docs-component-image} #### JSX Declaration ```JSX -20% COOL BLACK AND WHITE STYLISH WATCHES $280.00 $250.00 ``` ### Header / Deals item ![Header / Deals item example]({{ site.url }}/img/ui-toolkit/headers/header-deals-item@2x.png "Large Tile + Button"){:.docs-component-image} #### JSX Declaration ```JSX MIKE PATTON TEAMING WITH JOHN KAADA $150.00 $99.99 ``` ### Header / Products item ![Header / Products item example]({{ site.url }}/img/ui-toolkit/headers/header-products-item@2x.png "Header / products item"){:.docs-component-image} #### JSX Declaration ```JSX SMOKED SALMON, CLASSIC CONDIMENTS, BRIOCHE $18.30 ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-19-Overlay.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/overlay title: Overlay section: UI toolkit --- # Overlay `Overlay` provides a convenient way to place content over `Image`, through semi-transparent background. ![Solid (bright) overlay example]({{ site.url }}/img/ui-toolkit/tiles/large-list-item-price-tag@2x.png "Solid (bright) overlay example"){:.docs-component-image} ## API #### Props `Overlay` has no specific (custom) Props, however, it supports every prop that the standard React Native `View` component supports. For full list of available props, visit [React Native View component documentation](https://facebook.github.io/react-native/docs/view.html "React Native View component documentation"). #### Style names * if style name isn't given, then the background color (as defined in Theme) is applied below nested content * **fill-parent**: sets the Overlay to fully fill the parent container (without any margins, padding etc.) * **rounded-small**: sets the Overlay to be rounded, with fixed width and height of 38x38 px #### Style * Supports every `Style` prop that the standard React Native `View` component supports ## Example ### Simple overlay

#### JSX Declaration ```JSX SUSHI ACADEMY 1900 Warner Ave. Unit A Santa Ana, CA ``` ### Fill-parent image overlay

#### JSX Declaration ```JSX GASPAR BRASSERIE ``` ### Rounded-small overlay

#### JSX Declaration ```JSX constructor(props) { super(props); this.state = { photos: [ { "source": { "uri": "https://shoutem.github.io/static/getting-started/restaurant-1.jpg" } }, { "source": { "uri": "https://shoutem.github.io/static/getting-started/restaurant-2.jpg" } }, { "source": { "uri": "https://shoutem.github.io/static/getting-started/restaurant-3.jpg" } } ] } } render() { return ( Map } /> MAUI BY AIR THE BEST WAY AROUND THE ISLAND 1 hour ago 15:34 ); } ``` ================================================ FILE: docs/ui-toolkit/components/_posts/1970-01-20-Video.md ================================================ --- layout: doc permalink: /docs/ui-toolkit/components/video title: Video section: UI toolkit --- # Video `Video` component can be used to render all types of video items. It renders a Video based on the source type. If the source is `URL` to a web player, the video is displayed in a `WebView`. If the source is a video stream URL, a `video` `HTML` element is displayed in the `WebView`. The component does not support playback of local video files. ![Video example]({{ site.url }}/img/ui-toolkit/video/video_player@2x.png "Video"){:.docs-component-image} ## API #### Props * **source**: string - Prop that defines the source of the video that will be rendered * **poster**: string - Prop that defines the source of the poster image * **height**: number - Prop that sets the height of the container where the video preview thumbnail will be rendered * **width**: number - Prop that sets the width of the container where the video preview thumbnail will be rendered #### Style names `Video` has no specific style names. #### Style * **container** - Style prop for container `View` that holds the `Video` component ## Example ![Video example]({{ site.url }}/img/ui-toolkit/video/video_player@2x.png "Video"){:.docs-component-image} #### JSX Declaration ```JSX