The defaultDate
/default_date
prop has a null or empty string value by default. You can pass an ISO date string (preferred rails method) or date object (preferred JS method) if you want a default value on page load. Use Ruby UTC DateTime objects and convert them to ISO date strings with DateTime.now.utc.iso8601
.
If you use a Date object without UTC time standardization the Date Picker kit may misinterpret that date as yesterdays date (consequence of timezone differentials and the Javascript Date Object constructor). See this GitHub issue for more information and the anti-pattern examples below.
<%= pb_rails("date_picker", props: { default_date: "07/25/2020", label: "Default Date String", picker_id: "date-picker-default-date1" }) %> <%= pb_rails("date_picker", props: { default_date: DateTime.current.utc.iso8601, label: "Default Date Dynamic", picker_id: "date-picker-default-date2" }) %> <%= pb_rails("date_picker", props: { default_date: [DateTime.current.utc.iso8601, (DateTime.current + 7.day).utc.iso8601], label: "Default Date Range", mode: "range", picker_id: "date-picker-default-date3" }) %> <%= pb_rails("date_picker", props: { label: "Default Behavior", picker_id: "date-picker-default-date4" }) %>
The date picker is built with the text input kit. Text input props you pass to the date picker kit will be forwarded to the input, with a few exceptions. The value
attribute is automatically handled and bound to whatever date string is contained by the input field. You cannot pass a custom value prop. id
props passed to the date picker kit will be assigned to it's parent/wrapper div. The pickerId
prop is passed directly to the input and is required to instatiate the date picker.
You must use inputAria
or input_aria
and inputData
or input_data
props if you wish to pass data or aria attributes to the text input kit. If you use data
or aria
props they will be passed to the date picker kit itself instead. Also be aware the default behavior of text input aria and data props is to pass those props to attributes on the wrapping div not on the input itself.
The placeholder prop has a default string value: "Select Date". You can replace this with your own string or an empty string if you'd prefer it blank.
<%= pb_rails("date_picker", props: { input_aria: { label: "input-field" }, input_data: { key: "value", key2: "value2" }, label: "Aria, Name, and Data Attributes", name: "date-field", picker_id: "date-picker-input1", }) %> <%= pb_rails("date_picker", props: { label: "Custom Placeholder", picker_id: "date-picker-input2", placeholder: "custom-placeholder", }) %> <%= pb_rails("date_picker", props: { label: "Blank Placeholder", picker_id: "date-picker-input3", placeholder: "" }) %> <%= pb_rails("date_picker", props: { disable_input: true, label: "Disable Input", picker_id: "date-picker-input4", placeholder: "Disabled Input" }) %>
Default label prop is "Date Picker"
. To remove the label set the hideLabel
prop in React or the hide_label
prop in Rails to true
.
A full list of formatting tokens, i.e. "m/d/Y"
can be found here.
<%= pb_rails("date_picker", props: { default_date: DateTime.current.utc.iso8601, format: "m-d-Y", picker_id: "date-picker-format1" }) %> <%= pb_rails("date_picker", props: { default_date: DateTime.current.utc.iso8601, format: "m/d/y", picker_id: "date-picker-format2" }) %> <%= pb_rails("date_picker", props: { default_date: DateTime.current.utc.iso8601, format: "n-j-y", picker_id: "date-picker-format3" }) %> <%= pb_rails("date_picker", props: { default_date: DateTime.current.utc.iso8601, format: "Y-d-m", picker_id: "date-picker-format4" }) %>
<%= pb_rails("date_picker", props: { disable_date: [(DateTime.current + 1.day).utc.iso8601], label: "Disable Single Date", picker_id: "single-disabled-date" }) %> <%= pb_rails("date_picker", props: { disable_date: [(DateTime.current + 1.day).utc.iso8601, (DateTime.current + 2.day).utc.iso8601], label: "Disable Multiple Dates", picker_id: "multiple-disabled-dates" }) %> <%= pb_rails("date_picker", props: { disable_range: [ { from: DateTime.current.utc.iso8601, to: (DateTime.current + 7.day).utc.iso8601, }, ], label: "Disable Single Range", picker_id: "single-date-range" }) %> <%= pb_rails("date_picker", props: { disable_range: [ { from: (DateTime.current + 1.day).utc.iso8601, to: (DateTime.current + 2.day).utc.iso8601, }, { from: (DateTime.current + 7.day).utc.iso8601, to: (DateTime.current + 14.day).utc.iso8601, }, ], label: "Disable Multiple Ranges", picker_id: "multiple-date-ranges" }) %> <%= pb_rails("date_picker", props: { disable_weekdays: ['Sunday', 'Saturday'], label: "Disable Specific Weekdays", picker_id: "disabled-weekdays" }) %>
<%= pb_rails("date_picker", props: { label: "Dynamic dates", max_date: (DateTime.current + 1.day).utc.iso8601, min_date: (DateTime.current - 1.day).utc.iso8601, picker_id: "date-picker-min-max1" }) %> <%= pb_rails("date_picker", props: { format: "m/d/Y", label: "Absolute formatted dates", max_date: "10/20/2020", min_date: "10/10/2020", picker_id: "date-picker-min-max2" }) %>
<%= pb_rails("button", props: { id: "close-btn", margin_right: "sm", text: "Close" }) %> <%= pb_rails("button", props: { id: "clear-btn", margin_right: "sm", text: "Clear" }) %> <%= pb_rails("button", props: { id: "today-btn", text: "Today" }) %> <%= pb_rails("date_picker", props: { hide_label: true, margin_top: "sm", picker_id: "fp-methods" }) %> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", () => { const fp = document.querySelector("#fp-methods")._flatpickr const closeBtn = document.querySelector("#close-btn") const clearBtn = document.querySelector("#clear-btn") const todayBtn = document.querySelector("#today-btn") closeBtn.addEventListener("click", () => { fp.close() }) clearBtn.addEventListener("click", () => { fp.clear() }) todayBtn.addEventListener("click", () => { fp.setDate(new Date(), true) }) }) <% end %>
You can find a full list of flatpickr events and hooks in their documentation.
<%= pb_rails("date_picker", props: { label: "onChange", picker_id: "date-picker-hooks-onchange" }) %> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", () => { <%# Access flatpickr instance with picker id and assign it a variable %> const fp = document.querySelector("#date-picker-hooks-onchange")._flatpickr <%# Define Hook %> const changeHook = () => { alert('date changed') } <%# Push one or more hooks to onChange config array %> fp.config.onChange.push(changeHook) }) <% end %> <%= pb_rails("date_picker", props: { label: "onOpen", picker_id: "date-picker-hooks-onopen" }) %> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", () => { <%# Access flatpickr instance with picker id and assign it a variable %> const fp = document.querySelector("#date-picker-hooks-onopen")._flatpickr <%# Define Hook %> const openHook = () => { alert('calendar opened') } <%# Push one or more hooks to onOpen config array %> fp.config.onOpen.push(openHook) }) <% end %>
<%= pb_rails("date_picker", props: { label: "Unformatted Date Object", default_date: Date.today, picker_id: "date-picker-anti-pattern1" }) %> <%= pb_rails("date_picker", props: { label: "Date Object Without Time - Displays Yesterday's Date", default_date: Date.today.to_datetime.utc.iso8601, picker_id: "date-picker-anti-pattern2" }) %> <%= pb_rails("date_picker", props: { label: "Unformatted DateTime Object", default_date: DateTime.current, picker_id: "date-picker-anti-pattern3" }) %> <%= pb_rails("date_picker", props: { label: "String Conversion Without ISO Formatting", default_date: DateTime.current.utc.to_s, picker_id: "date-picker-anti-pattern5" }) %>
<%= pb_rails("date_picker", props: { classname: "inline-date-picker", hide_icon: true, inline: true, picker_id: "date-picker-inline" }) %> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", (event) => { const fpInline = document.querySelector("#date-picker-inline")._flatpickr <!-- Display the angle-down icon when a date has been selected --> const showAngleDownHandler = () => { document.querySelector('.inline-date-picker').classList.add('show-angle-down-icon') } fpInline.config.onChange.push(showAngleDownHandler) }) <% end %>
By default selectType prop is disabled. To activate it set selectionType
prop in JSX/TSX to month
. To activate it set selection_type
prop in a rails file to month
.
By default selectType prop is disabled. To activate it set selectionType
prop in JSX/TSX to week
. To activate it set selection_type
prop in a rails file to week
.
To select time as well, you should pass the enableTime
boolean prop. You can also enable timezone display by passing showTimezone
.
Datepicker supports position
options from Flatpickr Options Documentation. There are multiple positioning options to choose from.
Note: In order for the above prop to work properly, you must also send staticPosition={false}
to your Datepicker kit instance.
Upon adding static={false}
to the date picker, you will notice that the date picker detaches from the input field while scrolling. This is a known Flatpickr nuance. By adding the scrollContainer
prop, you can tell the date picker which DOM container it should watch for scroll events. In this example, you can see that scrollContainer=".pb--page--content--main"
is being passed in order to keep the date picker correctly positioned on page scroll.
Useage: scrollContainer: .validQuerySelectorHere
<%= pb_rails("flex") do %> <%= pb_rails("flex/flex_item", props: {fixed_size: "50%"}) do %> <%= pb_rails("date_picker", props: { label: "Datepicker (opens on the right)", picker_id: "date-picker-positions1", position: "auto right", scroll_container: ".pb--page--content--main", static_position: false }) %> <% end %> <% end %> <%= pb_rails("flex") do %> <%= pb_rails("flex/flex_item", props: {fixed_size: "50%"}) do %> <%= pb_rails("date_picker", props: { label: "Datepicker (opens on the left)", picker_id: "date-picker-positions2", position: "auto left", scroll_container: ".pb--page--content--main", static_position: false }) %> <% end %> <% end %> <%= pb_rails("flex") do %> <%= pb_rails("flex/flex_item", props: {fixed_size: "50%"}) do %> <%= pb_rails("date_picker", props: { label: "Datepicker (opens above on the left)", picker_id: "date-picker-positions3", position: "above left", scroll_container: ".pb--page--content--main", static_position: false }) %> <% end %> <% end %> <%= pb_rails("flex") do %> <%= pb_rails("flex/flex_item", props: {fixed_size: "50%"}) do %> <%= pb_rails("date_picker", props: { label: "Datepicker (opens below on the right)", picker_id: "date-picker-positions4", position: "below right", scroll_container: ".pb--page--content--main", static_position: false }) %> <% end %> <% end %>
<%= pb_rails("card", props: {id: "position-element", margin_bottom: "md"}) do %> <%= pb_rails("body") do %>👋 Datepicker will position from here based on ID.<% end %> <% end %> <%= pb_rails("flex") do %> <%= pb_rails("flex/flex_item", props: {fixed_size: "50%"}) do %> <%= pb_rails("date_picker", props: { label: "Datepicker (opens on the right)", picker_id: "date-picker-position-element", position: "auto right", position_element: "#position-element", scroll_container: ".pb--page--content--main", static_position: false }) %> <% end %> <% end %> <%= pb_rails("card", props: {classname: "position-element", margin_bottom: "md"}) do %> <%= pb_rails("body") do %>👋 Datepicker will position from here based on class name.<% end %> <% end %> <%= pb_rails("flex") do %> <%= pb_rails("flex/flex_item", props: {fixed_size: "50%"}) do %> <%= pb_rails("date_picker", props: { label: "Datepicker (opens on the right)", picker_id: "date-picker-position-element2", position: "auto right", position_element: ".position-element", scroll_container: ".pb--page--content--main", static_position: false }) %> <% end %> <% end %>
<% example_collection = [ OpenStruct.new(name: "Alabama", value: 1), OpenStruct.new(name: "Alaska", value: 2), OpenStruct.new(name: "Arizona", value: 3), OpenStruct.new(name: "Arkansas", value: 4), OpenStruct.new(name: "California", value: 5), OpenStruct.new(name: "Colorado", value: 6), OpenStruct.new(name: "Connecticut", value: 7), OpenStruct.new(name: "Delaware", value: 8), OpenStruct.new(name: "Florida", value: 9), OpenStruct.new(name: "Georgia", value: 10), ] %> <%= pb_form_with(scope: :example, url: "", method: :get) do |form| %> <%= form.typeahead :example_user, props: { data: { typeahead_example1: true, user: {} }, placeholder: "Search for a user" } %> <%= form.text_field :example_text_field, props: { label: true } %> <%= form.telephone_field :example_phone_field, props: { label: true } %> <%= form.email_field :example_email_field, props: { label: true } %> <%= form.number_field :example_number_field, props: { label: true } %> <%= form.search_field :example_search_field, props: { label: true } %> <%= form.password_field :example_password_field, props: { label: true } %> <%= form.url_field :example_url_field, props: { label: true } %> <%= form.text_area :example_text_area, props: { label: true } %> <%= form.select :example_select, [ ["Yes", 1], ["No", 2] ], props: { label: true } %> <%= form.collection_select :example_collection_select, example_collection, :value, :name, props: { label: true } %> <%= form.check_box :example_checkbox, data: { field1: "value1", field2: "value2" }, props: { text: "Example Checkbox", label: true }, checked_value: "yes", unchecked_value: "no", id: "checkbox-id", name: "checkbox-name", class: "checkbox-class" %> <%= form.date_picker :example_date_picker_1, props: { label: true } %> <%= form.actions do |action| %> <%= action.submit %> <%= action.button props: { type: "reset", text: "Cancel", variant: "secondary" } %> <% end %> <% end %> <!-- form.typeahead user results example template --> <template data-typeahead-example-result-option> <%= pb_rails("user", props: { name: tag(:slot, name: "name"), orientation: "horizontal", align: "left", avatar_url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGP6zwAAAgcBApocMXEAAAAASUVORK5CYII=", avatar: true }) %> </template> <!-- form.typeahead JS example implementation --> <%= javascript_tag defer: "defer" do %> document.addEventListener("pb-typeahead-kit-search", function(event) { if (!event.target.dataset || !event.target.dataset.typeaheadExample1) return; fetch(`https://api.github.com/search/users?q=${encodeURIComponent(event.detail.searchingFor)}`) .then(response => response.json()) .then((result) => { const resultOptionTemplate = document.querySelector("[data-typeahead-example-result-option]") event.detail.setResults((result.items || []).map((user) => { const wrapper = resultOptionTemplate.content.cloneNode(true) wrapper.children[0].dataset.user = JSON.stringify(user) wrapper.querySelector('slot[name="name"]').replaceWith(user.login) wrapper.querySelector('img').dataset.src = user.avatar_url return wrapper })) }) }) document.addEventListener("pb-typeahead-kit-result-option-selected", function(event) { if (!event.target.dataset.typeaheadExample1) return; const selectedUserJSON = event.detail.selected.firstElementChild.dataset.user const selectedUserData = JSON.parse(selectedUserJSON) // set the input field's value event.target.querySelector('input[name=example_user]').value = selectedUserData.login // log the selected option's dataset console.log('The selected user data:') console.dir(selectedUserData) // do even more with the data later - TBD event.target.dataset.user = selectedUserJSON }) <% end %>
Validation displays an error with red border and red text below indicating that the user must fill out the field.
<% example_collection = [ OpenStruct.new(name: "Alabama", value: 1), OpenStruct.new(name: "Alaska", value: 2), OpenStruct.new(name: "Arizona", value: 3), OpenStruct.new(name: "Arkansas", value: 4), OpenStruct.new(name: "California", value: 5), OpenStruct.new(name: "Colorado", value: 6), OpenStruct.new(name: "Connecticut", value: 7), OpenStruct.new(name: "Delaware", value: 8), OpenStruct.new(name: "Florida", value: 9), OpenStruct.new(name: "Georgia", value: 10), ] %> <%= pb_form_with(scope: :example, method: :get, url: "", validate: true) do |form| %> <%= form.text_field :example_text_field, props: { label: true, required: true } %> <%= form.telephone_field :example_phone_field, props: { label: true, required: true, validation: { pattern: "[0-9]{3}-[0-9]{3}-[0-9]{4}", message: "Please enter a valid phone number (example: 888-888-8888)." } } %> <%= form.email_field :example_email_field, props: { label: true, required: true } %> <%= form.number_field :example_number_field, props: { label: true, required: true } %> <%= form.search_field :example_project_number, props: { label: true, required: true, validation: { pattern: "[0-9]{2}-[0-9]{5}", message: "Please enter a valid project number (example: 33-12345)." } } %> <%= form.password_field :example_password_field, props: { label: true, required: true } %> <%= form.url_field :example_url_field, props: { label: true, required: true } %> <%= form.text_area :example_text_area, props: { label: true, required: true } %> <%= form.select :example_select, [ ["Yes", 1], ["No", 2] ], props: { label: true, blank_selection: "Select One...", required: true } %> <%= form.collection_select :example_collection_select, example_collection, :value, :name, props: { label: true, blank_selection: "Select One...", required: true } %> <%= form.check_box :example_checkbox, props: { text: "Example Checkbox", label: true, required: true } %> <%= form.date_picker :example_date_picker_2, props: { label: true, required: true } %> <%= form.actions do |action| %> <%= action.submit %> <%= action.button props: { type: "reset", text: "Cancel", variant: "secondary" } %> <% end %> <% end %>
<div> <%= pb_rails("form_group") do %> <%= pb_rails("text_input", props: { label: "First Name", placeholder: "Enter First Name" }) %> <%= pb_rails("text_input", props: { label: "Middle Intial", placeholder: "Enter Middle Initial" }) %> <%= pb_rails("text_input", props: { label: "Last Name", placeholder: "Enter Last Name" }) %> <% end %> </div>
<div> <%= pb_rails("form_group") do %> <%= pb_rails("text_input", props: { label: "with label", placeholder: "Search" }) %> <%= pb_rails("button", props: { text: "Submit", variant: "secondary" }) %> <% end %> <br/> <br/> <%= pb_rails("form_group") do %> <%= pb_rails("text_input", props: { placeholder: "Search" }) %> <%= pb_rails("button", props: { text: "Submit", variant: "secondary" }) %> <% end %> </div>
Full Width is a prop that can be added to any of the Form Group options. This prop allows the Form Group to stretch the full width of the div.
<div> <%= pb_rails("form_group", props: { full_width: true }) do %> <%= pb_rails("text_input", props: { label: "First Name", placeholder: "Enter First Name" }) %> <%= pb_rails("text_input", props: { label: "Middle Intial", placeholder: "Enter Middle Initial" }) %> <%= pb_rails("text_input", props: { label: "Last Name", placeholder: "Enter Last Name" }) %> <% end %> <br/> <br/> <%= pb_rails("form_group", props: { full_width: true }) do %> <%= pb_rails("text_input", props: { placeholder: "Search" }) %> <%= pb_rails("button", props: { text: "Submit", variant: "secondary" }) %> <% end %> </div>
<div> <%= pb_rails("form_group") do %> <%= pb_rails("text_input", props: { label: "Artist", placeholder: "Enter Artist Name" }) %> <%= pb_rails("select", props: { blank_selection: "Genre", options: [ { value: "Country" }, { value: "Pop" }, { value: "Rock" }, { value: "Hip-Hop/Rap" }, { value: "Classical" }, { value: "Gospel" }, { value: "Alternative" }, { value: "Indie" }, { value: "Other" }, ] }) %> <% end %> </div>
<div> <%= pb_rails("form_group") do %> <%= pb_rails("selectable_card", props: { input_id: "cat1", name: "animal", value: "cat", multi: false }) do %> Cat <% end %> <%= pb_rails("selectable_card", props: { input_id: "dog1", name: "animal", value: "dog", multi: false }) do %> Dog <% end %> <% end %> </div>
<div> <%= pb_rails("form_group") do %> <%= pb_rails("selectable_card_icon", props: { icon: "basketball-ball", title_text: "Basketball", input_id: 7, multi: false, name: "select", }) %> <%= pb_rails("selectable_card_icon", props: { icon: "football-ball", title_text: "Football", input_id: 8, multi: false, name: "select", }) %> <% end %> </div>
The MultiLevelSelect kit renders a multi leveled select dropdown based on data from the user. treeData
is a required prop that is expected to contain the data in the form of an array of objects. See code snippet for an example data array.
For the React version of the kit, the onSelect
prop returns an array of all checked items, irrespective of whether it is a parent, child or grandchild. Open the console on this example and check and uncheck checkboxes to see this is action!
For the Rails version, the array of checked items is attached to the DOM in a data attribute titled data-tree
on the wrapping div around the MultiLevelSelect.
<% treeData = [{ label: "Power Home Remodeling", value: "Power Home Remodeling", id: "powerhome1", expanded: true, children: [ { label: "People", value: "People", id: "people1", children: [ { label: "Talent Acquisition", value: "Talent Acquisition", id: "talent1", }, { label: "Business Affairs", value: "Business Affairs", id: "business1", children: [ { label: "Initiatives", value: "Initiatives", id: "initiative1", }, { label: "Learning & Development", value: "Learning & Development", id: "development1", }, ], }, { label: "People Experience", value: "People Experience", id: "experience1", }, ], }, { label: "Contact Center", value: "Contact Center", id: "contact1", children: [ { label: "Appointment Management", value: "Appointment Management", id: "appointment1", }, { label: "Customer Service", value: "Customer Service", id: "customer1", }, { label: "Energy", value: "Energy", id: "energy1", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "default-multi-level-select", tree_data:treeData }) %>
The returnAllSelected
or return_all_selected
prop can be used when users want data on all checked nodes from the dropdown, irrespective of whether it is a parent or child node.
NOTE :This variant also does not automatically uncheck the parent when any of the child nodes are unchecked. returnAllSelected
is set to false by default.
<% treeData = [{ label: "Power Home Remodeling", value: "Power Home Remodeling", id: "powerhome1", expanded: true, children: [ { label: "People", value: "People", id: "people1", children: [ { label: "Talent Acquisition", value: "Talent Acquisition", id: "talent1", }, { label: "Business Affairs", value: "Business Affairs", id: "business1", children: [ { label: "Initiatives", value: "Initiatives", id: "initiative1", }, { label: "Learning & Development", value: "Learning & Development", id: "development1", }, ], }, { label: "People Experience", value: "People Experience", id: "experience1", }, ], }, { label: "Contact Center", value: "Contact Center", id: "contact1", children: [ { label: "Appointment Management", value: "Appointment Management", id: "appointment1", }, { label: "Customer Service", value: "Customer Service", id: "customer1", }, { label: "Energy", value: "Energy", id: "energy1", }, ], }, ], }] %> <%= pb_rails("multi_level_select", props: { id: "parent-persistence-multi-level-select", tree_data:treeData, return_all_selected: true }) %>
<%= pb_rails("passphrase", props: { classname: "pass_input_1" }) %> <%= pb_rails("passphrase", props: { confirmation: true, classname: "pass_input_2"}) %> <div id="match"> </div> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", () => { const useState = (defaultValue) => { let value = defaultValue; const getValue = () => value const setValue = (newValue) => { return value = newValue } return [getValue, setValue]; } const [input, setInput] = useState('') const [confirmationInput, setConfirmationInput] = useState('') const match = document.querySelector("#match") const input1 = document.querySelector(".pass_input_1").querySelector("input") const input2 = document.querySelector(".pass_input_2").querySelector("input") input1.addEventListener('input', (e) => { setInput(e.target.value) setMatchText() }); input2.addEventListener('input', (e) => { setConfirmationInput(e.target.value) setMatchText() }); const setMatchText = () => { if (input() && confirmationInput()) { if (input() === confirmationInput()) { match.textContent = "They match!" } else { match.textContent = "They don't match!" } } else { match.textContent = "" } } }) <% end %>
This example shows how to enhance the passphrase strenght by setting diferent thresholds and lengths.
The meterSettings
array contains different settings for each rendered input. The handleStrengthCalculation
handles the strength calculation using those settings, showing different results for the same passphrase
input.
By default, minLength
is 12. Try typing any value in the Default Example
input. Notice that the bar won't change from red until the minimum is met.
Adjust these props to tune the sensitivity of the bar.
Note: minimum length trumps strength and will set the bar to a red color, despite whatever strength is calculated.
This example depends on the zxcvbn
library.
You can use any library to achieve the same result, this example only intends to show how to add more features to the Passphrase
kit.
<%= pb_rails("passphrase", props: { label: "Default Settings", classname: "def_passphrase" }) %> <%= pb_rails("progress_simple", props: { percent: 0, id: "def_bar" }) %> <%= pb_rails("caption", props: { size: 'xs', text: "hello", id: "def_caption" }) %> <%= pb_rails("text_input", props: { label: "Calculated Strength", value: "0", disabled: true, id: "calc_strength" }) %> <%= pb_rails("passphrase", props: { label: "Min length = 5", classname: "min_5" }) %> <%= pb_rails("progress_simple", props: { percent: 0, id: "min_5_bar" }) %> <%= pb_rails("caption", props: { size: 'xs', text: "hello", id: "min_5_caption" }) %> <%= pb_rails("passphrase", props: { label: "Min length = 30", classname: "min_30" }) %> <%= pb_rails("progress_simple", props: { percent: 0, id: "min_30_bar" }) %> <%= pb_rails("caption", props: { size: 'xs', text: "hello", id: "min_30_caption" }) %> <%= pb_rails("passphrase", props: { label: "Average Threshold = 1", classname: "avg_1" }) %> <%= pb_rails("progress_simple", props: { percent: 0, id: "avg_1_bar" }) %> <%= pb_rails("caption", props: { size: 'xs', text: "hello", id: "avg_1_caption" }) %> <%= pb_rails("passphrase", props: { label: "Strong Threshold = 4", classname: "strong_4" }) %> <%= pb_rails("progress_simple", props: { percent: 0, id: "strong_4_bar" }) %> <%= pb_rails("caption", props: { size: 'xs', text: "hello", id: "strong_4_caption" }) %> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", () => { // variables for the passphrase kits you are targeting const defPassphrase = document.querySelector(".def_passphrase").querySelector("input") const min5 = document.querySelector(".min_5").querySelector("input") const min30 = document.querySelector(".min_30").querySelector("input") const avg1 = document.querySelector(".avg_1").querySelector("input") const strong4 = document.querySelector(".strong_4").querySelector("input") // variable for the text_input kit you are targeting const calcStrength = document.querySelector("#calc_strength") // variables for the progress_simple kits you are targeting const defBarVariant = document.getElementById("def_bar") const defBarPercent = document.getElementById("def_bar").querySelector("div") const min5BarVariant = document.getElementById("min_5_bar") const min5BarPercent = document.getElementById("min_5_bar").querySelector("div") const min30BarVariant = document.getElementById("min_30_bar") const min30BarPercent = document.getElementById("min_30_bar").querySelector("div") const avg1BarVariant = document.getElementById("avg_1_bar") const avg1BarPercent = document.getElementById("avg_1_bar").querySelector("div") const strong4BarVariant = document.getElementById("strong_4_bar") const strong4BarPercent = document.getElementById("strong_4_bar").querySelector("div") // hide all the progress_simple bars defBarVariant.style.display = 'none'; defBarPercent.style.display = 'none'; min5BarVariant.style.display = 'none'; min5BarPercent.style.display = 'none'; min30BarVariant.style.display = 'none'; min30BarPercent.style.display = 'none'; avg1BarVariant.style.display = 'none'; avg1BarPercent.style.display = 'none'; strong4BarVariant.style.display = 'none'; strong4BarPercent.style.display = 'none'; // variables for the caption kits you are targeting const defCaption = document.getElementById("def_caption") const min5Caption = document.getElementById("min_5_caption") const min30Caption = document.getElementById("min_30_caption") const avg1Caption = document.getElementById("avg_1_caption") const strong4Caption = document.getElementById("strong_4_caption") // hide all the captions defCaption.style.display = 'none'; min5Caption.style.display = 'none'; min30Caption.style.display = 'none'; avg1Caption.style.display = 'none'; strong4Caption.style.display = 'none'; // funtion that determines strenght of user passowrd using zxcvbn const handleStrengthCalculation = (settings) => { // define the settings object with its defaults const { passphrase = "", common = false, isPwned = false, averageThreshold = 2, minLength = 12, strongThreshold = 3, } = settings // define the resultsByScore objects, these return an object with a variant and percentage, // depending on the score of the password const resultByScore = { 0: { variant: 'negative', label: '', percent: 0, }, 1: { variant: 'negative', label: 'This passphrase is too common', percent: 25, }, 2: { variant: 'negative', label: 'Too weak', percent: 25, }, 3: { variant: 'warning', label: 'Almost there, keep going!', percent: 50, }, 4: { variant: 'positive', label: 'Success! Strong passphrase', percent: 100, } } const { score } = zxcvbn(passphrase); const noPassphrase = passphrase.length <= 0 const commonPassphrase = common || isPwned const weakPassphrase = passphrase.length < minLength || score < averageThreshold const averagePassphrase = score < strongThreshold const strongPassphrase = score >= strongThreshold // conditional that returns the score of the password, along with the resultByScore object // so we can change the percantage and variant of the progress_simple kit if (noPassphrase) { return {...resultByScore[0], score} } else if (commonPassphrase) { return {...resultByScore[1], score} } else if (weakPassphrase) { return {...resultByScore[2], score} } else if (averagePassphrase){ return {...resultByScore[3], score} } else if (strongPassphrase) { return {...resultByScore[4], score} } } // event listeners attached to the input field min5.addEventListener('input', (e) => { const passphrase = e.target.value; // defining setting object to spread into handleStrengthCalculation setting = { minLength: 5, } // pass in passphrase and setting object to handleStrengthCalculation and set that equal to result variable const result = handleStrengthCalculation({passphrase, ...setting}) // set the value of the text_input to the score calcStrength.value = result.score // conditional statment to show or hide progress_simple bar and caption if user has entered a password if (passphrase) { min5BarVariant.style.display = 'block'; min5BarPercent.style.display = 'block'; min5Caption.style.display = 'block'; } else { min5BarVariant.style.display = 'none'; min5BarPercent.style.display = 'none'; min5Caption.style.display = 'none'; } // set the width of the progress_simple kit min5BarPercent.style.width = result.percent.toString()+ "%" // set the variant of the progress_simple kit min5BarVariant.setAttribute("class", "pb_progress_simple_kit_"+ result.variant +"_left"); // set the text of the caption kit min5Caption.textContent = result.label }); defPassphrase.addEventListener('input', (e) => { const passphrase = e.target.value; const result = handleStrengthCalculation({passphrase}) calcStrength.value = result.score if (passphrase) { defBarVariant.style.display = 'block'; defBarPercent.style.display = 'block'; defCaption.style.display = 'block'; } else { defBarVariant.style.display = 'none'; defBarPercent.style.display = 'none'; defCaption.style.display = 'none'; } defBarPercent.style.width = result.percent.toString()+ "%" defBarVariant.setAttribute("class", "pb_progress_simple_kit_"+ result.variant +"_left"); defCaption.textContent = result.label }); min30.addEventListener('input', (e) => { const passphrase = e.target.value; setting = { minLength: 30, } const result = handleStrengthCalculation({passphrase, ...setting}) calcStrength.value = result.score if (passphrase) { min30BarVariant.style.display = 'block'; min30BarPercent.style.display = 'block'; min30Caption.style.display = 'block'; } else { min30BarVariant.style.display = 'none'; min30BarPercent.style.display = 'none'; min30Caption.style.display = 'none'; } min30BarPercent.style.width = result.percent.toString()+ "%" min30BarVariant.setAttribute("class", "pb_progress_simple_kit_"+ result.variant +"_left"); min30Caption.textContent = result.label }); avg1.addEventListener('input', (e) => { const passphrase = e.target.value; setting = { averageThreshold: 1, } const result = handleStrengthCalculation({passphrase, ...setting}) calcStrength.value = result.score if (passphrase) { avg1BarVariant.style.display = 'block'; avg1BarPercent.style.display = 'block'; avg1Caption.style.display = 'block'; } else { avg1BarVariant.style.display = 'none'; avg1BarPercent.style.display = 'none'; avg1Caption.style.display = 'none'; } avg1BarPercent.style.width = result.percent.toString()+ "%" avg1BarVariant.setAttribute("class", "pb_progress_simple_kit_"+ result.variant +"_left"); avg1Caption.textContent = result.label }); strong4.addEventListener('input', (e) => { const passphrase = e.target.value; setting = { strongThreshold: 4, } const result = handleStrengthCalculation({passphrase, ...setting}) calcStrength.value = result.score if (passphrase) { strong4BarVariant.style.display = 'block'; strong4BarPercent.style.display = 'block'; strong4Caption.style.display = 'block'; } else { strong4BarVariant.style.display = 'none'; strong4BarPercent.style.display = 'none'; strong4Caption.style.display = 'none'; } strong4BarPercent.style.width = result.percent.toString()+ "%" strong4BarVariant.setAttribute("class", "pb_progress_simple_kit_"+ result.variant +"_left"); strong4Caption.textContent = result.label }); }) <% end %>
inputProps
is passed directly to an underlying Text Input kit. See the specific docs here for more details.
<%= pb_rails("passphrase", props: { input_props: { disabled: true, id: "my-disabled-passphrase", name: "my-disabled-field", }, label: "Pass props directly to input kit" }) %> <%= pb_rails("passphrase", props: { input_props: { id: "my-custome-id", name: "my-value-name", }, label: "Set name and ID for use in form libraries" }) %>
showTipsBelow
(react) / show_tips_below
(rails) takes 'xs', 'sm', 'md', 'lg', 'xl' and only show the tips below the given screen size. Similar to the responsive table breakpoints. Omit this prop to always show.
<%= pb_rails("passphrase", props: { label: "Pass an array of strings to the tips prop", tips: ['And the info icon will appear.', 'Each string will be displayed as its own tip'], }) %> <%= pb_rails("passphrase", props: { label: "Omit the prop to hide the icon" }) %> <%= pb_rails("passphrase", props: { label: "Only show tips at small screen size", show_tips_below: "sm", tips: ['Make the password longer', 'Type more things', 'Use something else'], }) %> <%= pb_rails("passphrase", props: { label: "Only show tips at medium screen size", show_tips_below: "md", tips: ['Make the password longer', 'Type more things', 'Use something else'], }) %> <%= pb_rails("passphrase", props: { label: "Only show tips at large screen size", show_tips_below: "lg", tips: ['Make the password longer', 'Type more things', 'Use something else'], }) %>
Strength is calculated on a 0-4 scale by the Zxcvbn package.
This example depends on the zxcvbn
library.
You can use any library to achieve the same result, this example only intends to show how to add more features to the Passphrase
kit.
<%= pb_rails("passphrase", props: { label: "Passphrase", classname: "passphrase_change" }) %> <%= pb_rails("progress_simple", props: { percent: 0, id: "bar_change" }) %> <%= pb_rails("caption", props: { size: 'xs', text: "hello", id: "caption_change" }) %> <%= pb_rails("text_input", props: { label: "Passphrase Strength", value: "0", disabled: true, id: "calc_strength_change" }) %> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", () => { // variables for the kits you are targeting const passphrase = document.querySelector(".passphrase_change").querySelector("input") const calcStrength = document.querySelector("#calc_strength_change") const barVariant = document.getElementById("bar_change") const barPercent = document.getElementById("bar_change").querySelector("div") const caption = document.getElementById("caption_change") // hide the bar and captions barVariant.style.display = 'none'; barPercent.style.display = 'none'; caption.style.display = 'none'; const handleStrengthCalculation = (settings) => { const { passphrase = "", common = false, isPwned = false, averageThreshold = 2, minLength = 12, strongThreshold = 3, } = settings const resultByScore = { 0: { variant: 'negative', label: '', percent: 0, }, 1: { variant: 'negative', label: 'This passphrase is too common', percent: 25, }, 2: { variant: 'negative', label: 'Too weak', percent: 25, }, 3: { variant: 'warning', label: 'Almost there, keep going!', percent: 50, }, 4: { variant: 'positive', label: 'Success! Strong passphrase', percent: 100, } } const { score } = zxcvbn(passphrase); const noPassphrase = passphrase.length <= 0 const commonPassphrase = common || isPwned const weakPassphrase = passphrase.length < minLength || score < averageThreshold const averagePassphrase = score < strongThreshold const strongPassphrase = score >= strongThreshold if (noPassphrase) { return {...resultByScore[0], score} } else if (commonPassphrase) { return {...resultByScore[1], score} } else if (weakPassphrase) { return {...resultByScore[2], score} } else if (averagePassphrase){ return {...resultByScore[3], score} } else if (strongPassphrase) { return {...resultByScore[4], score} } } // event listeners attached to the input field passphrase.addEventListener('input', (e) => { const passphrase = e.target.value; // pass in passphrase to the handleStrengthCalculation and set that equal to result variable const result = handleStrengthCalculation({passphrase: passphrase}) // set the value of the text_input to the score calcStrength.value = result.score // conditional statment to show or hide progress_simple bar and caption if user has entered a password if (passphrase) { barVariant.style.display = 'block'; barPercent.style.display = 'block'; caption.style.display = 'block'; } else { barVariant.style.display = 'none'; barPercent.style.display = 'none'; caption.style.display = 'none'; } // set the width of the progress_simple kit barPercent.style.width = result.percent.toString()+ "%" // set the variant of the progress_simple kit barVariant.setAttribute("class", "pb_progress_simple_kit_"+ result.variant +"_left"); // set the text of the caption kit caption.textContent = result.label }); }) <% end %>
This example depends on the zxcvbn
library.
You can use any library to achieve the same result, this example only intends to show how to add more features to the Passphrase
kit.
<%= pb_rails("body", props: { margin_bottom: "md", id: "body_common" }) %> <%= pb_rails("passphrase", props: { label: "Passphrase", classname: "passphrase_common" }) %> <%= pb_rails("progress_simple", props: { percent: 0, id: "bar_common" }) %> <%= pb_rails("caption", props: { size: 'xs', text: "hello", id: "caption_common" }) %> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", () => { const commonText = document.querySelector("#body_common") // variables for the kits you are targeting const passphrase = document.querySelector(".passphrase_common").querySelector("input") const barVariant = document.getElementById("bar_common") const barPercent = document.getElementById("bar_common").querySelector("div") const caption = document.getElementById("caption_common") // hide the bar and captions barVariant.style.display = 'none'; barPercent.style.display = 'none'; caption.style.display = 'none'; const handleStrengthCalculation = (settings) => { const { passphrase = "", common = false, isPwned = false, averageThreshold = 2, minLength = 12, strongThreshold = 3, } = settings const resultByScore = { 0: { variant: 'negative', label: '', percent: 0, }, 1: { variant: 'negative', label: 'This passphrase is too common', percent: 25, }, 2: { variant: 'negative', label: 'Too weak', percent: 25, }, 3: { variant: 'warning', label: 'Almost there, keep going!', percent: 50, }, 4: { variant: 'positive', label: 'Success! Strong passphrase', percent: 100, } } const { score } = zxcvbn(passphrase); const noPassphrase = passphrase.length <= 0 const commonPassphrase = common || isPwned const weakPassphrase = passphrase.length < minLength || score < averageThreshold const averagePassphrase = score < strongThreshold const strongPassphrase = score >= strongThreshold if (noPassphrase) { return {...resultByScore[0], score} } else if (commonPassphrase) { return {...resultByScore[1], score} } else if (weakPassphrase) { return {...resultByScore[2], score} } else if (averagePassphrase){ return {...resultByScore[3], score} } else if (strongPassphrase) { return {...resultByScore[4], score} } } // array that holds the common passwords you wish to target const COMMON_PASSPHRASES = ['passphrase', 'apple', 'password', 'p@55w0rd'] commonText.textContent = `Try typing any of the following: ${COMMON_PASSPHRASES.join(', ')}` // function that checks if the user password is in the common password list const isCommon = (passphrase) => { if (COMMON_PASSPHRASES.includes(passphrase)) return true return false } // event listeners attached to the input field passphrase.addEventListener('input', (e) => { const passphrase = e.target.value; // pass in passphrase to the handleStrengthCalculation and set that equal to result variable const result = handleStrengthCalculation({ passphrase: passphrase, common: isCommon(passphrase) }) // conditional statment to show or hide progress_simple bar and caption if user has entered a password if (passphrase) { barVariant.style.display = 'block'; barPercent.style.display = 'block'; caption.style.display = 'block'; } else { barVariant.style.display = 'none'; barPercent.style.display = 'none'; caption.style.display = 'none'; } // set the width of the progress_simple kit barPercent.style.width = result.percent.toString()+ "%" // set the variant of the progress_simple kit barVariant.setAttribute("class", "pb_progress_simple_kit_"+ result.variant +"_left"); // set the text of the caption kit caption.textContent = result.label }); }) <% end %>
Use HaveIBeenPwned's API to check for breached passwords.
As the passphrase is typed, it is checked against more than half a billion breached passwords, to help ensure its not compromised.
Should it fail, the feedback will express the passphrase is too common, prompting the user to change.
This uses their k-Anonymity model, so only the first 5 characters of a hashed copy of the passphrase are sent.
This example depends on the zxcvbn
library and haveibeenpwned
API.
You can use any library to achieve the same result, this example only intends to show how to add more features to the Passphrase
kit.
<%= pb_rails("passphrase", props: { label: "Passphrase", classname: "passphrase_breached" }) %> <%= pb_rails("progress_simple", props: { percent: 0, id: "bar_breached" }) %> <%= pb_rails("caption", props: { size: 'xs', text: "hello", id: "caption_breached" }) %> <%= javascript_tag do %> window.addEventListener("DOMContentLoaded", () => { // variables for the kits you are targeting const passphrase = document.querySelector(".passphrase_breached").querySelector("input") const barVariant = document.getElementById("bar_breached") const barPercent = document.getElementById("bar_breached").querySelector("div") const caption = document.getElementById("caption_breached") // hide the bar and captions barVariant.style.display = 'none'; barPercent.style.display = 'none'; caption.style.display = 'none'; const handleStrengthCalculation = (settings) => { const { passphrase = "", common = false, isPwned = false, averageThreshold = 2, minLength = 12, strongThreshold = 3, } = settings const resultByScore = { 0: { variant: 'negative', label: '', percent: 0, }, 1: { variant: 'negative', label: 'This passphrase is too common', percent: 25, }, 2: { variant: 'negative', label: 'Too weak', percent: 25, }, 3: { variant: 'warning', label: 'Almost there, keep going!', percent: 50, }, 4: { variant: 'positive', label: 'Success! Strong passphrase', percent: 100, } } const { score } = zxcvbn(passphrase); const noPassphrase = passphrase.length <= 0 const commonPassphrase = common || isPwned const weakPassphrase = passphrase.length < minLength || score < averageThreshold const averagePassphrase = score < strongThreshold const strongPassphrase = score >= strongThreshold if (noPassphrase) { return {...resultByScore[0], score} } else if (commonPassphrase) { return {...resultByScore[1], score} } else if (weakPassphrase) { return {...resultByScore[2], score} } else if (averagePassphrase){ return {...resultByScore[3], score} } else if (strongPassphrase) { return {...resultByScore[4], score} } } // event listeners attached to the input field passphrase.addEventListener('input', (e) => { const passphrase = e.target.value; let pwndMatch = false const checkHaveIBeenPwned = async function (passphrase) { const buffer = new TextEncoder('utf-8').encode(passphrase) const digest = await crypto.subtle.digest('SHA-1', buffer) const hashArray = Array.from(new Uint8Array(digest)) const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') const firstFive = hashHex.slice(0, 5) const endOfHash = hashHex.slice(5) const resp = await fetch(`https://api.pwnedpasswords.com/range/${firstFive}`) const text = await resp.text() if (passphrase.length < 5) { pwndMatch = false } else { pwndMatch = text.split('\n').some((line) => { return line.split(':')[0] === endOfHash.toUpperCase() }) } // pass in passphrase and isPwnd match to the handleStrengthCalculation and set that equal to result variable const result = handleStrengthCalculation({ passphrase: passphrase, isPwned: pwndMatch }); // conditional statment to show or hide progress_simple bar and caption if user has entered a password if (passphrase) { barVariant.style.display = 'block'; barPercent.style.display = 'block'; caption.style.display = 'block'; } else { barVariant.style.display = 'none'; barPercent.style.display = 'none'; caption.style.display = 'none'; } // set the width of the progress_simple kit barPercent.style.width = result.percent.toString()+ "%" // set the variant of the progress_simple kit barVariant.setAttribute("class", "pb_progress_simple_kit_"+ result.variant +"_left"); // set the text of the caption kit caption.textContent = result.label } checkHaveIBeenPwned(passphrase) }); }) <% end %>
Preferred countries will display in the order they are listed with the first country preselected, and all non-preferred countries listed alphabetically below a section separator within the remaining dropdown.
<%= pb_rails("radio", props: { text: "Power", input_options: { tabindex: 0 }, name: "group 1", value: "Power" }) %> <br> <%= pb_rails("radio", props: { text: "Nitro", name: "group 1", value: "Nitro" }) %> <br> <%= pb_rails("radio", props: { text: "Google", name: "group 1", value: "Google" }) %>
<%= pb_rails("radio", props: { text: "Power" }) do %> <input type="radio" name="group 3" value="power" > <% end %> <br> <%= pb_rails("radio", props: { text: "Nitro" }) do %> <input type="radio" name="group 3" value="nitro" checked> <% end %> <br> <%= pb_rails("radio", props: { text: "Google" }) do %> <input type="radio"name="group 3" value="google"> <% end %>
<%= pb_rails("flex") do %> <%= pb_rails("radio", props: { alignment: "vertical", text: "Power", input_options: { tabindex: 0 }, margin_right: "sm", name: "group 1", value: "Power" }) %> <%= pb_rails("radio", props: { alignment: "vertical", text: "Nitro", margin_right: "sm", name: "group 1", value: "Nitro" }) %> <%= pb_rails("radio", props: { alignment: "vertical", text: "Google", name: "group 1", value: "Google" }) %> <% end %>
<%= pb_rails("rich_text_editor", props: {id: "sticky", sticky: true, value: "In this example, when you scroll down, the rich text editor's toolbar will scroll along with the page and it will no longer be visible at the top of the page. Dummy text to enable scroll.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ornare lorem ut pellentesque tempor. Vivamus ut ex vestibulum velit rich text editor eleifend fringilla. Sed non metus dictum, elementum mauris wysiwyg html editor non, sagittis odio. Nullam pellentesque leo sit amet ante suscipit wysiwyg html editor sagittis. Donec tempus vulputate suscipit. Ut non felis rich text editor ac dolor pulvinar lacinia eu eget urna. Sed tincidunt sapien vulputate tellus fringilla sodales. Morbi accumsan dui wysiwyg html editor sed massa pellentesque, quis vestibulum lectus scelerisque. Nulla ultrices mi id felis luctus aliquet. Donec nec ligula wysiwyg html editor pretium sapien semper dictum eu id quam. Etiam ut sollicitudin nibh. Quisque eu ultrices dui. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus rich text editor mi eu consequat. Nullam tincidunt erat et placerat mattis. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus mi eu consequat. Nullam tincidunt erat et placerat mattis.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ornare lorem ut pellentesque tempor. Vivamus ut ex vestibulum velit rich text editor eleifend fringilla. Sed non metus dictum, elementum mauris wysiwyg html editor non, sagittis odio. Nullam pellentesque leo sit amet ante suscipit wysiwyg html editor sagittis. Donec tempus vulputate suscipit. Ut non felis rich text editor ac dolor pulvinar lacinia eu eget urna. Sed tincidunt sapien vulputate tellus fringilla sodales. Morbi accumsan dui wysiwyg html editor sed massa pellentesque, quis vestibulum lectus scelerisque. Nulla ultrices mi id felis luctus aliquet. Donec nec ligula wysiwyg html editor pretium sapien semper dictum eu id quam. Etiam ut sollicitudin nibh. Quisque eu ultrices dui. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor vulputate suscipit. Ut non felis rich text editor ac dolor pulvinar lacinia eu eget urna. Sed tincidunt sapien vulputate tellus fringilla sodales. Morbi accumsan dui wysiwyg html editor sed massa pellentesque, quis vestibulum lectus scelerisque. Nulla ultrices mi id felis luctus aliquet. Donec nec ligula wysiwyg html editor pretium sapien semper dictum eu id quam. Etiam ut sollicitudin nibh. Quisque eu ultrices dui. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus rich text editor mi eu consequat. Nullam tincidunt erat et placerat mattis. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus mi eu consequat. Nullam tincidunt erat et placerat mattis.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ornare lorem ut pellentesque tempor. Vivamus ut ex vestibulum velit rich text editor eleifend fringilla. Sed non metus dictum, elementum mauris wysiwyg html editor non, sagittis odio. Nullam pellentesque leo sit amet ante suscipit wysiwyg html editor sagittis. Donec tempus vulputate suscipit. Ut non felis rich text editor ac dolor pulvinar lacinia eu eget urna. Sed tincidunt sapien vulputate tellus fringilla sodales. Morbi accumsan dui wysiwyg html editor sed massa pellentesque, quis vestibulum lectus scelerisque. Nulla ultrices mi id felis luctus aliquet. Donec nec ligula wysiwyg html editor pretium sapien semper dictum eu id quam. Etiam ut sollicitudin nibh. Quisque eu ultrices dui. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus rich text editor mi eu consequat. Nullam tincidunt erat et placerat mattis. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus mi eu consequat. Nullam tincidunt erat et placerat mattis.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ornare lorem ut pellentesque tempor. Vivamus ut ex vestibulum velit rich text editor eleifend fringilla. Sed non metus dictum, elementum mauris wysiwyg html editor non, sagittis odio. Nullam pellentesque leo sit amet ante suscipit wysiwyg html editor sagittis. Donec tempus vulputate suscipit. Ut non felis rich text editor ac dolor pulvinar lacinia eu eget urna. Sed tincidunt sapien vulputate tellus fringilla sodales. Morbi accumsan dui wysiwyg html editor sed massa pellentesque, quis vestibulum lectus scelerisque. Nulla ultrices mi id felis luctus aliquet. Donec nec ligula wysiwyg html editor pretium sapien semper dictum eu id quam. Etiam ut sollicitudin nibh. Quisque eu ultrices dui. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus rich text editor mi eu consequat. Nullam tincidunt erat et placerat mattis. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus mi eu consequat. Nullam tincidunt erat et placerat mattis.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ornare lorem ut pellentesque tempor. Vivamus ut ex vestibulum velit rich text editor eleifend fringilla. Sed non metus dictum, elementum mauris wysiwyg html editor non, sagittis odio. Nullam pellentesque leo sit amet ante suscipit wysiwyg html editor sagittis. Donec tempus vulputate suscipit. Ut non felis rich text editor ac dolor pulvinar lacinia eu eget urna. Sed tincidunt sapien vulputate tellus fringilla sodales. Morbi accumsan dui wysiwyg html editor sed massa pellentesque, quis vestibulum lectus scelerisque. Nulla ultrices mi id felis luctus aliquet. Donec nec ligula wysiwyg html editor pretium sapien semper dictum eu id quam. Etiam ut sollicitudin nibh. Quisque eu ultrices dui. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus rich text editor mi eu consequat. Nullam tincidunt erat et placerat mattis. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus mi eu consequat. Nullam tincidunt erat et placerat mattis.sit amet ex. Aliquam cursus maximus rich text editor mi eu consequat. Nullam tincidunt erat et placerat mattis. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus mi eu consequat. Nullam tincidunt erat et placerat mattis.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ornare lorem ut pellentesque tempor. Vivamus ut ex vestibulum velit rich text editor eleifend fringilla. Sed non metus dictum, elementum mauris wysiwyg html editor non, sagittis odio. Nullam pellentesque leo sit amet ante suscipit wysiwyg html editor sagittis. Donec tempus vulputate suscipit. Ut non felis rich text editor ac dolor pulvinar lacinia eu eget urna. Sed tincidunt sapien vulputate tellus fringilla sodales. Morbi accumsan dui wysiwyg html editor sed massa pellentesque, quis vestibulum lectus scelerisque. Nulla ultrices mi id felis luctus aliquet. Donec nec ligula wysiwyg html editor pretium sapien semper dictum eu id quam. Etiam ut sollicitudin nibh. Quisque eu ultrices dui. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus rich text editor mi eu consequat. Nullam tincidunt erat et placerat mattis. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus mi eu consequat. Nullam tincidunt erat et placerat mattis.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ornare lorem ut pellentesque tempor. Vivamus ut ex vestibulum velit rich text editor eleifend fringilla. Sed non metus dictum, elementum mauris wysiwyg html editor non, sagittis odio. Nullam pellentesque leo sit amet ante suscipit wysiwyg html editor sagittis. Donec tempus vulputate suscipit. Ut non felis rich text editor ac dolor pulvinar lacinia eu eget urna. Sed tincidunt sapien vulputate tellus fringilla sodales. Morbi accumsan dui wysiwyg html editor sed massa pellentesque, quis vestibulum lectus scelerisque. Nulla ultrices mi id felis luctus aliquet. Donec nec ligula wysiwyg html editor pretium sapien semper dictum eu id quam. Etiam ut sollicitudin nibh. Quisque eu ultrices dui. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus rich text editor mi eu consequat. Nullam tincidunt erat et placerat mattis. Nunc rich text editor congue, enim vitae dictum dignissim, libero nisl sagittis augue, non aliquet nibh tortor sit amet ex. Aliquam cursus maximus mi eu consequat. Nullam tincidunt erat et placerat mattis."}) %>
<% changelog = "<div> <strong>Changelog:<br></strong> [INSERT LINK]<br><br> You can test the normal spots of Playbook rails and react on dev docs plus the following: </div> <div> <br> </div>" %> <% release = "<div> <div> <strong>Story Background</strong> </div> <div> Follow the{' '} <a href='https://github.com/powerhome/playbook/wiki/Release-Team-Guide'> release process </a>{' '} to create a new version, create a gem, and package. Create a Ninja testing plan, then update Nitro with the new version. </div> <div> <br /> </div> <div> <strong>Timeline / Due Date</strong> </div> <div> <em>Release End of business Thursday</em> </div> <div> <em>Testing on Nitro End of business Friday</em> </div> <div> <br /> </div> <div> <strong>Definition of done</strong> </div> <ol> <li>Merge all PR’s</li> <li>Update the final CHANGELOG</li> <li>Version up and generate NPM, and RubyGem</li> <li>Create next version branch and milestone</li> <li>Update default branch and branch protection rules </li> <li>Notify Everyone of new version</li> <li> Generate testing plan and pages to test for Ninjas (update runway ticket) </li> <li>Update version on Nitro and get on Demo</li> <li>Send Ninjas demo and runway ticket for testing</li> <li>Ninja Approved + PR Approved</li> </ol> <div> <br /> </div> <div> <strong>Stakeholders / Sign-off</strong> </div> <ul> <li>Code Owners</li> </ul> <div> <br /> <strong>Cadence</strong> <br /> Jason, Jon, Stephen, Jasper, Brendan, Cole </div> </div>" %> <%= pb_rails("select", props: { id: "rails-select-dropdown", label: "", name: "", blank_selection: "Select a template", options: [ { value: release, value_text: "Playbook Release", }, { value: changelog, value_text: "Changelog", }, ] }) %> <%= pb_rails("rich_text_editor", props: {classname: 'template-test', id: "template", template: '' }) %> <script> const updateContent = (template) => { const trix = document.querySelector('.template-test trix-editor'); const editor = trix.editor; console.log(editor) editor.loadHTML("") editor.setSelectedRange([0, 0]) editor.insertHTML(template) } window.addEventListener('DOMContentLoaded', () => { const editor = document.querySelector("#rails-select-dropdown") editor.addEventListener('change', function() { console.log('You selected: ', this.value); const template = this.value updateContent(template); }); }); </script>
<%= pb_rails("rich_text_editor", props: { id: "content-preview-editor" }) %> <div id="card-obfuscation" style="display:none"> <%= pb_rails("card", props: { margin_top: "md" }) do %> <div id="content-preview" class="trix-content"> </div> <% end %> </div> <%= pb_rails("button", props: { id: "preview-button", variant: "secondary", margin_top: "md" }) do %> <span>Preview Output</span> <% end %> <script> // Button event handler const button = document.getElementById('preview-button'); button.addEventListener('click', function() { const content = document.getElementById('content-preview-editor'); const previewArea = document.getElementById('content-preview'); const cardDiv = document.getElementById('card-obfuscation'); previewArea.innerHTML = content.value; cardDiv.classList.add('mt_md'); cardDiv.style.display = 'block'; }); </script>